trading-platform/docs/97-adr/ADR-007-security.md
rckrdmrd c1b5081208 feat(ml): Complete FASE 11 - BTCUSD update and comprehensive documentation alignment
ML Engine Updates:
- Updated BTCUSD with Polygon API data (2024-2025): 215,699 new records
- Re-trained all ML models: Attention (R²: 0.223), Base, Metamodel (87.3% confidence)
- Backtest results: +176.71R profit with aggressive_filter strategy

Documentation Consolidation:
- Created docs/99-analisis/_MAP.md index with 13 new analysis documents
- Consolidated inventories: removed duplicates from orchestration/inventarios/
- Updated ML_INVENTORY.yml with BTCUSD metrics and training results
- Added execution reports: FASE11-BTCUSD, correction issues, alignment validation

Architecture & Integration:
- Updated all module documentation with NEXUS v3.4 frontmatter
- Fixed _MAP.md indexes across all folders
- Updated orchestration plans and traces

Files: 229 changed, 5064 insertions(+), 1872 deletions(-)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 09:31:29 -06:00

483 lines
13 KiB
Markdown

---
id: "ADR-007-security"
title: "Seguridad y Autenticación"
type: "Documentation"
project: "trading-platform"
version: "1.0.0"
updated_date: "2026-01-04"
---
# ADR-006: Seguridad y Autenticación
**Estado:** Aceptado
**Fecha:** 2025-12-06
**Decisores:** Tech Lead, Arquitecto, Security Engineer
**Relacionado:** ADR-001, ADR-005
---
## Contexto
Trading Platform maneja datos financieros críticos y transacciones reales. Necesitamos:
1. **Authentication**: Identificar usuarios de forma segura
2. **Authorization**: Controlar acceso a recursos (portfolios, predictions, pagos)
3. **Data Protection**: Encriptar datos sensibles (passwords, API keys, PII)
4. **Attack Prevention**: Proteger contra brute force, XSS, CSRF, SQL injection
5. **Compliance**: Preparar para GDPR, PCI-DSS (pagos con Stripe)
6. **Audit Trail**: Logs de acciones críticas (trades, transfers, config changes)
Requisitos de Seguridad:
- Zero-trust: Never trust, always verify
- Defense in depth: Múltiples capas de seguridad
- Fail securely: Errores deben denegar acceso por defecto
---
## Decisión
### Authentication Strategy
**JWT (JSON Web Tokens) + Refresh Tokens**
#### Access Token (Short-lived)
```typescript
{
"sub": "user_123abc", // User ID
"email": "user@example.com",
"role": "premium", // User tier
"iat": 1701878400, // Issued at
"exp": 1701882000 // Expires in 1 hour
}
```
#### Refresh Token (Long-lived)
- Stored in **httpOnly cookie** (no JavaScript access)
- TTL: 7 days
- Rotated on each use (automatic refresh)
- Revocable via Redis blacklist
### Password Security
**bcrypt** with cost factor 12
```typescript
// apps/backend/src/services/auth.service.ts
import bcrypt from 'bcryptjs';
const SALT_ROUNDS = 12; // 2^12 iterations (~250ms)
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(
password: string,
hash: string
): Promise<boolean> {
return bcrypt.compare(password, hash);
}
```
### Password Requirements
```typescript
const PASSWORD_POLICY = {
minLength: 12,
requireUppercase: true,
requireLowercase: true,
requireNumber: true,
requireSpecial: true,
maxAge: 90 * 24 * 60 * 60 * 1000, // 90 days
preventReuse: 5, // Last 5 passwords
};
```
### Multi-Factor Authentication (MFA)
**TOTP (Time-based One-Time Password)** using **speakeasy**
```typescript
// apps/backend/src/services/mfa.service.ts
import speakeasy from 'speakeasy';
import QRCode from 'qrcode';
async function generateMFASecret(userId: string) {
const secret = speakeasy.generateSecret({
name: `Trading Platform (${userId})`,
issuer: 'Trading Platform'
});
const qrCode = await QRCode.toDataURL(secret.otpauth_url);
// Store secret encrypted in DB
await db.user.update({
where: { id: userId },
data: { mfaSecret: encrypt(secret.base32) }
});
return { secret: secret.base32, qrCode };
}
function verifyMFAToken(secret: string, token: string): boolean {
return speakeasy.totp.verify({
secret,
encoding: 'base32',
token,
window: 2 // Allow ±2 time steps (60 seconds tolerance)
});
}
```
### Rate Limiting
**Redis-based rate limiting** (ADR-005)
```typescript
// apps/backend/src/middleware/rateLimit.ts
const RATE_LIMITS = {
// Auth endpoints (aggressive)
login: { max: 5, window: 60 * 15 }, // 5 attempts per 15 min
register: { max: 3, window: 60 * 60 }, // 3 per hour
resetPassword: { max: 3, window: 60 * 60 },
// API endpoints (generous)
api: { max: 100, window: 60 }, // 100 per minute
apiPremium: { max: 500, window: 60 }, // Premium users
// ML predictions (moderate)
mlPredictions: { max: 20, window: 60 }, // 20 per minute
};
async function rateLimit(
key: string,
config: RateLimitConfig
): Promise<boolean> {
const current = await redis.incr(`ratelimit:${key}`);
if (current === 1) {
await redis.expire(`ratelimit:${key}`, config.window);
}
return current <= config.max;
}
```
### CORS Configuration
**Strict CORS** for API
```typescript
// apps/backend/src/config/cors.ts
import cors from 'cors';
const ALLOWED_ORIGINS = process.env.NODE_ENV === 'production'
? ['https://app.trading.com', 'https://trading.com']
: ['http://localhost:5173', 'http://localhost:3000'];
export const corsOptions: cors.CorsOptions = {
origin: (origin, callback) => {
if (!origin || ALLOWED_ORIGINS.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true, // Allow cookies
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 86400, // 24 hours
};
```
### SQL Injection Prevention
**Parameterized Queries** (Prisma ORM)
```typescript
// ✅ SAFE - Prisma uses parameterized queries
const user = await db.user.findUnique({
where: { email: userInput }
});
// ❌ NEVER DO THIS
const users = await db.$queryRaw`
SELECT * FROM users WHERE email = ${userInput}
`; // Vulnerable to SQL injection
```
### XSS Prevention
```typescript
// apps/backend/src/middleware/security.ts
import helmet from 'helmet';
import { sanitize } from 'dompurify';
// Helmet middleware (sets security headers)
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"], // Avoid 'unsafe-inline' in prod
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.trading.com"],
},
},
hsts: {
maxAge: 31536000, // 1 year
includeSubDomains: true,
preload: true,
},
}));
// Sanitize user input
function sanitizeInput(input: string): string {
return sanitize(input, {
ALLOWED_TAGS: [], // Strip all HTML
ALLOWED_ATTR: [],
});
}
```
### CSRF Protection
**Double Submit Cookie Pattern**
```typescript
// apps/backend/src/middleware/csrf.ts
import { randomBytes } from 'crypto';
function generateCSRFToken(): string {
return randomBytes(32).toString('hex');
}
function csrfProtection(req: Request, res: Response, next: NextFunction) {
// Skip for GET, HEAD, OPTIONS
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
return next();
}
const tokenFromHeader = req.headers['x-csrf-token'];
const tokenFromCookie = req.cookies['csrf-token'];
if (!tokenFromHeader || tokenFromHeader !== tokenFromCookie) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
next();
}
```
### Encryption
**AES-256-GCM** for sensitive data at rest
```typescript
// apps/backend/src/utils/crypto.ts
import crypto from 'crypto';
const ALGORITHM = 'aes-256-gcm';
const KEY = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex'); // 32 bytes
export function encrypt(plaintext: string): string {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
// Return: iv:authTag:ciphertext
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
}
export function decrypt(ciphertext: string): string {
const [ivHex, authTagHex, encrypted] = ciphertext.split(':');
const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');
const decipher = crypto.createDecipheriv(ALGORITHM, KEY, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
```
### Secure Headers
```typescript
// Set via Helmet
app.use(helmet({
frameguard: { action: 'deny' }, // X-Frame-Options: DENY
contentSecurityPolicy: true, // CSP
hsts: { maxAge: 31536000 }, // Strict-Transport-Security
noSniff: true, // X-Content-Type-Options: nosniff
xssFilter: true, // X-XSS-Protection
referrerPolicy: { policy: 'strict-origin' }, // Referrer-Policy
}));
```
### Audit Logging
```typescript
// apps/backend/src/middleware/audit.ts
import { logger } from './logger';
const AUDITABLE_ACTIONS = [
'LOGIN', 'LOGOUT', 'REGISTER',
'PASSWORD_CHANGE', 'MFA_ENABLE', 'MFA_DISABLE',
'TRADE_EXECUTED', 'FUND_TRANSFER', 'API_KEY_CREATED',
'SUBSCRIPTION_CHANGED', 'PAYMENT_PROCESSED',
];
function auditLog(action: string, userId: string, metadata: object) {
if (AUDITABLE_ACTIONS.includes(action)) {
logger.info('audit_log', {
action,
userId,
timestamp: new Date().toISOString(),
ip: metadata.ip,
userAgent: metadata.userAgent,
metadata,
});
}
}
// Example usage
auditLog('TRADE_EXECUTED', user.id, {
symbol: 'AAPL',
quantity: 10,
price: 150.25,
ip: req.ip,
userAgent: req.headers['user-agent'],
});
```
---
## Consecuencias
### Positivas
1. **Zero Trust**: JWT stateless, cada request validado
2. **MFA Protection**: TOTP previene account takeover incluso con password leak
3. **Rate Limiting**: Brute force prácticamente imposible (5 attempts / 15 min)
4. **Defense in Depth**: Múltiples capas (bcrypt, JWT, MFA, rate limit, CORS)
5. **Audit Trail**: Compliance-ready logs de acciones críticas
6. **Encryption**: Datos sensibles encriptados at rest (AES-256-GCM)
7. **Secure Headers**: Helmet previene XSS, clickjacking, MIME sniffing
### Negativas
1. **UX Friction**: MFA agrega paso extra en login
2. **Complexity**: Refresh token rotation es complejo de implementar
3. **Performance**: bcrypt (250ms) y MFA verificación agregan latencia
4. **Token Management**: JWT blacklist requiere Redis storage
5. **Mobile Challenges**: httpOnly cookies no funcionan bien en mobile apps
### Riesgos y Mitigaciones
| Riesgo | Mitigación |
|--------|-----------|
| JWT secret leak | Rotate secrets monthly, use strong random (32+ bytes) |
| Brute force MFA | Rate limit: 5 attempts → lock account 1 hour |
| Session fixation | Regenerate session ID on login |
| MITM attacks | Enforce HTTPS only, HSTS header |
| Replay attacks | Short JWT TTL (1 hour), nonce for critical actions |
| Account enumeration | Generic error messages ("Invalid credentials") |
---
## Alternativas Consideradas
### 1. Session-Based Auth (Cookies)
- **Pros**: Simple, server-side revocation fácil
- **Contras**: Stateful, no funciona bien con mobile apps
- **Decisión**: ❌ Descartada - JWT es más flexible para multi-platform
### 2. OAuth 2.0 + Third-Party (Google, Facebook)
- **Pros**: No password management, MFA delegado
- **Contras**: Vendor dependency, no apropiado para finanzas
- **Decisión**: ⚠️ Complementario - Ofrecer como opción adicional
### 3. Argon2 para Password Hashing
- **Pros**: Más seguro que bcrypt, resistente a GPU attacks
- **Contras**: Menos maduro en Node.js ecosystem
- **Decisión**: ❌ Descartada - bcrypt es suficientemente seguro
### 4. SMS-based MFA
- **Pros**: User-friendly, no app required
- **Contras**: SIM swapping attacks, costo por SMS
- **Decisión**: ❌ Descartada - TOTP es más seguro y gratis
### 5. WebAuthn / FIDO2
- **Pros**: Phishing-resistant, passwordless future
- **Contras**: Browser support limitado, complejo de implementar
- **Decisión**: ❌ Pospuesta - Evaluar post-MVP
### 6. No Rate Limiting
- **Pros**: Menos complejidad
- **Contras**: Vulnerable a brute force y DDoS
- **Decisión**: ❌ Descartada - Rate limiting es crítico
---
## Security Checklist
### Development
- [ ] No hardcoded secrets (use .env)
- [ ] All inputs sanitized
- [ ] SQL injection tests
- [ ] XSS tests
- [ ] CSRF protection enabled
- [ ] Rate limiting configured
- [ ] Error messages don't leak info
### Deployment
- [ ] HTTPS enforced (no HTTP)
- [ ] Secrets in environment variables
- [ ] Database credentials rotated
- [ ] JWT secret rotated monthly
- [ ] Helmet middleware enabled
- [ ] CORS configured strictly
- [ ] Security headers validated
### Monitoring
- [ ] Failed login attempts tracked
- [ ] Rate limit violations alerted
- [ ] Suspicious activity flagged
- [ ] Audit logs reviewed weekly
---
## Compliance Roadmap
### GDPR (EU)
- [ ] User consent tracking
- [ ] Right to deletion (account deletion)
- [ ] Data export functionality
- [ ] Privacy policy
- [ ] Cookie consent banner
### PCI-DSS (Payments)
- [ ] Stripe handles card data (we never store cards)
- [ ] Encryption at rest and in transit
- [ ] Access controls
- [ ] Audit logging
---
## Referencias
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [JWT Best Practices](https://datatracker.ietf.org/doc/html/rfc8725)
- [bcrypt Security](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)
- [Helmet.js Documentation](https://helmetjs.github.io/)
- [TOTP RFC 6238](https://datatracker.ietf.org/doc/html/rfc6238)