✨ Características: - 45 ejercicios universitarios (Basic → Advanced) - Renderizado LaTeX profesional - IA generativa (Z.ai/DashScope) - Docker 9 servicios - Tests 123/123 pasando - Seguridad enterprise (JWT, XSS, Rate limiting) 🐳 Infraestructura: - Next.js 14 + Node.js 20 - PostgreSQL 15 + Redis 7 - Docker Compose completo - Nginx + SSL ready 📚 Documentación: - 5 informes técnicos completos - README profesional - Scripts de deployment automatizados Estado: Producción lista ✅
5.2 KiB
5.2 KiB
Documentación de Cambio de Seguridad - Token Blacklist
Resumen
Se corrigió un FAIL-OPEN CRÍTICO en la verificación de token blacklist que permitía el bypass de autenticación cuando Redis estaba indisponible.
Fecha del Cambio
2024-03-30
Archivo Modificado
backend/src/shared/database/redis.client.ts
Problema Original (Líneas 145-157)
} catch (error) {
logger.error({ error }, 'Redis unavailable - cannot check token blacklist');
// Return false to allow request to proceed (fail-open for availability)
// Note: This is a security trade-off. If strict security is required,
// change to return true (fail-closed)
return false; // <-- BYPASS DE SEGURIDAD CRÍTICO
}
Riesgo: Cuando Redis fallaba, cualquier token (incluso los blacklisteados) se consideraba válido, permitiendo autenticación no autorizada.
Solución Implementada
1. Comportamiento FAIL-CLOSED (Seguridad Máxima)
} catch (error) {
logger.error({
error: (error as Error).message,
consecutiveFailures,
tokenPrefix: token.substring(0, 10),
timestamp: new Date().toISOString(),
service: 'token-blacklist',
circuitBreakerOpen: false
}, 'Redis unavailable - SECURITY: blocking token');
throw new AuthenticationError('Unable to verify token status. Service temporarily unavailable.');
}
2. Circuit Breaker con Fallback
- Umbral: 5 fallos consecutivos activan el circuit breaker
- Fallback: Cache en memoria (TTL 1 minuto) para operaciones de blacklist
- Hashing: SHA256 para almacenamiento seguro (nunca guarda tokens completos)
3. Retry con Backoff Exponencial
- Intentos: 3 reintentos máximo
- Delay: 100ms × intento (100ms, 200ms, 300ms)
4. Métricas de Monitoreo
const metrics = {
redisBlacklistFailures: 0, // Fallos totales
redisBlacklistConsecutiveFailures: 0, // Fallos consecutivos actuales
redisBlacklistSuccesses: 0, // Éxitos totales
circuitBreakerOpens: 0 // Veces que se abrió el circuit breaker
};
5. Logging Estructurado
- Nunca se loguean tokens completos (solo prefijo de 10 caracteres)
- Timestamp ISO8601
- Identificación de servicio
- Contadores de fallos consecutivos
Cambios de Comportamiento
| Escenario | Antes (FAIL-OPEN) | Después (FAIL-CLOSED) |
|---|---|---|
| Redis indisponible | Token permitido ❌ | Error 401 lanzado ✅ |
| Error temporal | Token permitido ❌ | Retry con backoff ✅ |
| 5+ fallos consecutivos | Token permitido ❌ | Circuit breaker activado ✅ |
| Verificación exitosa | Token rechazado/aceptado | Sin cambios ✅ |
Impacto en Disponibilidad
⚠️ IMPORTANTE: Este cambio puede causar downtime del servicio de autenticación si Redis falla.
Recomendaciones para Alta Disponibilidad
- Implementar Redis Replica: Configurar Redis Cluster o Sentinel
- Monitoreo: Alertas inmediatas cuando
metrics.redisBlacklistFailures > 0 - Health Checks: Verificar estado de Redis antes de despliegues
- Graceful Degradation: Cache en memoria como último recurso
API Changes
Nuevas Funciones Exportadas
// Obtener métricas actuales
export function getBlacklistMetrics(): {
redisBlacklistFailures: number;
redisBlacklistConsecutiveFailures: number;
redisBlacklistSuccesses: number;
circuitBreakerOpens: number;
}
// Resetear métricas (útil para testing)
export function resetBlacklistMetrics(): void
Error Handling
class AuthenticationError extends Error {
message: 'Unable to verify token status. Service temporarily unavailable.'
}
Tests Unitarios
Archivo: backend/tests/redis.client.test.ts
14 tests implementados:
- ✅ Verificar token blacklisteado
- ✅ Verificar token no blacklisteado
- ✅ FAIL-CLOSED cuando Redis falla
- ✅ FAIL-CLOSED con múltiples fallos
- ✅ Retry con backoff exponencial
- ✅ Métricas de éxito
- ✅ Blacklist exitoso en Redis
- ✅ Fallback a cache en memoria
- ✅ Circuit breaker se abre después de 5 fallos
- ✅ Reset de contador tras éxito
- ✅ Nunca permite acceso con Redis caído
- ✅ No loguea tokens completos
- ✅ Tracking de métricas
- ✅ Reset de métricas
Comandos de Verificación
# TypeScript
npm run type-check
# Tests
npm test -- tests/redis.client.test.ts
# Build
npm run build
Lista de Verificación Pre-Deploy
- Redis Cluster configurado en producción
- Alertas de métricas configuradas
- Documentación de rollback lista
- Comunicación al equipo sobre cambio de comportamiento
- Monitoreo de errores 401 post-deploy
- Plan de contingencia si hay degradación de servicio
Rollback
Si es necesario revertir (aunque NO recomendado por riesgo de seguridad):
git revert <commit-hash>
# O manualmente: restaurar return false en catch block
Referencias
- Issue: Token blacklist bypass (fixear.md Issue #2)
- Archivo:
backend/src/shared/database/redis.client.ts:145-157 - PR: N/A (cambio directo)
Nota de Seguridad: Este cambio es CRÍTICO y bloquea un vector de ataque de autenticación. No debe revertirse sin considerar las implicaciones de seguridad.