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
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
⊕ PROPOSALrequieren 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).
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
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 |
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. |
// 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) |
| 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.
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. |
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 |
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 |
| 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 |
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) |
| 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).
| 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 |
| 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) |
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).
| 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.
- Tracker Target Architecture (TAD) — §16 RLS, §18 auth UMS, §25 logging
- UMS Authentication Integration
- UMS Authorization Graph Design
- PostgreSQL Data Design — RLS, schema de auditoría
- Test Strategy — cobertura de testing de seguridad
- Global Rules — R-08, R-15
- OWASP Top 10 (2021)