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

13 KiB

id title type project version updated_date
ADR-007-security Seguridad y Autenticación Documentation trading-platform 1.0.0 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)

{
  "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

// 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

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

// 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)

// 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

// 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)

// ✅ 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

// 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

// 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

// 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

// 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

// 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