Files
mercadodevida/work/artifacts/F-005/security.json
rikrdo 75293f39bc feat(identity): F-005 register/login/logout with argon2 sessions and rate limiting
- Hexagonal identity module: domain ports, use cases, argon2id hasher, pg repos
- Migration 002_identity: identity_users + identity_sessions (token hash only)
- Opaque 512-bit session tokens; DB stores SHA-256 hash; 7-day TTL in SQL
- Cookie HttpOnly + Secure (COOKIE_SECURE, default true) + SameSite=Lax
- LoginRateLimiter: 10 failures -> 429 + Retry-After, 15-min cooldown
- Anti-enumeration: identical generic 401 + dummy-hash timing equalization
- buildApp gains optional pool/cookieSecure; foundation-only app preserved
- 47 unit + 14 integration tests; live smoke covers all acceptance criteria
2026-08-14 22:58:32 +02:00

23 lines
2.0 KiB
JSON

{
"feature_id": "F-005",
"agent": "security",
"verdict": "APPROVED",
"summary": "El servidor es la única autoridad de identidad. Credenciales solo como argon2id PHC, sesiones opacas con hash persistido, cookies HttpOnly/Secure/SameSite, anti-enumeración con timing igualado y 401 genérico, brute force contenido con 429 + Retry-After. Sin hallazgos bloqueantes.",
"checks": {
"credential_storage": "PASS: argon2id m=19456KiB t=2 p=1 (OWASP 2024); integration test prueba que el plaintext no aparece en ningún valor de la fila",
"user_enumeration": "PASS: 401 genérico idéntico para email inexistente y password incorrecto (assert + curl live); dummy-hash verify memoizado iguala timing",
"sql_injection": "PASS: todas las queries parametrizadas; email validado por zod + citext UNIQUE en DB (race-safe)",
"session_token": "PASS: 512 bits crypto.randomBytes base64url; solo SHA-256 hex persiste (token_hash UNIQUE); fuga de DB no entrega sesiones utilizables",
"cookie_flags": "PASS: HttpOnly + Secure + SameSite=Lax + Path=/ + Max-Age=604800 verificados en vivo; COOKIE_SECURE default true",
"revocation": "PASS: logout revoca server-side (WHERE revoked_at IS NULL AND expires_at > now()); expiración en SQL, no en memoria",
"brute_force": "PASS: 10 fallos por email -> 429 + Retry-After: 900 (verificado en vivo); rate check corre ANTES del lookup en DB",
"input_abuse": "PASS: password max 128 chars limita costo argon2 por request; validación zod antes del dominio",
"secrets": "PASS: sin secretos hardcodeados; .env gitignored; errores de config nombran variables, nunca valores"
},
"residual_risks_accepted": [
"Sesiones concurrentes ilimitadas por usuario (sin cap). Aceptable en este slice.",
"Rate limiter in-memory: se pierde con restart y es por instancia. Documentado; interfaz lista para Redis.",
"Lockout temporal: un atacante puede bloquear un email 15 min (trade-off estándar); cooldown acotado lo contiene."
]
}