Skip to content

Latest commit

 

History

History
319 lines (244 loc) · 17.7 KB

File metadata and controls

319 lines (244 loc) · 17.7 KB

Evolith Tracker — Especificación de Seguridad

Navegación bilingüe: English · Español (este documento)

Estado del Documento: Borrador Tipo: Especificación de Seguridad Satélite: Evolith Tracker Upstream: Evolith Core Fecha: 2026-06-07 Autor: Architect Agent (enfoque seguridad) + Governance Auditor (BMAD) Resuelve: GAP-006 / REM-005


1. Propósito y Alcance

Este documento define la arquitectura y controles de seguridad de Evolith Tracker. Es la referencia autoritativa de autenticación, autorización, aislamiento de tenants, protección de datos, secretos, validación de entrada, modelado de amenazas y testing de seguridad.

Deriva sus mandatos de la gobernanza y el diseño existentes:

Fuente Mandato
Global Rules R-08 "Los diseños de autenticación deben mostrar explícitamente ambos flujos: IDP e Interno"
Global Rules R-15 Multi-tenancy: aislamiento de capa de aplicación primario, nativo de BD (RLS) como failsafe secundario
Tech Standards Auth de doble vía (IDP Externo + Credenciales Internas); RLS para aislamiento de tenants
TAD §16, §18, §25 Auth UMS, RLS, logging PII-safe, permission guard fail-closed
PRD BR-006 (aislamiento de tenant absoluto); STRIDE (EPIC-001, Could Have)

Las decisiones marcadas ⊕ PROPOSAL requieren aprobación humana (PO/Architect/Security) y NO están adoptadas aún. Son opciones surgidas de esta auditoría, no política.

Fuera de alcance: reportes de ejecución de pentests (operacional), certificación de cumplimiento (SOC2/ISO — futuro) y la seguridad interna del propio UMS (propiedad del producto UMS).


2. Visión General de la Arquitectura de Seguridad

Evolith Tracker delega la identidad por completo a UMS y aplica la autorización localmente como un motor de políticas fail-closed. El aislamiento de tenants es defensa en profundidad: scoping a nivel de aplicación (primario) respaldado por Row-Level Security de PostgreSQL (failsafe secundario).

flowchart LR
    subgraph Client
      U[Usuario / Agente BMAD]
    end
    subgraph Edge
      GW[API Gateway / BFF]
    end
    subgraph Tracker[Tracker Monolith]
      MW[TenantContext + Auth Middleware]
      G[TrackerPermissionGuard fail-closed]
      H[Command/Query Handlers]
    end
    subgraph External
      UMS[UMS SaaS - AuthN/AuthZ]
    end
    DB[(PostgreSQL + RLS)]

    U -->|Bearer JWT| GW
    GW -->|valida JWT vía JWKS| UMS
    GW --> MW
    MW --> G
    G -->|resuelve permisos efectivos| UMS
    G --> H
    H -->|con alcance de tenant, RLS aplicado| DB
Loading

Fronteras de confianza:

Frontera Control
Cliente → Edge TLS 1.2+; bearer JWT requerido
Edge → UMS Firma JWT verificada contra JWKS de UMS; caché corta de claves JWKS
Middleware → Guard Contexto de tenant establecido antes de cualquier handler
Guard → Handler Fail-closed: sin grant explícito ⇒ denegar
Handler → BD Consultas con alcance de tenant + failsafe RLS

3. Autenticación (AuthN)

Según R-08, ambas vías son explícitas:

Vía Flujo
IDP Externo (primario) UMS es el IdP. El usuario se autentica en UMS; UMS emite un JWT firmado. El Tracker nunca posee credenciales.
Interno / servicio Los agentes servicio-a-servicio y CLI/MCP presentan un token emitido por UMS (estilo client-credentials). El Tracker lo valida idénticamente — no hay un store de credenciales local separado.

3.1 Validación de Token

// Edge/middleware — valida el bearer token de cada request contra JWKS de UMS
interface ITokenValidator {
  validate(token: string): Promise<Result<TokenClaims, AuthError>>;
}

@Injectable()
export class UmsJwtValidator implements ITokenValidator {
  // Claves JWKS cacheadas con TTL corto; rotación honrada vía lookup de `kid` + refetch en cache miss
  async validate(token: string): Promise<Result<TokenClaims, AuthError>> {
    // 1. Parsear header, resolver `kid` → clave pública desde JWKS cacheado
    // 2. Verificar firma (RS256/ES256 — nunca `none`, nunca HS con secreto compartido)
    // 3. Verificar `iss` == issuer de UMS, `aud` incluye 'evolith-tracker', `exp`/`nbf`
    // 4. Devolver TokenClaims canónico { userId, tenantId, sessionId }
  }
}

Reglas AuthN:

# Regla
AN-1 Solo se aceptan firmas asimétricas (RS256/ES256); alg: none y HS256 se rechazan de plano
AN-2 iss, aud, exp, nbf siempre se verifican; tolerancia de skew de reloj ≤ 60s
AN-3 Claves JWKS cacheadas con TTL corto; un kid desconocido dispara un único refetch de JWKS (soporte de rotación de claves)
AN-4 El contenido del token no se confía para autorización más allá de la identidad — los permisos se resuelven frescos desde UMS (ver §4)
AN-5 Los tokens nunca se loguean, cachean en texto plano, ni persisten (ver §7, §8)

3.2 Ciclo de Vida del Token ⊕ PROPOSAL

Aspecto Política propuesta Owner de la decisión
TTL de access token ⊕ 15 minutos Security + alineación con UMS
Refresh ⊕ Manejado por UMS; el Tracker dispara re-auth en 401, nunca acuña tokens Architect
Revocación de sesión ⊕ Honrar introspección de token de UMS / TTL corto para que la revocación se propague en una ventana de TTL Security

Estos valores deben confirmarse contra el contrato real de token de UMS (brecha del TAD: "esquema de Authorization Graph de UMS no inspeccionado"). Hasta confirmar, son propuestas.


4. Autorización (AuthZ)

La autorización es fail-closed y se resuelve desde el grafo de autorización de UMS, mapeado a permisos canónicos del Tracker (TAD §18).

@Injectable()
export class TrackerPermissionGuard implements CanActivate {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const required = this.reflector.get<TrackerPermission>('requiredPermission', context.getHandler());
    if (!required) return true; // los endpoints sin @RequirePermission son públicos por diseño (debe ser explícito)

    const req = context.switchToHttp().getRequest();
    const permissions = await this.umsAdapter.getEffectivePermissions(req.user.id, {
      tenantId: req.tenantId,
      initiativeId: req.initiativeId,
    });

    return permissions.includes(required); // FAIL-CLOSED: ausencia de grant ⇒ false
  }
}

Reglas AuthZ:

# Regla
AZ-1 Fail-closed por defecto. Sin grant ⇒ denegar. No hay vía de "permitir ante error".
AZ-2 Un handler sin @RequirePermission debe ser explícitamente intencionado como público; la postura por defecto es "permiso requerido".
AZ-3 Los permisos tienen alcance (tenant + recurso). Un grant tracker:initiative:approve en el tenant A nunca aplica en el tenant B.
AZ-4 Los fallos de resolución del grafo de UMS resultan en denegar (no permitir); los errores se loguean y se exponen como 403, nunca como 500-con-acceso.
AZ-5 La autorización se aplica del lado del servidor. La UI permission-driven del frontend (TAD §22) es solo UX — nunca la frontera de seguridad.
AZ-6 Las superficies Web, CLI y MCP comparten el mismo guard y modelo de permisos (BR-008) — ninguna superficie tiene un bypass privilegiado.

4.1 Autorización MCP / Agentes

Según PRD §5.3, el Tracker es autoritativo sobre los agentes. Implicaciones de seguridad:

# Regla
MCP-1 Un agente no puede auto-asignarse trabajo, saltar una gate ni sobrescribir una restricción upstream vía MCP (principio MCP del PRD)
MCP-2 Los tokens de agente portan los mismos permisos con alcance que los humanos; BR-007 (agente = humano para evaluación de gate) no significa acceso elevado
MCP-3 Cada acción de agente se loguea al rastro de auditoría (BR-009) con atribución

5. Aislamiento de Tenants (Defensa en Profundidad)

Según R-15 y BR-006, el aislamiento se aplica en dos capas:

Capa Mecanismo Rol
Aplicación (primaria) TenantContextMiddleware deriva tenantId del token validado y da alcance a cada consulta Primera línea — cada consulta de repositorio se filtra por tenant
Base de datos (failsafe secundario) Política RLS de PostgreSQL sobre app.current_tenant_id Captura cualquier bug de scoping de la capa de aplicación; un WHERE tenant_id faltante no puede filtrar datos
ALTER TABLE tracker_discovery.initiatives ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON tracker_discovery.initiatives
  USING (tenant_id = current_setting('app.current_tenant_id')::UUID);

Reglas de aislamiento:

# Regla
TI-1 app.current_tenant_id se establece por request dentro de la transacción, desde el token — nunca desde un header/body provisto por el cliente
TI-2 RLS está habilitado en cada tabla con alcance de tenant; una tabla nueva sin RLS falla la revisión de seguridad
TI-3 El acceso entre tenants es imposible por diseño; no hay vía de consulta "admin ve todos los tenants" en Fase 1
TI-4 El aislamiento de tenant se prueba en la capa de integración (ver Test Strategy §4.3) — ambas capas aseguradas
TI-5 El rol de BD usado por la app es no-superusuario y no-BYPASSRLS, de modo que RLS no pueda saltarse silenciosamente

6. Modelo de Amenazas STRIDE

Amenaza Vector Mitigación Riesgo residual
S — Spoofing JWT falsificado/reenviado Verificación de firma asimétrica, checks iss/aud/exp, rotación JWKS (AN-1..3) Bajo
S — Spoofing Agente suplantando a otro agente/usuario vía MCP Tokens UMS con alcance; sin auto-asignación (MCP-1..2) Bajo
T — Tampering Modificar estado de initiative/contract fuera de banda Guards fail-closed; atomicidad Unit of Work; concurrencia optimista (xmin) Bajo
T — Tampering Falsificación de payload de webhook (ACL GitHub/Jira) ⊕ Verificar firmas de webhook (HMAC) en la frontera de la ACL Medio hasta adoptar ⊕
R — Repudiation Agente/humano niega una acción Rastro de auditoría append-only (tracker_audit), con timestamp + atribución (BR-009) Bajo
I — Information Disclosure Fuga de datos entre tenants Aislamiento de dos capas: scoping de app + failsafe RLS (§5) Bajo
I — Information Disclosure PII/secretos en logs Logging estructurado PII-safe; nunca loguear tokens/email/PII (§7) Bajo
D — Denial of Service Inundación de requests, consultas costosas ⊕ Rate limiting + timeouts de consulta (§9) Medio hasta adoptar ⊕
E — Elevation of Privilege Acceder a operación de mayor alcance Fail-closed, permisos con alcance (AZ-1..6); rol de BD no-superusuario (TI-5) Bajo
E — Elevation of Privilege Bypass del frontend de acciones ocultas La aplicación del lado del servidor es la frontera; el gating de UI es solo UX (AZ-5) Bajo

7. Protección de Datos y Logging

Según TAD §25, el logging es PII-safe por construcción.

# Regla
DP-1 Nunca loguear: tokens, contraseñas, email, ni PII alguna. Usar solo userId interno (UUID).
DP-2 Los logs son estructurados (JSON) con correlationId, tenantId, userId, action, durationMs
DP-3 Datos en tránsito: TLS 1.2+ en todas partes (cliente→edge, edge→UMS, app→BD)
DP-4 Datos en reposo: ⊕ PROPOSAL — cifrado de volumen PostgreSQL + backups cifrados (gestionado por el proveedor cloud)
DP-5 tracker_audit es append-only; sin grants de UPDATE/DELETE en tablas de auditoría
DP-6 Las respuestas de error nunca filtran stack traces ni identificadores internos a clientes (problem+json genérico)

8. Gestión de Secretos ⊕ PROPOSAL

Aspecto Enfoque propuesto Owner de la decisión
Almacenamiento de secretos ⊕ Secret manager cloud (AWS Secrets Manager / Azure Key Vault) — no archivos .env commiteados en ningún entorno no local DevOps + Security
Dev local .env (gitignored) aceptable solo para local
Inyección Secretos inyectados como env vars en runtime desde el secret manager; nunca horneados en imágenes DevOps
Rotación ⊕ Rotación periódica de credenciales de BD y tokens de servicio Security
Alcance Rol de app de BD de mínimo privilegio, no-superusuario, no-BYPASSRLS (TI-5) Architect

Son propuestas coherentes con el uso de env vars del TAD y las secciones Docker/Helm; la elección concreta del secret manager es una decisión de DevOps (relacionada con GAP-007 CI/CD spec).


9. Validación de Entrada, Rate Limiting y Hardening

Control Especificación
Validación de entrada class-validator (DTOs) + zod (fronteras) — apropiado por capa según TAD §10; rechazar campos desconocidos
Defensa de inyección Solo consultas parametrizadas de TypeORM; sin SQL por concatenación de strings; entradas JSONB validadas antes de persistir
Rate limiting ⊕ ⊕ PROPOSAL — límites por tenant + por IP en el gateway/BFF; límites más estrictos en endpoints de auth y mutación
Timeouts de consulta ⊕ ⊕ PROPOSAL — statement timeout en sesiones de BD para acotar consultas costosas (mitigación DoS)
Security headers ⊕ PROPOSAL — HSTS, X-Content-Type-Options, X-Frame-Options/CSP en el edge
CORS Allowlist de orígenes de frontend conocidos (Shell Host + remotes); sin wildcard en producción
Mass assignment Los DTOs hacen whitelist de campos permitidos; los agregados se construyen vía factories, no por binding directo del body

10. Cobertura OWASP Top 10 (2021)

OWASP Cobertura en Tracker
A01 Broken Access Control Guards fail-closed, permisos con alcance, aplicación del lado del servidor (§4)
A02 Cryptographic Failures JWT asimétrico, TLS 1.2+, ⊕ cifrado en reposo (§3, §7)
A03 Injection TypeORM parametrizado, class-validator/zod (§9)
A04 Insecure Design Aislamiento hexagonal, default fail-closed, modelo de amenazas (§6)
A05 Security Misconfiguration Rol de BD no-superusuario, RLS obligatorio, ⊕ security headers (§5, §9)
A06 Vulnerable Components ⊕ PROPOSAL — escaneo de dependencias (SCA) en CI (relacionado con GAP-007)
A07 Auth Failures IdP UMS, validación fuerte de token, ⊕ TTL corto + revocación (§3)
A08 Data Integrity Failures Atomicidad Unit of Work, concurrencia optimista, ⊕ verificación de firma de webhook (§6)
A09 Logging/Monitoring Failures Logs estructurados PII-safe, auditoría append-only, trazas OpenTelemetry (§7)
A10 SSRF ⊕ PROPOSAL — allowlist de egress para adapters ACL (GitHub/Jira/Core/UMS)

11. Testing de Seguridad

Según la Test Strategy:

Test Capa Asegura
Autorización fail-closed Presentation e2e Permiso faltante ⇒ 403 (§4)
Permiso con alcance Integración Grant en tenant A denegado en tenant B (AZ-3)
Aislamiento de tenant (RLS) Integración Tenant B no puede leer filas del tenant A (TI-4)
Rechazo de token Unit/integración alg: none, expirado, aud incorrecto todos rechazados (AN-1..2)
Logging PII-safe Unit El logger redacta/omite token, email, PII (DP-1)
Rastro de auditoría Integración Cada acción mutante añade un registro de auditoría con atribución (DP-5, BR-009)

⊕ PROPOSAL — añadir escaneo SAST + dependencias (SCA) al CI (GAP-007 CI/CD spec).


12. Decisiones de Seguridad Abiertas (Requieren Aprobación Humana)

ID Decisión Owner Bloquea
SEC-D1 Valores de TTL / refresh / revocación de token Security + UMS Confirmar contra contrato de UMS
SEC-D2 Elección de secret manager (AWS/Azure/otro) DevOps + Security Deploy a producción
SEC-D3 Umbrales de rate-limiting (por tenant/IP) Architect + Security Hardening de producción
SEC-D4 Verificación de firma de webhook (HMAC) para ACLs Architect Antes de habilitar ingesta de GitHub/Jira
SEC-D5 Enfoque de cifrado en reposo + backups DevOps Deploy a producción
SEC-D6 Política de security headers / CSP Frontend + Security Deploy a producción
SEC-D7 Allowlist de egress (SSRF) + SCA/SAST en CI DevOps + Security CI/CD spec (GAP-007)

Según las reglas de auditoría, estas se registran como propuestas — no decisiones aprobadas. Deben ser ratificadas por los owners correspondientes y, donde sean arquitectónicas, registradas en DECISIONS.md.


Referencias