167 KiB
🛡️ Aegis — Plan de Tareas Completo (V2 + V3)
Plataforma Avanzada de Gestión y Validación de Cobertura MITRE ATT&CK
Instrucciones de uso: Cada tarea (T-XXX) es una unidad de trabajo independiente que debe resultar en un commit. Están ordenadas secuencialmente — cada tarea puede depender de las anteriores pero nunca de las posteriores. Cada tarea incluye una sección de validación: no hagas commit hasta que todos los checks pasen.
Contexto: Este plan se ejecuta DESPUÉS de completar el MVP de Aegis (T-001 a T-036). Se divide en dos grandes bloques:
- V2 (T-100 a T-134): Flujo Red Team / Blue Team con validación dual, templates y notificaciones
- V3 (T-200 a T-237): Plataforma enterprise con múltiples fuentes, threat actors, scoring, compliance, campañas y heatmap avanzado
PARTE 1 — AEGIS V2: Sistema de Tests de Validación Red Team / Blue Team
Visión General del Flujo de Validación
┌─────────────────────────────────────────────────────────────────────────┐
│ CICLO DE VIDA DE UN TEST │
│ │
│ ┌──────┐ ┌──────────────┐ ┌─────────────────┐ ┌───────────┐ │
│ │ DRAFT│───▶│RED_EXECUTING │───▶│ BLUE_EVALUATING │───▶│ IN_REVIEW │ │
│ └──────┘ └──────────────┘ └─────────────────┘ └───────────┘ │
│ │ │
│ ┌────────────────────┤ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ REJECTED │ │VALIDATED │ │
│ └──────────┘ └──────────┘ │
│ │ │
│ └──────▶ Vuelve a DRAFT │
└─────────────────────────────────────────────────────────────────────────┘
Estados del Test:
- draft: Creado, pendiente de ejecución por Red Team
- red_executing: Red Team documenta ataque y sube evidencias
- blue_evaluating: Blue Team documenta detección y sube evidencias
- in_review: Ambos managers revisan evidencias
- validated: Aprobado por ambos managers
- rejected: Rechazado — vuelve a draft para rehacer
Estados editables por equipo:
- Red Team puede editar en: draft, red_executing
- Blue Team puede editar en: blue_evaluating
- Evidencias Red se pueden subir/borrar en: draft, red_executing
- Evidencias Blue se pueden subir/borrar en: blue_evaluating
- Managers validan/rechazan en: in_review
Roles involucrados:
- red_tech: Crea tests, documenta ataques, sube evidencias de ataque
- blue_tech: Documenta detección, sube evidencias de detección
- red_lead: Valida/rechaza la parte de Red Team
- blue_lead: Valida/rechaza la parte de Blue Team
- admin: Acceso total
Catálogo de Tests Básicos por TTP
Los tests básicos se obtienen de varias fuentes:
- Atomic Red Team (Red Canary): repositorio open-source con tests atómicos mapeados a MITRE ATT&CK
- MITRE ATT&CK procedures: procedimientos documentados en la propia base de datos de MITRE
- Tests personalizados: creados manualmente por los equipos según su entorno
FASE 10 — Evolución del Modelo de Datos para Red/Blue Team
T-100: Ampliar estados del Test (TestState)
Objetivo: Añadir los nuevos estados al ciclo de vida del test que permitan diferenciar las fases de Red Team ejecutando, Blue Team evaluando, y revisión por managers.
Archivos a modificar:
backend/app/models/enums.py
Cambios en TestState:
class TestState(str, enum.Enum):
draft = "draft"
red_executing = "red_executing" # NUEVO: Red Team documentando ataque
blue_evaluating = "blue_evaluating" # NUEVO: Blue Team evaluando detección
in_review = "in_review"
validated = "validated"
rejected = "rejected"
Generar migración Alembic para actualizar el enum en PostgreSQL.
⚠️ IMPORTANTE — Migración de enums en PostgreSQL:
PostgreSQL no permite modificar enums con un simple ALTER TABLE. Alembic no maneja esto bien automáticamente. La migración generada por --autogenerate probablemente NO funcione. Hay que escribir la migración manualmente usando:
from alembic import op
def upgrade():
# PostgreSQL requiere ALTER TYPE para añadir valores a un enum existente
op.execute("ALTER TYPE teststate ADD VALUE IF NOT EXISTS 'red_executing' AFTER 'draft'")
op.execute("ALTER TYPE teststate ADD VALUE IF NOT EXISTS 'blue_evaluating' AFTER 'red_executing'")
def downgrade():
# Downgrade de enums en PostgreSQL es complejo — requiere recrear el tipo
# Solo implementar si es estrictamente necesario
pass
No usar --autogenerate para esta migración. Crearla manualmente con alembic revision -m "add_new_test_states".
Validación:
alembic upgrade headaplica la migración sin errores- Los tests existentes con estados antiguos siguen funcionando
- Se pueden crear tests con los nuevos estados vía SQL directo:
INSERT INTO tests (..., state) VALUES (..., 'red_executing') SELECT enum_range(NULL::teststate)en psql muestra todos los valores incluyendo los nuevos
T-101: Modelo EvidenceTeam — separar evidencias Red/Blue
Objetivo: Añadir un campo team a las evidencias para distinguir si pertenecen al Red Team (evidencia de ataque) o al Blue Team (evidencia de detección).
Archivos a modificar:
backend/app/models/enums.py— añadir enumTeamSidebackend/app/models/evidence.py— añadir camposteamynotes
Nuevo enum:
class TeamSide(str, enum.Enum):
red = "red"
blue = "blue"
Nuevos campos en Evidence:
| Campo | Tipo | Restricciones |
|---|---|---|
| team | Enum(TeamSide) | not null, default "red" |
| notes | Text | nullable (notas sobre la evidencia) |
⚠️ Migración: Usar la misma técnica de T-100 para crear el enum teamside manualmente en PostgreSQL, luego añadir la columna. El default red asegura que las evidencias existentes se asignen correctamente.
Generar migración.
Validación:
alembic upgrade headañade la columnateamynotesa la tablaevidences- Las evidencias existentes tienen
team = 'red'por defecto - Se puede insertar una evidencia con
team = 'blue' - La columna
notesacepta texto largo
T-102: Campos de validación dual en Test (red_lead + blue_lead)
Objetivo: Extender el modelo Test para soportar validación independiente por Red Lead y Blue Lead, de manera que un test solo pase a validated cuando ambos managers lo aprueban.
Archivos a modificar:
backend/app/models/test.py
Deprecar campos existentes del MVP:
Los campos validated_by y validated_at del MVP quedan obsoletos con la validación dual. La migración debe:
- Renombrar
validated_by→legacy_validated_byyvalidated_at→legacy_validated_at(preservar datos) - O bien eliminarlos directamente si no hay datos de producción relevantes
Si se decide eliminar, añadir en la migración:
op.drop_column('tests', 'validated_by')
op.drop_column('tests', 'validated_at')
Nuevos campos:
| Campo | Tipo | Restricciones |
|---|---|---|
| red_validated_by | UUID | FK → users.id, nullable |
| red_validated_at | DateTime | nullable |
| red_validation_status | String | nullable (pending/approved/rejected) |
| red_validation_notes | Text | nullable |
| blue_validated_by | UUID | FK → users.id, nullable |
| blue_validated_at | DateTime | nullable |
| blue_validation_status | String | nullable (pending/approved/rejected) |
| blue_validation_notes | Text | nullable |
| red_summary | Text | nullable (resumen del ataque por red) |
| blue_summary | Text | nullable (resumen de detección por blue) |
| detection_result | Enum(TestResult) | nullable (resultado de detección blue) |
| attack_success | Boolean | nullable (si el ataque tuvo éxito) |
Relaciones nuevas:
red_validator = relationship("User", foreign_keys=[red_validated_by])
blue_validator = relationship("User", foreign_keys=[blue_validated_by])
Generar migración.
Validación:
alembic upgrade headcrea las nuevas columnas y elimina/renombra las antiguas sin errores- Los tests existentes tienen los nuevos campos como
null - Se puede actualizar
red_validation_statusyblue_validation_statusindependientemente - Las FKs a
users.idfuncionan correctamente - No quedan referencias en el código a los campos
validated_by/validated_atantiguos
T-103: Modelo TestTemplate — catálogo de tests predefinidos
Objetivo: Crear un modelo para almacenar plantillas de tests predefinidos (basados en Atomic Red Team, MITRE procedures, etc.) que los usuarios pueden instanciar como tests reales.
Archivo a crear: backend/app/models/test_template.py
Campos:
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| mitre_technique_id | String | not null (ej: "T1059.001") |
| name | String | not null |
| description | Text | nullable |
| source | String | not null (ej: "atomic_red_team", "mitre", "custom") |
| source_url | String | nullable (URL al test original) |
| attack_procedure | Text | nullable (procedimiento de ataque sugerido) |
| expected_detection | Text | nullable (qué debería detectar blue team) |
| platform | String | nullable (windows, linux, macos) |
| tool_suggested | String | nullable (herramienta sugerida) |
| severity | String | nullable (low, medium, high, critical) |
| atomic_test_id | String | nullable (ID del test en Atomic Red Team) |
| is_active | Boolean | default True |
| created_at | DateTime | default utcnow |
Índices a crear:
Index('ix_test_templates_mitre_technique_id', TestTemplate.mitre_technique_id)
Index('ix_test_templates_source', TestTemplate.source)
Index('ix_test_templates_platform', TestTemplate.platform)
Index('ix_test_templates_severity', TestTemplate.severity)
Actualizar models/__init__.py para importar TestTemplate.
Generar migración.
Validación:
alembic upgrade headcrea la tablatest_templatescon los índices- Se puede insertar un template con todos los campos
- El campo
sourceacepta los valores esperados - La tabla soporta múltiples templates para la misma técnica MITRE
EXPLAINde una query filtrando pormitre_technique_idmuestra uso del índice
T-104: Schemas Pydantic para los nuevos modelos
Objetivo: Crear schemas de request/response para los modelos modificados y nuevos.
Archivos a crear/modificar:
backend/app/schemas/test.py— actualizar con nuevos camposbackend/app/schemas/evidence.py— añadirteamynotesbackend/app/schemas/test_template.py— nuevo
Schemas de Test actualizados:
TestOut: añadir campos de validación dual (red_validated_by,blue_validated_by,red_validation_status,blue_validation_status,red_summary,blue_summary, etc.). Eliminar referencias a los camposvalidated_by/validated_atantiguos.TestRedUpdate: name, description, procedure_text, tool_used, attack_success, red_summary (campos que rellena Red Team)TestBlueUpdate: detection_result, blue_summary (campos que rellena Blue Team)TestRedValidate: red_validation_status (approved/rejected), red_validation_notesTestBlueValidate: blue_validation_status (approved/rejected), blue_validation_notes
Schemas de Evidence actualizados:
EvidenceOut: añadirteamynotesEvidenceUpload: añadirteam(requerido) ynotes(opcional)
Schemas de TestTemplate:
TestTemplateOut: todos los camposTestTemplateCreate: para crear templates personalizadosTestTemplateSummary: id, mitre_technique_id, name, source, platform, severity (para listados)TestTemplateInstantiate: template_id, technique_id (para crear un test real desde un template)
Validación:
- Todos los schemas se importan sin errores
TestOutincluye los campos de validación dual y NO incluye los antiguosTestTemplateCreatevalida correctamente los campos requeridosEvidenceOutincluyeteamynotes
T-105: Índices de base de datos para V2
Objetivo: Crear índices para los campos que se usarán frecuentemente en filtros y consultas, evitando queries lentas a medida que crece el volumen de datos.
Archivo a crear: migración Alembic manual
Índices a crear:
# Tests
Index('ix_tests_state', Test.state)
Index('ix_tests_technique_id', Test.technique_id)
Index('ix_tests_created_by', Test.created_by)
Index('ix_tests_red_validation_status', Test.red_validation_status)
Index('ix_tests_blue_validation_status', Test.blue_validation_status)
# Evidences
Index('ix_evidences_test_id', Evidence.test_id)
Index('ix_evidences_team', Evidence.team)
# Techniques (si no existen ya del MVP)
Index('ix_techniques_tactic', Technique.tactic)
Index('ix_techniques_status_global', Technique.status_global)
Index('ix_techniques_review_required', Technique.review_required)
Validación:
alembic upgrade headcrea todos los índices\dien psql lista los nuevos índicesEXPLAIN ANALYZEde una query filtrando tests porstatemuestra Index Scan
FASE 11 — Lógica de Negocio del Flujo Red/Blue
T-106: Servicio de transiciones de estado del Test
Objetivo: Crear un servicio que controle las transiciones de estado válidas del test y garantice que solo se puedan hacer los cambios permitidos.
Archivo a crear: backend/app/services/test_workflow_service.py
Transiciones válidas:
VALID_TRANSITIONS = {
TestState.draft: [TestState.red_executing],
TestState.red_executing: [TestState.blue_evaluating],
TestState.blue_evaluating: [TestState.in_review],
TestState.in_review: [TestState.validated, TestState.rejected],
TestState.rejected: [TestState.draft],
TestState.validated: [], # estado final (o puede reabrirse)
}
Funciones a implementar:
can_transition(test: Test, target_state: TestState) -> booltransition_state(db, test, target_state, user) -> Test— valida transición, cambia estado, log de auditoríastart_execution(db, test, user) -> Test— mueve dedraftared_executingsubmit_red_evidence(db, test, user) -> Test— marca comoblue_evaluatingcuando Red Team terminasubmit_blue_evidence(db, test, user) -> Test— marca comoin_reviewcuando Blue Team terminavalidate_as_red_lead(db, test, user, status, notes) -> Test— valida parte Redvalidate_as_blue_lead(db, test, user, status, notes) -> Test— valida parte Bluecheck_dual_validation(db, test) -> Test— si ambos aprobaron, pasa a validated; si alguno rechazó, pasa a rejectedreopen_test(db, test, user) -> Test— mueve derejectedadraft, limpia campos de validación
Validación:
- Transición draft → red_executing funciona
- Transición draft → validated falla (no permitida)
- Transición red_executing → blue_evaluating funciona
check_dual_validationpasa a validated solo si ambos managers aprobaroncheck_dual_validationpasa a rejected si algún manager rechazóreopen_testlimpiared_validation_status,blue_validation_statusy campos asociados- Cada transición genera un log de auditoría
T-107: Actualizar servicio de recalculación de status
Objetivo: Mejorar status_service.py para tener en cuenta los nuevos estados y la validación dual.
Archivo a modificar: backend/app/services/status_service.py
Nueva lógica:
def recalculate_technique_status(db, technique):
tests = technique.tests
if not tests:
technique.status_global = TechniqueStatus.not_evaluated
elif all(t.state == TestState.validated for t in tests):
# Todos validados — revisar resultados de detección
results = [t.detection_result for t in tests if t.detection_result]
if all(r == "detected" for r in results):
technique.status_global = TechniqueStatus.validated
elif any(r == "partially_detected" for r in results):
technique.status_global = TechniqueStatus.partial
else:
technique.status_global = TechniqueStatus.not_covered
elif any(t.state == TestState.validated for t in tests):
technique.status_global = TechniqueStatus.partial
else:
technique.status_global = TechniqueStatus.in_progress
db.commit()
Validación:
- Sin tests →
not_evaluated - Todos validated con detection=detected →
validated - Algunos validated, otros en progreso →
partial - Todos en estados intermedios →
in_progress - Todos validated con detection=not_detected →
not_covered
T-108: Servicio de importación de Atomic Red Team
Objetivo: Crear un servicio que importe tests predefinidos desde el repositorio de Atomic Red Team de Red Canary y los almacene como TestTemplates.
Archivo a crear: backend/app/services/atomic_import_service.py
⚠️ Estrategia de descarga desde GitHub: La API de GitHub sin autenticación solo permite 60 requests/hora. El repositorio de Atomic Red Team tiene 1500+ archivos YAML, por lo que NO se puede hacer un request por archivo. La estrategia correcta es:
- Opción preferida: Descargar el ZIP del repositorio completo via
https://github.com/redcanaryco/atomic-red-team/archive/refs/heads/master.zip - Descomprimir en memoria o en directorio temporal
- Parsear todos los ficheros YAML de
atomics/T*/T*.yaml - Limpiar el directorio temporal al finalizar
Alternativamente, si se quiere usar la API de GitHub, configurar un token de acceso personal en settings (GITHUB_TOKEN) para tener 5000 requests/hora.
Lógica:
- Descargar ZIP del repositorio de Atomic Red Team
- Descomprimir y localizar ficheros YAML en
atomics/ - Para cada archivo YAML (organizados por técnica MITRE
atomics/T1059.001/T1059.001.yaml):- Parsear el YAML que contiene una lista de
atomic_tests - Cada test atómico tiene:
name,description,supported_platforms,executor(tipo y command)
- Parsear el YAML que contiene una lista de
- Por cada test atómico:
- Crear un
TestTemplateconsource = "atomic_red_team" - Setear
atomic_test_idcon el ID del test (formato{technique_id}-{index}) - Setear
platformdesdesupported_platforms - Setear
attack_proceduredesdeexecutor.command - Mapear a la técnica MITRE correspondiente
- Crear un
- No duplicar templates que ya existen (comparar por
atomic_test_id) - Log de auditoría con resumen
Validación:
- Ejecutar la importación crea TestTemplates en la BD
- Cada template tiene
source = "atomic_red_team"y datos válidos - Ejecutar dos veces no duplica templates
- Los templates se mapean correctamente a técnicas MITRE existentes
- Se importan al menos 500+ templates
- La descarga del ZIP funciona sin alcanzar rate limits
FASE 12 — Endpoints API Red/Blue
T-109: Endpoints actualizados de Tests con flujo Red/Blue
Objetivo: Modificar y añadir endpoints al router de tests para soportar el nuevo flujo de trabajo.
Archivo a modificar: backend/app/routers/tests.py
Endpoints nuevos/modificados:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| GET | /tests | autenticado | Listar tests con filtros (state, technique_id) |
| POST | /tests | red_tech, admin | Crear test (nuevo o desde template) |
| POST | /tests/from-template | red_tech, admin | Crear test instanciando un template |
| GET | /tests/{id} | autenticado | Detalle con evidencias separadas red/blue |
| PATCH | /tests/{id}/red | red_tech, admin | Red Team actualiza su parte |
| PATCH | /tests/{id}/blue | blue_tech, admin | Blue Team actualiza su parte |
| POST | /tests/{id}/start-execution | red_tech, admin | Mover de draft → red_executing |
| POST | /tests/{id}/submit-red | red_tech, admin | Red Team finaliza → pasa a blue_evaluating |
| POST | /tests/{id}/submit-blue | blue_tech, admin | Blue Team finaliza → pasa a in_review |
| POST | /tests/{id}/validate-red | red_lead, admin | Red Lead valida/rechaza parte red |
| POST | /tests/{id}/validate-blue | blue_lead, admin | Blue Lead valida/rechaza parte blue |
| POST | /tests/{id}/reopen | red_lead, blue_lead, admin | Reabrir test rechazado → draft |
| GET | /tests/{id}/timeline | autenticado | Timeline de cambios de estado del test |
Estados permitidos para edición:
PATCH /tests/{id}/red: permitido endraftyred_executing(Red Team prepara y ejecuta)PATCH /tests/{id}/blue: permitido solo enblue_evaluatingPOST /tests/{id}/start-execution: solo desdedraftPOST /tests/{id}/submit-red: solo desdered_executingPOST /tests/{id}/submit-blue: solo desdeblue_evaluating
Detalle del endpoint GET /tests/{id}:
La respuesta debe incluir:
{
"id": "...",
"name": "...",
"state": "blue_evaluating",
"red_evidences": [],
"blue_evidences": [],
"red_summary": "...",
"blue_summary": "...",
"attack_success": true,
"detection_result": "detected",
"red_validation_status": "approved",
"blue_validation_status": "pending",
"timeline": []
}
Validación:
POST /testscrea un test en estadodraftPOST /tests/from-templatecrea un test con datos pre-rellenados del templatePOST /tests/{id}/start-executionmueve de draft a red_executingPATCH /tests/{id}/redfunciona endraftyred_executingPATCH /tests/{id}/redfalla enblue_evaluating(400)PATCH /tests/{id}/bluesolo funciona enblue_evaluatingPOST /tests/{id}/submit-redcambia estado ablue_evaluatingPOST /tests/{id}/submit-bluecambia estado ain_reviewPOST /tests/{id}/validate-redsolo accesible por red_lead/adminPOST /tests/{id}/validate-bluesolo accesible por blue_lead/admin- Cuando ambos validan como approved → test pasa a
validated - Cuando alguno rechaza → test pasa a
rejected POST /tests/{id}/reopensolo funciona en testsrejectedGET /tests/{id}/timelineretorna el historial ordenado cronológicamente- Cada operación genera audit log
T-110: Endpoints de Evidence con separación Red/Blue
Objetivo: Modificar el router de evidencias para soportar la separación por equipo.
Archivo a modificar: backend/app/routers/evidence.py
Endpoints modificados:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| POST | /tests/{test_id}/evidence | autenticado | Subir evidencia indicando team (red/blue) |
| GET | /tests/{test_id}/evidence | autenticado | Listar evidencias del test, filtrable por team |
| GET | /evidence/{id} | autenticado | Obtener URL pre-firmada |
| DELETE | /evidence/{id} | creador o admin | Eliminar evidencia (solo en estados editables) |
Lógica de control explícita:
- Red Team (
red_tech) solo puede subir evidencias conteam=redcuando el test está endraftored_executing - Blue Team (
blue_tech) solo puede subir evidencias conteam=bluecuando el test está enblue_evaluating - Admin puede subir en cualquier momento con cualquier
team - DELETE de evidencias Red: permitido en
draftyred_executing - DELETE de evidencias Blue: permitido en
blue_evaluating - DELETE en estados
in_review,validated,rejected: NO permitido (403)
Validación:
- Un
red_techpuede subir evidencia conteam=reden estadored_executing - Un
red_techpuede subir evidencia conteam=reden estadodraft - Un
red_techNO puede subir evidencia conteam=blue(403) - Un
blue_techpuede subir evidencia conteam=blueen estadoblue_evaluating - Un
blue_techNO puede subir evidencia conteam=red(403) GET /tests/{id}/evidence?team=redfiltra correctamenteDELETE /evidence/{id}en estadoin_review→ 403DELETE /evidence/{id}en estadored_executingpara evidencia red → 200- Admin puede subir cualquier tipo de evidencia en cualquier momento
T-111: Endpoints CRUD de TestTemplates
Objetivo: Crear endpoints para gestionar el catálogo de templates de tests.
Archivo a crear: backend/app/routers/test_templates.py
Endpoints:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| GET | /test-templates | autenticado | Listar templates con filtros |
| GET | /test-templates/{id} | autenticado | Detalle de un template |
| POST | /test-templates | admin | Crear template personalizado |
| PATCH | /test-templates/{id} | admin | Actualizar template |
| DELETE | /test-templates/{id} | admin | Desactivar template (soft delete) |
| GET | /test-templates/by-technique/{mitre_id} | autenticado | Templates para una técnica MITRE específica |
Filtros del GET /test-templates:
source: atomic_red_team, mitre, customplatform: windows, linux, macosseverity: low, medium, high, criticalmitre_technique_id: filtrar por técnicasearch: búsqueda por nombre/descripción- Paginación:
offset+limit(default limit=50)
Validación:
GET /test-templatesretorna lista paginadaGET /test-templates?source=atomic_red_teamfiltra por fuenteGET /test-templates?platform=windowsfiltra por plataformaGET /test-templates/by-technique/T1059.001retorna templates para esa técnicaPOST /test-templatessolo accesible por adminDELETE /test-templates/{id}hace soft delete (is_active=False)- El filtro
searchbusca en name y description
T-112: Endpoint de importación de Atomic Red Team
Objetivo: Exponer la importación de Atomic Red Team como endpoint del sistema.
Archivo a modificar: backend/app/routers/system.py
Endpoint:
POST /api/v1/system/import-atomic-tests
Auth: admin only
Response: {"message": "Import completed", "imported": X, "skipped": Y, "errors": Z}
Validación:
POST /system/import-atomic-testsejecuta la importación y retorna estadísticas- Solo admin puede ejecutar
- Audit log registra la importación
- Ejecutar dos veces no duplica — incrementa
skipped
FASE 13 — Frontend: Tipos y API Clients
T-113: Actualizar tipos TypeScript
Objetivo: Actualizar los tipos del frontend para reflejar los cambios del backend.
Archivo a modificar: frontend/src/types/models.ts
Tipos a añadir/modificar:
// Actualizar TestState
export type TestState =
| "draft"
| "red_executing"
| "blue_evaluating"
| "in_review"
| "validated"
| "rejected";
// Nuevo tipo TeamSide
export type TeamSide = "red" | "blue";
// Estados editables por equipo
export const RED_EDITABLE_STATES: TestState[] = ["draft", "red_executing"];
export const BLUE_EDITABLE_STATES: TestState[] = ["blue_evaluating"];
// Actualizar Evidence
export interface Evidence {
id: string;
test_id: string;
file_name: string;
file_path: string;
sha256_hash: string;
uploaded_by: string | null;
uploaded_at: string;
team: TeamSide;
notes: string | null;
}
// Actualizar Test con campos duales
export interface Test {
id: string;
technique_id: string;
name: string;
description: string | null;
platform: string | null;
procedure_text: string | null;
tool_used: string | null;
state: TestState;
created_by: string | null;
created_at: string;
red_summary: string | null;
blue_summary: string | null;
attack_success: boolean | null;
detection_result: TestResult | null;
red_validation_status: ValidationStatus | null;
blue_validation_status: ValidationStatus | null;
red_validation_notes: string | null;
blue_validation_notes: string | null;
red_validated_by: string | null;
blue_validated_by: string | null;
red_evidences: Evidence[];
blue_evidences: Evidence[];
}
export type ValidationStatus = "pending" | "approved" | "rejected";
// Nuevo tipo TestTemplate
export interface TestTemplate {
id: string;
mitre_technique_id: string;
name: string;
description: string | null;
source: string;
source_url: string | null;
attack_procedure: string | null;
expected_detection: string | null;
platform: string | null;
tool_suggested: string | null;
severity: string | null;
atomic_test_id: string | null;
is_active: boolean;
created_at: string;
}
// Timeline
export interface TestTimelineEntry {
id: string;
action: string;
user: string;
timestamp: string;
details: Record<string, unknown>;
}
Validación:
- TypeScript compila sin errores
- Todos los tipos nuevos están exportados
- Los tipos coinciden con los schemas del backend
- No hay referencias a los tipos antiguos
validated_by/validated_at
T-114: Nuevos API clients
Objetivo: Crear/actualizar los clientes API del frontend para los nuevos endpoints.
Archivos a crear/modificar:
frontend/src/api/tests.ts— actualizar con nuevos endpointsfrontend/src/api/evidence.ts— actualizar con parámetroteamfrontend/src/api/test-templates.ts— nuevo
Funciones nuevas en tests.ts:
export const createTestFromTemplate = (templateId: string, techniqueId: string) => ...
export const updateTestRed = (testId: string, data: RedUpdateData) => ...
export const updateTestBlue = (testId: string, data: BlueUpdateData) => ...
export const startExecution = (testId: string) => ...
export const submitRedEvidence = (testId: string) => ...
export const submitBlueEvidence = (testId: string) => ...
export const validateAsRedLead = (testId: string, data: RedValidation) => ...
export const validateAsBlueLead = (testId: string, data: BlueValidation) => ...
export const reopenTest = (testId: string) => ...
export const getTestTimeline = (testId: string) => ...
Funciones nuevas en evidence.ts:
export const uploadEvidence = (testId: string, file: File, team: TeamSide, notes?: string) => ...
export const getTestEvidences = (testId: string, team?: TeamSide) => ...
export const deleteEvidence = (evidenceId: string) => ...
Funciones en test-templates.ts:
export const getTemplates = (filters?: TemplateFilters) => ...
export const getTemplateById = (id: string) => ...
export const getTemplatesByTechnique = (mitreId: string) => ...
export const createTemplate = (data: CreateTemplate) => ...
export const importAtomicTests = () => ...
Validación:
- Todos los imports funcionan sin errores TypeScript
- Cada función envía la petición al endpoint correcto
uploadEvidenceincluye el campoteamen FormDatagetTestEvidencesenvía el query paramteamcorrectamente
FASE 14 — Frontend: Página de Test Rediseñada con Pestañas Red/Blue
T-115: Componente TestDetailHeader
Objetivo: Crear el header del detalle del test con información del estado, progreso y acciones contextuales.
Archivo a crear: frontend/src/components/test-detail/TestDetailHeader.tsx
Contenido:
- Nombre del test y badge de estado con color
- Barra de progreso visual (5 pasos: draft → red → blue → review → validated)
- Nombre de la técnica asociada (link)
- Botones de acción contextuales según rol y estado:
- Red Tech en
draft: botón "Start Execution" - Red Tech en
red_executing: botón "Submit to Blue Team" - Blue Tech en
blue_evaluating: botón "Submit for Review" - Red Lead en
in_review: botón "Approve/Reject Red" - Blue Lead en
in_review: botón "Approve/Reject Blue"
- Red Tech en
- Indicadores de validación dual (checkmarks para red_lead y blue_lead)
Validación:
- El header muestra toda la información correcta
- La barra de progreso refleja el estado actual
- Los botones aparecen solo cuando el rol y estado lo permiten
- El botón "Start Execution" aparece solo en draft para red_tech
- Los indicadores de validación dual se actualizan correctamente
T-116: Componente de pestañas Red Team / Blue Team
Objetivo: Crear el sistema de pestañas que separa las evidencias y el contenido entre Red Team y Blue Team.
Archivo a crear: frontend/src/components/test-detail/TeamTabs.tsx
Estructura de pestañas:
┌──────────────────────────────────────────────────────────────┐
│ [🔴 Red Team] [🔵 Blue Team] [📋 Summary] [📜 Timeline] │
├──────────────────────────────────────────────────────────────┤
│ │
│ Contenido de la pestaña seleccionada │
│ │
└──────────────────────────────────────────────────────────────┘
Pestaña Red Team:
- Procedimiento de ataque (editable en
draftyred_executing) - Herramienta utilizada (editable en
draftyred_executing) - Indicador de éxito del ataque (switch: sí/no)
- Resumen del Red Team (textarea)
- Lista de evidencias Red con upload (solo en
draftyred_executingpara red_tech) - Estado de validación del Red Lead (si aplica)
Pestaña Blue Team:
- Resultado de detección (detected/not_detected/partially_detected)
- Resumen del Blue Team (textarea)
- Lista de evidencias Blue con upload (solo en
blue_evaluatingpara blue_tech) - Estado de validación del Blue Lead (si aplica)
Pestaña Summary:
- Vista resumen con ambos lados lado a lado
- Comparativa visual: ataque vs detección
- Resultado final
Pestaña Timeline:
- Historial cronológico de todos los cambios del test
- Cada entrada con usuario, acción, fecha y detalles
Validación:
- Las pestañas se renderizan correctamente
- Cambiar de pestaña muestra el contenido correcto
- Los campos son editables solo en el estado y rol apropiados
- Upload de evidencias funciona dentro de cada pestaña
- La pestaña Summary muestra comparativa correcta
T-117: Página TestDetailPage rediseñada
Objetivo: Integrar los nuevos componentes en la página de detalle del test, reemplazando el diseño actual.
Archivo a modificar: frontend/src/pages/TestDetailPage.tsx
Estructura:
┌─────────────────────────────────────────────────────────┐
│ TestDetailHeader (estado, progreso, acciones) │
├─────────────────────────────────────────────────────────┤
│ │
│ TeamTabs │
│ ┌─────────────────────────────────────────────────────┐│
│ │ Pestaña seleccionada (Red/Blue/Summary/Timeline) ││
│ └─────────────────────────────────────────────────────┘│
│ │
├─────────────────────────────────────────────────────────┤
│ Sidebar: Metadata del test │
│ - Técnica asociada │
│ - Plataforma │
│ - Creador │
│ - Fechas │
│ - Template origen (si aplica) │
└─────────────────────────────────────────────────────────┘
Interacciones:
- Toda acción usa mutations de react-query con invalidación
- Modales de confirmación para validar/rechazar
- Toast notifications para feedback
- Loading states en todas las operaciones
Validación:
- La página carga y muestra todos los datos del test
- Las pestañas Red/Blue/Summary/Timeline funcionan
- Las acciones de validación dual funcionan correctamente
- La transición de estado se refleja en tiempo real tras cada acción
- Los permisos de edición se respetan según rol y estado
- La subida de evidencias funciona dentro de las pestañas
T-118: Modal de Validación Dual
Objetivo: Crear un modal de validación que permita a los managers aprobar o rechazar su parte del test, con notas obligatorias en caso de rechazo.
Archivo a crear: frontend/src/components/test-detail/ValidationModal.tsx
Contenido:
- Título: "Validate as Red Lead" / "Validate as Blue Lead"
- Resumen de evidencias del equipo correspondiente
- Opciones: Approve / Reject
- Textarea para notas (obligatorio en rechazo)
- Indicador visual del estado de la otra validación
- Botón de confirmar con loading state
Validación:
- El modal aparece al hacer click en Validate
- Se puede seleccionar Approve o Reject
- Reject requiere notas obligatorias — botón deshabilitado sin notas
- Approve envía la petición y cierra el modal
- Se muestra el estado de la validación del otro manager
- Loading state funciona durante la petición
FASE 15 — Frontend: Catálogo de Tests y Creación desde Templates
T-119: Página de catálogo de TestTemplates
Objetivo: Crear una página donde los usuarios puedan explorar el catálogo de tests disponibles, filtrar por técnica, plataforma y fuente, y ver el detalle de cada template.
Archivo a crear: frontend/src/pages/TestCatalogPage.tsx
Componentes necesarios:
- Barra de búsqueda y filtros (source, platform, severity, technique)
- Grid/lista de templates con cards
- Cada card muestra: nombre, técnica MITRE, plataforma, severidad, fuente (badge), botón "Use Template"
- Paginación
Ruta: /test-catalog — añadir al router y al sidebar.
Validación:
- La página carga y muestra templates del backend
- Los filtros funcionan (source, platform, severity, search)
- Cada card muestra la información correcta
- El botón "Use Template" navega o abre modal de instanciación
- Responsive en móvil y desktop
T-120: Modal/Página de instanciación de Template
Objetivo: Permitir crear un test real a partir de un template, pre-rellenando los campos y permitiendo modificaciones.
Archivo a crear: frontend/src/components/TestFromTemplateForm.tsx
Flujo:
- El usuario selecciona un template (desde el catálogo o desde la vista de técnica)
- Se abre un formulario pre-rellenado con los datos del template
- El usuario puede modificar los campos
- Al guardar, se crea un test real con
state=draft
Campos del formulario:
- Nombre (pre-rellenado)
- Descripción (pre-rellenado)
- Técnica asociada (pre-rellenado si se viene de una técnica)
- Plataforma (pre-rellenado)
- Procedimiento de ataque sugerido (pre-rellenado, editable)
- Herramienta sugerida (pre-rellenado, editable)
- Detección esperada (pre-rellenado, readonly — referencia para blue team)
Validación:
- El formulario se pre-rellena con datos del template
- Se puede modificar cualquier campo editable
- Submit crea el test y redirige al detalle
- El test creado tiene referencia al template origen
- Campos requeridos se validan antes de submit
T-121: Integrar catálogo en vista de Técnica
Objetivo: Desde la página de detalle de una técnica, permitir ver los templates disponibles y crear tests directamente.
Archivo a modificar: frontend/src/pages/TechniqueDetailPage.tsx
Cambios:
- Añadir sección "Available Test Templates" debajo de los tests existentes
- Mostrar cards resumidas de templates disponibles para esa técnica
- Botón "Run This Test" en cada template que abre el formulario de instanciación
- Si no hay templates, mostrar mensaje y link al catálogo general
Validación:
- La sección de templates aparece en la página de técnica
- Se muestran solo los templates para esa técnica MITRE
- "Run This Test" pre-rellena correctamente el formulario
- Si no hay templates se muestra mensaje apropiado
- La creación del test actualiza la lista de tests de la técnica
FASE 16 — Frontend: Vistas de Gestión y Dashboard Mejorado
T-122: Vista de Tests mejorada con filtros por estado y equipo
Objetivo: Mejorar la página de listado de tests con filtros avanzados y vistas específicas por equipo.
Archivo a modificar: frontend/src/pages/TestsPage.tsx
Mejoras:
- Filtros: por estado (todos los nuevos estados), por equipo asignado, por técnica, por plataforma
- Vista de "Mis tareas pendientes" según rol:
- Red Tech: tests en
draftored_executingcreados por mí - Blue Tech: tests en
blue_evaluating - Red Lead: tests en
in_reviewpendientes de validación red - Blue Lead: tests en
in_reviewpendientes de validación blue
- Red Tech: tests en
- Estadísticas rápidas: contadores por estado (cards superiores)
- Tabla con columnas: nombre, técnica, estado, equipo actual, última actualización, acciones
Validación:
- Los filtros por estado funcionan con los nuevos estados
- "Mis tareas pendientes" filtra correctamente según el rol del usuario
- Los contadores por estado son correctos
- La tabla muestra toda la información necesaria
- Click en un test navega al detalle
T-123: Dashboard mejorado con métricas Red/Blue
Objetivo: Añadir al dashboard métricas específicas del flujo de validación Red/Blue.
Archivos a modificar:
backend/app/routers/metrics.py— añadir nuevos endpointsbackend/app/schemas/metrics.py— añadir nuevos schemasfrontend/src/pages/DashboardPage.tsx— añadir nuevas secciones
Nuevos endpoints de métricas:
GET /metrics/test-pipeline → contadores por estado del pipeline
GET /metrics/team-activity → actividad por equipo (tests completados, pendientes)
GET /metrics/validation-rate → tasa de aprobación/rechazo por manager
Nuevas secciones del dashboard:
- Pipeline de Tests: gráfico de funnel mostrando cuántos tests hay en cada estado
- Actividad por equipo: Red Team vs Blue Team — tests completados, tiempo medio
- Tasa de validación: porcentaje de aprobación por Red Lead y Blue Lead
- Tests recientes: tabla con los últimos 10 tests actualizados
Validación:
- Los nuevos endpoints retornan datos correctos
- El dashboard muestra las nuevas secciones
- El pipeline de tests refleja los estados reales
- Las métricas de equipo se calculan correctamente
- La sección de tests recientes se actualiza
T-124: Panel de administración de Templates
Objetivo: Añadir al panel de sistema la gestión de templates: importar Atomic Red Team, crear templates personalizados, ver estadísticas del catálogo.
Archivo a modificar: frontend/src/pages/SystemPage.tsx
Nuevas secciones:
- Importar Atomic Red Team: botón para ejecutar importación, con progreso y resultado
- Estadísticas del catálogo: total templates, por fuente, por plataforma
- Crear template personalizado: formulario inline o modal
- Gestionar templates: tabla con opción de activar/desactivar
Validación:
- Botón de importación ejecuta y muestra resultados
- Las estadísticas del catálogo se muestran correctamente
- Se puede crear un template personalizado
- Se puede desactivar un template
- Solo admin puede acceder a estas funciones
FASE 17 — Backend Tests Automatizados
T-125: Tests del flujo de trabajo Red/Blue
Objetivo: Crear tests automatizados que verifiquen todo el ciclo de vida de un test de seguridad.
Archivo a crear: backend/tests/test_workflow.py
Tests a implementar:
class TestWorkflow:
def test_full_happy_path():
"""draft → red_executing → blue_evaluating → in_review → validated"""
def test_rejection_and_reopen():
"""in_review → rejected → draft → red_executing → ..."""
def test_invalid_transitions():
"""Verificar que transiciones no válidas fallan"""
def test_red_tech_cannot_access_blue_phase():
"""Red tech no puede editar en blue_evaluating"""
def test_blue_tech_cannot_access_red_phase():
"""Blue tech no puede editar en red_executing"""
def test_dual_validation_both_approve():
"""Ambos managers aprueban → validated"""
def test_dual_validation_one_rejects():
"""Un manager rechaza → rejected"""
def test_evidence_team_separation():
"""Evidencias red y blue se separan correctamente"""
def test_red_edit_allowed_in_draft_and_red_executing():
"""PATCH /tests/{id}/red funciona en draft y red_executing"""
def test_reopen_clears_validation_fields():
"""Reopen limpia red/blue_validation_status y campos asociados"""
Validación:
pytest tests/test_workflow.pyejecuta todos los tests- Todos los tests pasan (verde)
- Cobertura del flujo completo
T-126: Tests de TestTemplates
Objetivo: Tests automatizados para el CRUD de templates y la instanciación.
Archivo a crear: backend/tests/test_templates.py
Tests:
class TestTemplates:
def test_create_template():
"""Admin puede crear un template"""
def test_list_templates_with_filters():
"""Filtros de source, platform, severity funcionan"""
def test_get_templates_by_technique():
"""Filtrar templates por técnica MITRE"""
def test_instantiate_template():
"""Crear test desde template pre-rellena campos"""
def test_soft_delete_template():
"""Desactivar template no lo borra físicamente"""
def test_non_admin_cannot_create_template():
"""Solo admin puede crear templates"""
Validación:
pytest tests/test_templates.pypasa todos los tests- Cobertura de CRUD y filtros
- Cobertura de permisos
T-127: Tests de métricas actualizadas
Objetivo: Tests automatizados para los nuevos endpoints de métricas.
Archivo a crear: backend/tests/test_metrics_v2.py
Tests:
class TestMetricsV2:
def test_pipeline_metrics():
"""Contadores por estado del pipeline correctos"""
def test_team_activity_metrics():
"""Actividad por equipo calculada correctamente"""
def test_technique_status_recalculation_with_new_states():
"""Recalculación funciona con los nuevos estados"""
def test_coverage_with_dual_validation():
"""Cobertura correcta tras validación dual"""
Validación:
pytest tests/test_metrics_v2.pypasa todos los tests- Las métricas coinciden con los datos de prueba
FASE 18 — Notificaciones y Sidebar de Actividad
T-128: Modelo de notificaciones
Objetivo: Crear un sistema básico de notificaciones in-app para alertar a los usuarios cuando necesitan actuar.
Archivo a crear: backend/app/models/notification.py
Campos:
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| user_id | UUID | FK → users.id, not null |
| type | String | not null (test_assigned, validation_needed, test_rejected, etc.) |
| title | String | not null |
| message | Text | nullable |
| entity_type | String | nullable (test, technique) |
| entity_id | UUID | nullable |
| read | Boolean | default False |
| created_at | DateTime | default utcnow |
Índices:
Index('ix_notifications_user_id', Notification.user_id)
Index('ix_notifications_read', Notification.read)
Index('ix_notifications_created_at', Notification.created_at)
Generar migración.
Servicio backend/app/services/notification_service.py:
def create_notification(db, user_id, type, title, message, entity_type, entity_id)
def mark_as_read(db, notification_id, user_id)
def mark_all_as_read(db, user_id)
def get_unread_count(db, user_id) -> int
def cleanup_old_notifications(db, days=90) -> int # Elimina notificaciones leídas > 90 días
Disparar notificaciones automáticamente:
- Cuando un test pasa a
red_executing→ notificar al creador (confirmación) - Cuando un test pasa a
blue_evaluating→ notificar a todos losblue_tech - Cuando un test pasa a
in_review→ notificar ared_leadyblue_lead - Cuando un test es rechazado → notificar al creador
- Cuando un test es validado → notificar al creador
Job de limpieza: Programar un job APScheduler diario que ejecute cleanup_old_notifications(db, days=90).
Validación:
- Se crea una notificación cuando un test cambia a
blue_evaluating - Se crea una notificación para managers cuando un test llega a
in_review - Se crea una notificación al creador cuando un test es rechazado
- Se crea una notificación al creador cuando un test es validado
get_unread_countretorna el número correctocleanup_old_notificationselimina notificaciones antiguas leídas
T-129: Endpoints y frontend de notificaciones
Objetivo: Endpoints API y UI de notificaciones.
Archivos a crear:
backend/app/routers/notifications.pyfrontend/src/api/notifications.tsfrontend/src/components/NotificationBell.tsxfrontend/src/components/NotificationDropdown.tsx
Endpoints:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| GET | /notifications | autenticado | Listar notificaciones del user (paginado, limit=20, offset) |
| GET | /notifications/unread-count | autenticado | Contador de no leídas |
| PATCH | /notifications/{id}/read | autenticado | Marcar como leída |
| POST | /notifications/read-all | autenticado | Marcar todas como leídas |
Paginación obligatoria en GET /notifications: limit (default 20, max 100) y offset.
Frontend:
NotificationBell: icono de campana en el header con badge de conteoNotificationDropdown: dropdown con lista de notificaciones (últimas 20)- Click en notificación navega a la entidad correspondiente y marca como leída
- Polling cada 30 segundos para actualizar conteo (usar react-query con
refetchInterval: 30000) - Botón "Mark all as read" en el dropdown
Validación:
- La campana muestra el conteo correcto de no leídas
- El dropdown lista las notificaciones ordenadas por fecha
- Click en una notificación navega correctamente y marca como leída
- "Mark all as read" limpia el conteo
- Las notificaciones se generan automáticamente con los cambios de estado
- El polling actualiza el conteo sin refrescar la página
FASE 19 — Mejoras de Remediación y Reportes
T-130: Campo de remediación en tests y templates
Objetivo: Añadir campos de remediación y recomendaciones, inspirados en el enfoque de Validato de "step-by-step remediation".
Archivos a modificar:
backend/app/models/test.py— nuevos camposbackend/app/models/test_template.py— nuevo campo- Schemas correspondientes
Nuevos campos en Test:
| Campo | Tipo | Restricciones |
|---|---|---|
| remediation_steps | Text | nullable |
| remediation_status | String | nullable (pending, in_progress, completed, not_applicable) |
| remediation_assignee | UUID | FK → users.id, nullable |
Nuevo campo en TestTemplate:
| Campo | Tipo | Restricciones |
|---|---|---|
| suggested_remediation | Text | nullable |
Generar migración.
Validación:
- Los nuevos campos se crean en la BD
- Se pueden asignar pasos de remediación a un test
- Se puede asignar un responsable de remediación
- El template puede sugerir remediación al instanciar
T-131: Endpoint y UI de reportes
Objetivo: Crear un sistema básico de reportes que permita exportar el estado de cobertura en diferentes formatos.
Archivos a crear:
backend/app/routers/reports.pyfrontend/src/pages/ReportsPage.tsx
Endpoints:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| GET | /reports/coverage-summary | autenticado | Reporte JSON completo |
| GET | /reports/coverage-csv | autenticado | Export CSV de cobertura |
| GET | /reports/test-results | autenticado | Reporte de resultados de tests |
| GET | /reports/remediation-status | autenticado | Reporte de estado de remediación |
Página de reportes:
- Selector de tipo de reporte
- Filtros (rango de fechas, tácticas, plataformas)
- Preview del reporte
- Botones de descarga (CSV, JSON)
- Resumen visual con métricas clave
Ruta: /reports — añadir al router y sidebar.
Validación:
- Cada endpoint retorna datos correctos
- El CSV se descarga y abre correctamente en Excel
- Los filtros funcionan en el frontend
- La preview del reporte se muestra correctamente
- Solo usuarios autenticados pueden acceder
FASE 20 — Pulido Final V2 y Documentación
T-132: Actualizar navegación y routing
Objetivo: Integrar todas las nuevas páginas en la navegación de la aplicación.
Archivos a modificar:
frontend/src/App.tsx— nuevas rutasfrontend/src/components/Sidebar.tsx— nuevos items
Nuevas rutas:
/test-catalog → TestCatalogPage
/tests/:testId → TestDetailPage (rediseñada)
/reports → ReportsPage
Items del sidebar:
- Dashboard
- ATT&CK Matrix (Techniques)
- Tests (con submenu)
- All Tests
- My Pending Tasks
- Test Catalog
- Reports
- System (admin)
- MITRE Sync
- Intel Scan
- Templates Management
- Users
- Audit Log
Validación:
- Todas las rutas nuevas funcionan
- El sidebar muestra los items correctos según el rol
- La navegación entre páginas es fluida
- No hay rutas rotas o 404
T-133: Error handling y edge cases
Objetivo: Asegurar que todos los nuevos flujos manejan errores correctamente.
Backend:
- Todos los endpoints nuevos tienen manejo de 404, 400, 403
- Las transiciones de estado inválidas retornan errores descriptivos con el formato:
{"detail": "Cannot transition from 'draft' to 'validated'. Valid transitions: ['red_executing']", "code": "INVALID_TRANSITION"}
- Los permisos de equipo se validan en cada endpoint
Frontend:
- Loading states en todas las operaciones nuevas
- Error messages descriptivos en validaciones y transiciones
- Confirmación antes de acciones destructivas (rechazar, reabrir)
- Feedback visual tras cada acción exitosa (toast)
Validación:
- Intentar transición inválida muestra error descriptivo con las transiciones válidas
- Permisos incorrectos muestran 403 con mensaje claro
- Loading states aparecen en todas las operaciones
- Toast de éxito tras cada acción exitosa
- Modal de confirmación antes de rechazar un test
T-134: Backend tests finales de integración V2
Objetivo: Suite final de tests que verifica el sistema completo end-to-end.
Archivo a crear: backend/tests/test_integration_v2.py
Tests:
class TestIntegrationV2:
def test_full_e2e_flow():
"""
1. Admin importa Atomic Red Team templates
2. Red Tech crea test desde template
3. Red Tech inicia ejecución (start-execution)
4. Red Tech sube evidencias y submite
5. Blue Tech evalúa y sube evidencias
6. Blue Tech submite para review
7. Red Lead y Blue Lead validan
8. Verificar que la técnica cambia de estado
"""
def test_rejection_recovery_flow():
"""Flujo completo con rechazo y recuperación"""
def test_notification_flow():
"""Verificar que las notificaciones se generan correctamente"""
def test_metrics_accuracy():
"""Verificar que las métricas son correctas tras operaciones"""
def test_report_generation():
"""Verificar generación de reportes"""
Validación:
pytest tests/test_integration_v2.pypasa todos los tests- El flujo E2E completo funciona sin errores
- Las métricas son consistentes tras todas las operaciones
T-135: Actualizar documentación V2
Objetivo: Actualizar README y documentación API para reflejar todos los cambios.
Archivos a modificar:
README.md— actualizar con nuevas funcionalidadesdocs/API.md— documentar nuevos endpoints
Secciones nuevas en README:
- Descripción del flujo Red Team / Blue Team
- Descripción de los roles y permisos
- Diagrama del ciclo de vida del test
- Cómo importar tests de Atomic Red Team
- Cómo usar el catálogo de templates
- Explicación del flujo de validación dual
Documentación API:
- Nuevos endpoints de tests (flujo Red/Blue)
- Endpoints de templates
- Endpoints de notificaciones
- Endpoints de reportes
- Nuevos endpoints de métricas
Validación:
- El README refleja todas las funcionalidades nuevas
- La documentación API cubre todos los endpoints nuevos
- Swagger UI en /docs muestra todos los endpoints correctamente
- Siguiendo el README, un nuevo desarrollador puede entender el flujo completo
Resumen de Fases V2
| Fase | Tareas | Descripción |
|---|---|---|
| 10 | T-100 a T-105 | Evolución del modelo de datos para Red/Blue Team |
| 11 | T-106 a T-108 | Lógica de negocio del flujo Red/Blue |
| 12 | T-109 a T-112 | Endpoints API Red/Blue |
| 13 | T-113 a T-114 | Frontend: tipos y API clients |
| 14 | T-115 a T-118 | Frontend: página de test con pestañas Red/Blue |
| 15 | T-119 a T-121 | Frontend: catálogo de tests y templates |
| 16 | T-122 a T-124 | Frontend: vistas de gestión y dashboard mejorado |
| 17 | T-125 a T-127 | Backend tests automatizados |
| 18 | T-128 a T-129 | Notificaciones in-app |
| 19 | T-130 a T-131 | Remediación y reportes |
| 20 | T-132 a T-135 | Pulido final y documentación |
Total V2: 36 tareas = 36 commits mínimo
PARTE 2 — AEGIS V3: Plataforma Avanzada de Validación de Seguridad
Lo que aporta V3 sobre V2
| Área | V2 ya cubre | V3 añade |
|---|---|---|
| Fuentes de tests | Atomic Red Team | +Sigma Rules, +LOLBAS, +GTFOBins, +CALDERA, +Adversary Emulation Library, +Elastic Detection Rules |
| Defensa | Evidencias Blue Team | +MITRE D3FEND mapping, +Sigma rule sugerida por test, +detection rule validation |
| Threat actors | Ninguno | Perfiles de grupo APT, campañas, priorización por sector/geografía |
| Métricas | Pipeline y cobertura | +MTTD, +MTTR, +Detection Efficacy, +tendencias temporales |
| Visualización | Matriz ATT&CK básica | +Heatmap estilo Navigator, +capas superpuestas, +export de layers |
| Compliance | Ninguno | Mapeo a NIST 800-53, DORA, NIS2, ISO 27001, reportes de compliance |
| Kill chain | Tests individuales | +Campañas (cadenas de tests), +attack path visualization |
| Scoring | Estados binarios | +Score de cobertura por técnica, +score global, +benchmark |
| Comparativa | Ninguna | +Comparar snapshots temporales, +antes/después de remediación |
| Automatización | Manual | +Scheduling de campañas, +re-test automático post-remediación |
Fuentes de Tests Adicionales
Fuentes para Red Team (Procedimientos de Ataque)
| Fuente | Descripción | Formato | Tests aprox. | URL |
|---|---|---|---|---|
| Atomic Red Team | Tests atómicos individuales por técnica | YAML | 1,500+ | GitHub |
| MITRE CALDERA | Abilities (acciones) ejecutables por agente | YAML | 400+ | GitHub |
| Adversary Emulation Library | Planes completos de emulación de APTs | YAML/JSON/PDF | 15+ planes | GitHub |
| LOLBAS | Binarios legítimos de Windows abusables | YAML/JSON | 400+ | GitHub |
| GTFOBins | Binarios legítimos de Unix/Linux abusables | Markdown/JSON | 350+ | GitHub |
| MITRE ATT&CK Procedures | Procedimientos documentados en la propia framework | STIX/JSON | 1,000+ | TAXII Server |
Fuentes para Blue Team (Reglas de Detección)
| Fuente | Descripción | Formato | Reglas aprox. | URL |
|---|---|---|---|---|
| SigmaHQ | Reglas de detección genéricas para SIEM | YAML | 3,000+ | GitHub |
| Elastic Detection Rules | Reglas de detección de Elastic SIEM | TOML | 1,000+ | GitHub |
| MITRE D3FEND | Framework de contramedidas defensivas | OWL/JSON | 200+ técnicas | d3fend.mitre.org |
| Splunk Security Content | Reglas de detección para Splunk | YAML | 1,500+ | GitHub |
Fuentes de Threat Intelligence
| Fuente | Descripción | Formato | URL |
|---|---|---|---|
| MITRE CTI | Datos de ATT&CK en STIX 2.0 (grupos, software, campañas) | STIX/JSON | GitHub |
| MITRE ATT&CK Groups | Perfiles de 140+ grupos APT con sus TTPs | STIX | attack.mitre.org/groups |
FASE 21 — Seed de Datos de Demo y Modelo de Fuentes
T-200: Seed de datos de demo para V3
Objetivo: Crear un script de seed que genere un volumen realista de datos para poder validar las funcionalidades de V3 (heatmaps, scoring, compliance). Sin datos suficientes, muchas tareas de V3 no se pueden validar.
Archivo a crear: backend/app/seed_demo.py
Datos a generar:
- 5 usuarios de cada rol (red_tech, blue_tech, red_lead, blue_lead, admin)
- 50 técnicas con diferentes estados (validated, partial, not_covered, in_progress, not_evaluated) — distribuidas entre varias tácticas
- 100 tests en diferentes estados del pipeline (draft, red_executing, blue_evaluating, in_review, validated, rejected)
- 50 evidencias (archivos dummy) asociadas a tests
- 20 audit logs variados
- 30 notificaciones para diferentes usuarios
- 10 templates manuales (además de los de Atomic Red Team)
Ejecutable con: python -m app.seed_demo
⚠️ Requisito: Solo ejecutar sobre una BD con el sync MITRE ya completado (necesita técnicas reales).
Validación:
- El seed genera todos los datos sin errores
- Las métricas del dashboard muestran datos variados
- El heatmap tiene técnicas de diferentes colores
- Los tests están en diferentes estados del pipeline
- Ejecutar el seed dos veces no falla (limpia o usa upsert)
T-201: Modelo unificado de fuente de datos (DataSource)
Objetivo: Crear un sistema de gestión de fuentes de datos que permita registrar, configurar y monitorizar las distintas fuentes de tests y reglas de detección.
Archivo a crear: backend/app/models/data_source.py
Campos:
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| name | String | unique, not null (ej: "atomic_red_team") |
| display_name | String | not null (ej: "Atomic Red Team") |
| type | String | not null (attack_procedure / detection_rule / threat_intel / defensive_technique) |
| url | String | nullable (URL base del repositorio/API) |
| description | Text | nullable |
| is_enabled | Boolean | default True |
| last_sync_at | DateTime | nullable |
| last_sync_status | String | nullable (success/error/in_progress) |
| last_sync_stats | JSONB | nullable ({"imported": X, "updated": Y, ...}) |
| sync_frequency | String | nullable (daily/weekly/monthly/manual) |
| config | JSONB | nullable (configuración específica de la fuente) |
| created_at | DateTime | default utcnow |
Generar migración.
Seed de fuentes iniciales: crear un script que registre todas las fuentes conocidas:
- atomic_red_team (attack_procedure)
- sigma (detection_rule)
- lolbas (attack_procedure)
- gtfobins (attack_procedure)
- caldera (attack_procedure)
- elastic_rules (detection_rule)
- d3fend (defensive_technique)
- mitre_cti (threat_intel)
Validación:
alembic upgrade headcrea la tabladata_sources- El seed crea las fuentes iniciales con tipos, URLs y configuración correctos
- Se pueden activar/desactivar fuentes individualmente
T-202: Modelo DetectionRule
Objetivo: Crear el modelo para almacenar reglas de detección de múltiples fuentes (Sigma, Elastic, Splunk, custom).
Archivo a crear: backend/app/models/detection_rule.py
Campos:
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| mitre_technique_id | String | not null |
| title | String | not null |
| description | Text | nullable |
| source | String | not null (sigma, elastic, splunk, custom) |
| source_id | String | nullable (ID en la fuente original) |
| source_url | String | nullable |
| rule_content | Text | not null (contenido YAML/KQL de la regla) |
| rule_format | String | not null (sigma_yaml, kql, spl, custom) |
| severity | String | nullable (informational, low, medium, high, critical) |
| platforms | JSONB | nullable, default [] |
| log_sources | JSONB | nullable (ej: {"product": "windows", "service": "sysmon"}) |
| false_positive_rate | String | nullable (low, medium, high) |
| is_active | Boolean | default True |
| created_at | DateTime | default utcnow |
Índices:
Index('ix_detection_rules_mitre_technique_id', DetectionRule.mitre_technique_id)
Index('ix_detection_rules_source', DetectionRule.source)
Index('ix_detection_rules_severity', DetectionRule.severity)
Generar migración.
Validación:
- La tabla se crea con los índices correctos
- Se puede insertar una regla con todos los campos
- El campo
rule_contentacepta YAML largo
FASE 22 — Importación de Fuentes de Tests
T-203: Servicio de importación de Sigma Rules
Objetivo: Importar reglas de detección Sigma del repositorio SigmaHQ y almacenarlas como DetectionRules.
Archivo a crear: backend/app/services/sigma_import_service.py
Dependencia a añadir: pySigma en requirements.txt (para parsear YAML de Sigma correctamente, ya que algunos tienen formatos edge-case que un parser YAML genérico no maneja bien).
⚠️ Estrategia de descarga desde GitHub: El repositorio SigmaHQ tiene 3000+ archivos YAML. Usar la misma estrategia que Atomic Red Team:
- Descargar ZIP del repositorio completo vía
https://github.com/SigmaHQ/sigma/archive/refs/heads/main.zip - Descomprimir en directorio temporal
- Parsear todos los ficheros YAML dentro de
rules/ - Limpiar al finalizar
Lógica:
- Descargar y descomprimir el repositorio
- Para cada regla
.ymlenrules/:- Parsear YAML usando pySigma (título, descripción, logsource, detection, tags, level)
- Extraer tags de ATT&CK:
attack.t1059.001→T1059.001(normalizar a uppercase) - Ignorar reglas sin tags de ATT&CK (no se pueden mapear)
- Extraer severidad del campo
level - Extraer logsource para el campo
log_sources - Almacenar como
DetectionRuleconsource = "sigma",rule_format = "sigma_yaml" - Usar el path relativo del fichero como
source_idpara deduplicación
- No duplicar reglas existentes (comparar por
source+source_id) - Actualizar
data_source.last_sync_aty stats
Validación:
- La importación crea DetectionRules en la BD
- Cada regla tiene su técnica MITRE mapeada correctamente
- El contenido YAML de la regla se almacena completo
- Ejecutar dos veces no duplica
- Se importan al menos 2000+ reglas (las que tienen tags ATT&CK)
- Las severidades se mapean correctamente
- La descarga del ZIP funciona sin rate limiting
T-204: Servicio de importación de LOLBAS y GTFOBins
Objetivo: Importar binarios y técnicas de "living off the land" desde LOLBAS (Windows) y GTFOBins (Linux) como templates de ataque.
Archivo a crear: backend/app/services/lolbas_import_service.py
⚠️ Estrategia de descarga:
- LOLBAS: Descargar ZIP del repositorio
https://github.com/LOLBAS-Project/LOLBAS/archive/refs/heads/master.zipy parsear los YAMLs deyml/OSBinaries/,yml/OSLibraries/,yml/OSScripts/ - GTFOBins: Descargar ZIP de
https://github.com/GTFOBins/GTFOBins.github.io/archive/refs/heads/master.zipy parsear los markdowns de_gtfobins/
Lógica para LOLBAS:
- Descargar y descomprimir el repositorio
- Cada YAML contiene: Name, Description, Commands (lista con Description, Command, Usecase, MitreID), Paths
- Por cada binario y cada comando con MitreID:
- Crear TestTemplate con
source = "lolbas",platform = "windows" - El
attack_procedureincluye el Command documentado - El
tool_suggestedes el nombre del binario - El
mitre_technique_idviene del campo MitreID
- Crear TestTemplate con
- Usar
source + Name + MitreIDcomo clave de deduplicación
Lógica para GTFOBins:
- Descargar y descomprimir el repositorio
- Cada markdown en
_gtfobins/contiene front-matter YAML con funciones (shell, file-upload, file-download, sudo, suid, etc.) - Por cada binario y función:
- Crear TestTemplate con
source = "gtfobins",platform = "linux" - Mapear funciones a técnicas MITRE cuando sea posible (shell → T1059, file-download → T1105, etc.)
- El
attack_procedureincluye los ejemplos de comandos
- Crear TestTemplate con
- Nota: GTFOBins no tiene mapeos directos a MITRE — crear un diccionario de mapeo estático de función → técnica MITRE
Validación:
- LOLBAS importa templates para Windows
- GTFOBins importa templates para Linux
- Cada template tiene su técnica MITRE mapeada
- Los comandos de ejemplo se almacenan en
attack_procedure - No se duplican en ejecuciones posteriores
- Las descargas de ZIP funcionan sin rate limiting
T-205: Servicio de importación de MITRE CALDERA abilities
Objetivo: Importar abilities (acciones ejecutables) del framework CALDERA como templates de tests.
Archivo a crear: backend/app/services/caldera_import_service.py
⚠️ Estrategia de descarga:
Descargar ZIP del repositorio CALDERA: https://github.com/mitre/caldera/archive/refs/heads/master.zip. Las abilities están en data/abilities/ organizadas por táctica, cada una es un fichero YAML.
Lógica:
- Descargar y descomprimir el repositorio
- Navegar
data/abilities/{tactic}/— cada subdirectorio corresponde a una táctica MITRE - Cada YAML de ability contiene:
id,name,description,tactic,technique.attack_id,platforms(dict con OS → executors) - Por cada ability:
- Crear TestTemplate con
source = "caldera" - Setear
mitre_technique_iddesdetechnique.attack_id - Setear
platformdesde las keys deplatforms(windows, linux, darwin) - Extraer los comandos de ejecución de
platforms.{os}.{executor}.command - Setear
attack_procedurecon los comandos - Usar el
idde la ability comoatomic_test_idpara deduplicación
- Crear TestTemplate con
- No duplicar abilities existentes
Validación:
- Se importan abilities de CALDERA
- Cada template tiene la técnica MITRE correcta
- Las plataformas soportadas se registran
- Los comandos de ejecución se almacenan
- Se importan al menos 300+ templates
T-206: Servicio de importación de Elastic Detection Rules
Objetivo: Importar reglas de detección del repositorio open-source de Elastic como DetectionRules.
Archivo a crear: backend/app/services/elastic_import_service.py
Dependencia a añadir: toml en requirements.txt
⚠️ Estrategia de descarga:
Descargar ZIP del repositorio: https://github.com/elastic/detection-rules/archive/refs/heads/main.zip. Las reglas están en rules/ organizadas por OS/plataforma.
Lógica:
- Descargar y descomprimir el repositorio
- Parsear cada fichero
.tomlenrules/ - Cada regla TOML contiene secciones:
[metadata]— creation_date, maturity, etc.[rule]— name, description, query (KQL), severity, type[[rule.threat]]— array con framework="MITRE ATT&CK", technique (id, name)
- Por cada regla:
- Extraer mappings de ATT&CK del campo
rule.threat - Crear DetectionRule con
source = "elastic",rule_format = "kql" - Almacenar el query KQL en
rule_content - Usar el nombre del fichero TOML como
source_id
- Extraer mappings de ATT&CK del campo
- No duplicar en re-ejecuciones
Validación:
- Se importan reglas de Elastic
- Cada regla tiene su técnica MITRE mapeada
- El KQL se almacena completo y correctamente
- Las severidades se mapean
- Se importan al menos 500+ reglas
T-207: Endpoint unificado de importación y panel de fuentes
Objetivo: Crear un panel de administración centralizado para gestionar todas las fuentes de datos.
Archivos a crear/modificar:
backend/app/routers/data_sources.pyfrontend/src/pages/DataSourcesPage.tsx
Endpoints:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| GET | /data-sources | admin | Listar todas las fuentes |
| PATCH | /data-sources/{id} | admin | Activar/desactivar, cambiar config |
| POST | /data-sources/{id}/sync | admin | Ejecutar importación de una fuente |
| POST | /data-sources/sync-all | admin | Importar de todas las fuentes activas |
| GET | /data-sources/{id}/stats | admin | Estadísticas de la fuente |
Backend — Dispatcher de sync:
Crear un dispatcher que mapee cada data_source.name a su servicio de importación:
SYNC_HANDLERS = {
"atomic_red_team": atomic_import_service.sync,
"sigma": sigma_import_service.sync,
"lolbas": lolbas_import_service.sync,
"gtfobins": lolbas_import_service.sync_gtfobins,
"caldera": caldera_import_service.sync,
"elastic_rules": elastic_import_service.sync,
# d3fend y mitre_cti se añaden en fases posteriores
}
Frontend — Panel de fuentes:
- Tabla con todas las fuentes: nombre, tipo, estado (badge), última sync, stats
- Toggle para activar/desactivar
- Botón de sync individual con loading state y resultado
- Botón de "Sync All" con progreso (ejecuta secuencialmente)
- Estadísticas: total de items importados por fuente, última fecha, errores
Validación:
- El panel muestra todas las fuentes registradas
- Se puede activar/desactivar cada fuente
- Sync individual ejecuta la importación correcta y muestra resultado
- "Sync All" ejecuta todas las fuentes activas secuencialmente
- Las estadísticas se actualizan tras cada sync
- Solo admin puede acceder
FASE 23 — Perfiles de Amenaza (Threat Actor Profiles)
T-208: Modelo ThreatActor
Objetivo: Crear un modelo para almacenar perfiles de grupos de amenaza (APTs) con sus TTPs asociadas.
Archivo a crear: backend/app/models/threat_actor.py
Campos de ThreatActor:
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| mitre_id | String | unique, nullable (ej: "G0016" para APT29) |
| name | String | not null |
| aliases | JSONB | nullable, default [] |
| description | Text | nullable |
| country | String | nullable |
| target_sectors | JSONB | nullable, default [] |
| target_regions | JSONB | nullable, default [] |
| motivation | String | nullable (espionage, financial, destruction, etc.) |
| sophistication | String | nullable (low, medium, high, advanced) |
| first_seen | String | nullable |
| last_seen | String | nullable |
| references | JSONB | nullable, default [] |
| mitre_url | String | nullable |
| is_active | Boolean | default True |
| created_at | DateTime | default utcnow |
Modelo ThreatActorTechnique (tabla intermedia):
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| threat_actor_id | UUID | FK → threat_actors.id, not null |
| technique_id | UUID | FK → techniques.id, not null |
| usage_description | Text | nullable |
| first_seen_using | String | nullable |
Índices:
Index('ix_threat_actor_techniques_actor', ThreatActorTechnique.threat_actor_id)
Index('ix_threat_actor_techniques_technique', ThreatActorTechnique.technique_id)
UniqueConstraint('threat_actor_id', 'technique_id', name='uq_actor_technique')
Generar migración.
Validación:
- Las tablas se crean correctamente con índices
- Se puede asociar un threat actor a múltiples técnicas
- Una técnica puede estar asociada a múltiples threat actors
- No se permite duplicar la misma relación actor-técnica (unique constraint)
T-209: Importación de Threat Actors desde MITRE CTI
Objetivo: Importar perfiles de grupos de amenaza desde el repositorio MITRE CTI (STIX 2.0).
Archivo a crear: backend/app/services/threat_actor_import_service.py
⚠️ Estrategia de descarga:
Descargar ZIP de https://github.com/mitre/cti/archive/refs/heads/master.zip. Los datos están en enterprise-attack/.
⚠️ Complejidad del formato STIX 2.0: Los datos STIX no son triviales de parsear. Los threat actors (grupos) no contienen directamente sus técnicas. La estructura es:
- Objetos
intrusion-set= grupos APT (lo que queremos como ThreatActor) - Objetos
relationshipde tipouses= conectan unintrusion-setcon unattack-pattern(técnica) - Objetos
attack-pattern= técnicas MITRE (ya las tenemos en la BD)
Lógica detallada:
- Descargar y descomprimir el repositorio
- Cargar el JSON bundle principal:
enterprise-attack/enterprise-attack.json - Paso 1 — Parsear intrusion-sets: Filtrar objetos donde
type == "intrusion-set"- Para cada grupo: extraer
name,description,aliases,external_references - De
external_referencesextraer el MITRE ID (dondesource_name == "mitre-attack") - Crear ThreatActor en BD
- Para cada grupo: extraer
- Paso 2 — Parsear relationships: Filtrar objetos donde
type == "relationship"yrelationship_type == "uses"- Filtrar relaciones donde
source_refapunte a unintrusion-setytarget_refapunte a unattack-pattern - Para cada relación:
- Resolver
source_ref→ encontrar el ThreatActor en BD - Resolver
target_ref→ extraer el MITRE ID del attack-pattern y encontrar la Technique en BD - Crear ThreatActorTechnique con
usage_descriptiondel campodescriptionde la relación
- Resolver
- Filtrar relaciones donde
- No duplicar en re-ejecuciones (comparar por
mitre_id)
Validación:
- Se importan 140+ threat actors
- Cada actor tiene sus técnicas asociadas
- Las relaciones actor-técnica son correctas (verificar APT29/G0016 con datos de MITRE)
- Los aliases y descriptions se importan
- Re-ejecutar no duplica
- El campo
usage_descriptioncontiene la descripción de cómo el grupo usa la técnica
T-210: Endpoints de Threat Actors
Objetivo: API para gestionar y consultar threat actors con sus técnicas y cobertura.
Archivo a crear: backend/app/routers/threat_actors.py
Endpoints:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| GET | /threat-actors | autenticado | Listar con filtros |
| GET | /threat-actors/{id} | autenticado | Detalle con técnicas |
| GET | /threat-actors/{id}/coverage | autenticado | Porcentaje de cobertura contra este actor |
| GET | /threat-actors/{id}/gaps | autenticado | Técnicas del actor sin tests validados |
Filtros del listado:
country,target_sectors,motivation,sophisticationsearch(busca en name, aliases, description)- Paginación:
offset+limit
Lógica de /coverage:
- Obtener todas las técnicas del actor
- Contar cuántas tienen
status_global= validated o partial - Retornar porcentaje y desglose
Lógica de /gaps:
- Obtener técnicas del actor donde
status_globalNOT IN (validated) - Retornar lista con info de cada técnica y templates disponibles
Validación:
- El listado muestra threat actors con filtros funcionales
- El detalle incluye las técnicas del actor
- El coverage calcula correctamente el porcentaje
- El gap analysis identifica técnicas sin tests validados
- La paginación funciona
T-211: Frontend de Threat Actors — Listado
Objetivo: Página de listado de threat actors con filtros y cards.
Archivos a crear:
frontend/src/api/threat-actors.tsfrontend/src/pages/ThreatActorsPage.tsx
Contenido:
- Grid de cards con: nombre, país (bandera), sectores, motivación, nº técnicas, cobertura %
- Filtros laterales por sector, región, motivación
- Buscador
- Paginación
Ruta: /threat-actors — añadir al router y al sidebar.
Validación:
- La página carga y muestra threat actors del backend
- Los filtros funcionan
- Cada card muestra la información correcta
- Click en un actor navega al detalle
- La ruta aparece en el sidebar
T-212: Frontend de Threat Actors — Detalle con heatmap y gap analysis
Objetivo: Página de detalle de un threat actor con su perfil, heatmap de técnicas y gap analysis.
Archivo a crear: frontend/src/pages/ThreatActorDetailPage.tsx
Secciones:
- Header: nombre, aliases, country, motivación, sophistication
- Description: texto descriptivo del grupo
- Heatmap de técnicas: mini-matriz ATT&CK mostrando solo las técnicas de este actor, coloreadas por estado de cobertura
- Coverage gap analysis: tabla de técnicas NO cubiertas, con templates disponibles
- Lista de tests existentes vinculados a técnicas de este actor
Validación:
- El perfil completo se muestra
- El heatmap renderiza solo las técnicas del actor
- Los colores corresponden al estado de cobertura
- El gap analysis lista técnicas sin cobertura
- Si hay templates disponibles para una gap, se muestra un indicador
FASE 24 — MITRE D3FEND: Contramedidas Defensivas
T-213: Modelo y importación de D3FEND
Objetivo: Integrar el framework MITRE D3FEND para mapear cada técnica ATT&CK a las contramedidas defensivas recomendadas.
Archivo a crear: backend/app/models/defensive_technique.py
Campos de DefensiveTechnique:
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| d3fend_id | String | unique, not null (ej: "D3-AL") |
| name | String | not null |
| description | Text | nullable |
| tactic | String | nullable (D3FEND tactic: Detect, Isolate, etc.) |
| d3fend_url | String | nullable |
| created_at | DateTime | default utcnow |
Modelo DefensiveTechniqueMapping (mapeo ATT&CK → D3FEND):
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| attack_technique_id | UUID | FK → techniques.id, not null |
| defensive_technique_id | UUID | FK → defensive_techniques.id, not null |
Servicio de importación backend/app/services/d3fend_import_service.py:
- Usar la API de D3FEND (
https://d3fend.mitre.org/api/) para obtener técnicas defensivas - Usar el endpoint de mappings ATT&CK-D3FEND (
/api/offensive-technique/{attack_id}.json) para establecer relaciones - Almacenar técnicas defensivas y sus mapeos
Validación:
- Se importan 200+ técnicas defensivas D3FEND
- Los mappings ATT&CK → D3FEND se crean correctamente
- Desde una técnica ATT&CK se pueden consultar sus contramedidas D3FEND
T-214: UI de contramedidas en vista de técnica y test
Objetivo: Mostrar las contramedidas D3FEND recomendadas en la vista de detalle de técnica y en la pestaña Blue Team del test.
Archivos a modificar:
frontend/src/pages/TechniqueDetailPage.tsx— nueva sección "Recommended Defenses"frontend/src/components/test-detail/TeamTabs.tsx— en pestaña Blue, mostrar contramedidas
Sección en TechniqueDetailPage:
- Lista de contramedidas D3FEND recomendadas para esta técnica
- Cada contramedida con: nombre, descripción, tactic D3FEND, link a documentación
En pestaña Blue Team del test:
- Panel "Recommended Detection Approaches"
- Lista de contramedidas D3FEND aplicables
- Reglas de detección Sigma/Elastic disponibles para esta técnica (del catálogo)
Validación:
- La vista de técnica muestra contramedidas D3FEND
- La pestaña Blue Team muestra las contramedidas y reglas de detección
- Los links a documentación D3FEND funcionan
FASE 25 — Reglas de Detección Sugeridas por Test
T-215: Asociar DetectionRules a tests y templates
Objetivo: Vincular reglas de detección a los tests y templates para que el Blue Team sepa qué regla debería haber detectado el ataque.
Modelo a crear: tabla intermedia test_template_detection_rules:
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| test_template_id | UUID | FK → test_templates.id, nullable |
| detection_rule_id | UUID | FK → detection_rules.id, not null |
| is_primary | Boolean | default False |
Lógica de auto-asociación:
Al importar templates y reglas de detección, asociar automáticamente por técnica MITRE:
- Un template de ataque
T1059.001se asocia a todas las reglas Sigma/Elastic paraT1059.001 - Marcar como
is_primarylas reglas cuya severidad sea >= high
Endpoints nuevos:
GET /test-templates/{id}/detection-rules → reglas de detección sugeridas
GET /detection-rules?technique={mitre_id} → reglas para una técnica
GET /detection-rules?source={source} → reglas por fuente
Validación:
- Los templates se asocian automáticamente a sus reglas de detección
GET /test-templates/{id}/detection-rulesretorna las reglas correctas- Las reglas primarias se marcan correctamente
- Filtrar por técnica y fuente funciona
T-216: UI de reglas de detección y checklist en pestaña Blue Team
Objetivo: Cuando el Blue Team evalúa un test, mostrarle las reglas de detección que deberían haber saltado, permitiéndole marcar cuáles detectaron y cuáles no.
Archivos a crear:
frontend/src/components/test-detail/DetectionRuleChecklist.tsxbackend/app/models/test_detection_result.py
Modelo TestDetectionResult:
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| test_id | UUID | FK → tests.id, not null |
| detection_rule_id | UUID | FK → detection_rules.id, not null |
| triggered | Boolean | nullable (null = not evaluated) |
| notes | Text | nullable |
| evaluated_by | UUID | FK → users.id, nullable |
| evaluated_at | DateTime | nullable |
Componente DetectionRuleChecklist:
- Lista de reglas de detección asociadas al test/template
- Cada regla con: título, severidad (badge color), fuente (Sigma/Elastic), contenido expandible
- Checkbox: "Triggered" / "Not triggered" / "Not applicable"
- Campo de notas por regla
- Resumen: X/Y reglas detectaron (con porcentaje)
Validación:
- El checklist muestra las reglas de detección correctas
- Se puede marcar cada regla como triggered/not triggered/N.A.
- Las notas se guardan correctamente
- El resumen X/Y se calcula
- Los resultados se persisten en la BD
FASE 26 — Campañas de Tests (Attack Chains)
T-217: Modelo Campaign
Objetivo: Crear campañas que agrupen múltiples tests en una secuencia que simula una cadena de ataque completa (kill chain).
Archivo a crear: backend/app/models/campaign.py
Campos de Campaign:
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| name | String | not null |
| description | Text | nullable |
| type | String | not null (custom, apt_emulation, kill_chain, compliance) |
| threat_actor_id | UUID | FK → threat_actors.id, nullable |
| status | String | default "draft" (draft, active, completed, archived) |
| created_by | UUID | FK → users.id, nullable |
| scheduled_at | DateTime | nullable |
| completed_at | DateTime | nullable |
| target_platform | String | nullable |
| tags | JSONB | nullable, default [] |
| created_at | DateTime | default utcnow |
Campos de CampaignTest (tests de la campaña, con orden):
| Campo | Type | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| campaign_id | UUID | FK → campaigns.id, not null |
| test_id | UUID | FK → tests.id, not null |
| order_index | Integer | not null (posición en la cadena) |
| depends_on | UUID | FK → campaign_tests.id, nullable (test previo) |
| phase | String | nullable (initial_access, execution, persistence, etc.) |
⚠️ Prevención de dependencias circulares:
El campo depends_on es una FK self-referencial que podría crear ciclos (A depende de B que depende de A). Implementar validación:
def validate_no_circular_dependency(db, campaign_id, test_id, depends_on_id):
"""Recorrer la cadena de depends_on y verificar que no se forma un ciclo."""
visited = set()
current = depends_on_id
while current is not None:
if current in visited or current == test_id:
raise HTTPException(400, "Circular dependency detected")
visited.add(current)
parent = db.query(CampaignTest).filter_by(id=current).first()
current = parent.depends_on if parent else None
Generar migración.
Validación:
- Las tablas se crean correctamente
- Una campaña puede contener múltiples tests ordenados
- Los tests pueden tener dependencias
- Intentar crear una dependencia circular falla con error descriptivo
- El campo
phaseacepta las fases del kill chain
T-218: Endpoints y lógica de Campañas
Archivos a crear:
backend/app/routers/campaigns.pybackend/app/services/campaign_service.py
Endpoints:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| GET | /campaigns | autenticado | Listar campañas con filtros |
| POST | /campaigns | red_tech, admin | Crear campaña |
| GET | /campaigns/{id} | autenticado | Detalle con tests y progreso |
| PATCH | /campaigns/{id} | creador, admin | Actualizar campaña |
| POST | /campaigns/{id}/tests | red_tech, admin | Añadir test a campaña |
| DELETE | /campaigns/{id}/tests/{test_id} | creador, admin | Quitar test de campaña |
| POST | /campaigns/{id}/activate | red_tech, admin | Activar campaña |
| POST | /campaigns/{id}/complete | red_lead, admin | Marcar como completada |
| GET | /campaigns/{id}/progress | autenticado | Progreso: tests por estado |
| POST | /campaigns/from-threat-actor/{actor_id} | red_tech, admin | Auto-generar campaña desde gaps |
Servicio de generación automática:
generate_campaign_from_threat_actor(db, actor_id, user):
- Obtener técnicas del actor no cubiertas (via
/threat-actors/{id}/gaps) - Para cada técnica sin test validado, buscar el mejor template disponible (priorizar por severity)
- Crear test desde template
- Crear campaña con los tests ordenados por kill chain (tactic order: reconnaissance → initial_access → execution → ... → exfiltration)
- Retornar la campaña con sus tests
Validación:
- CRUD de campañas funciona
- Se pueden añadir/quitar tests con orden
- Activar campaña cambia status y notifica
- El progreso se calcula correctamente
- La generación automática desde threat actor crea una campaña coherente
- Los tests se ordenan por kill chain
T-219: UI de Campañas — Listado
Archivo a crear: frontend/src/pages/CampaignsPage.tsx
Contenido:
- Grid de cards con: nombre, tipo (badge), threat actor (si aplica), status, progreso %, nº tests
- Filtros: tipo, status, threat actor
- Botón "New Campaign" y "Generate from Threat Actor"
Ruta: /campaigns — añadir al router y sidebar.
Validación:
- El listado muestra campañas con filtros
- Cada card muestra la información correcta
- Los botones de creación funcionan
- La ruta aparece en el sidebar
T-220: UI de Campañas — Detalle con Kill Chain Timeline
Archivos a crear:
frontend/src/pages/CampaignDetailPage.tsxfrontend/src/components/CampaignTimeline.tsx
CampaignDetailPage:
- Header: nombre, descripción, status, threat actor linkado, dates
- Kill Chain Timeline: visualización horizontal mostrando los tests agrupados por fase (Initial Access → Execution → Persistence → ...)
- Cada nodo es un test con color según su estado
- Flechas de dependencia entre tests
- Click en un nodo abre el detalle del test
- Progress Panel: barra de progreso + contadores por estado
- Tests Table: tabla con todos los tests de la campaña, reordenable
Validación:
- El timeline visual renderiza correctamente los tests por fase
- El progreso se actualiza al cambiar estado de tests
- Se pueden añadir/quitar tests desde la UI
- Click en nodos navega al detalle del test
FASE 27 — Heatmap ATT&CK Avanzado (estilo Navigator)
T-221: Backend de layers para heatmap
Objetivo: Crear un sistema de "layers" (capas) que permitan visualizar la matriz ATT&CK con diferentes datos superpuestos, similar al MITRE ATT&CK Navigator.
Archivo a crear: backend/app/routers/heatmap.py
Endpoints:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| GET | /heatmap/coverage | autenticado | Capa de cobertura (status de cada técnica) |
| GET | /heatmap/threat-actor/{actor_id} | autenticado | Capa de técnicas usadas por un actor |
| GET | /heatmap/detection-rules | autenticado | Capa de cobertura de reglas de detección |
| GET | /heatmap/campaign/{campaign_id} | autenticado | Capa de progreso de una campaña |
| GET | /heatmap/export-navigator | autenticado | Exportar como JSON del ATT&CK Navigator |
Formato de respuesta:
Todas las capas retornan el mismo formato base, compatible con ATT&CK Navigator:
{
"name": "Aegis Coverage",
"versions": {"attack": "15", "navigator": "5.0", "layer": "4.5"},
"domain": "enterprise-attack",
"description": "Coverage layer generated by Aegis",
"filters": {
"platforms": ["windows", "linux", "macos"]
},
"gradient": {
"colors": ["#ff6666", "#ffff66", "#66ff66"],
"minValue": 0,
"maxValue": 100
},
"techniques": [
{
"techniqueID": "T1059.001",
"tactic": "execution",
"color": "#00ff00",
"score": 100,
"comment": "Validated - 3 tests passed, 5 detection rules active",
"enabled": true,
"metadata": [
{"name": "tests_count", "value": "3"},
{"name": "detection_rules", "value": "5"},
{"name": "last_validated", "value": "2026-01-15"}
]
}
]
}
Lógica por endpoint:
/heatmap/coverage: score basado enstatus_global(validated=100, partial=60, in_progress=30, not_covered=10, not_evaluated=0). Color derivado del score./heatmap/threat-actor/{id}: técnicas del actor con color de cobertura, técnicas no del actor conenabled=false./heatmap/detection-rules: score basado en ratio de reglas de detección disponibles vs evaluadas para cada técnica./heatmap/campaign/{id}: solo técnicas de la campaña, color basado en estado del test asociado./heatmap/export-navigator: acepta query paramlayer(coverage, threat-actor, detection-rules, campaign) ylayer_id(actor_id o campaign_id si aplica). Retorna fichero JSON descargable con headerContent-Disposition: attachment.
Query params comunes para filtrar todas las capas:
platforms: filtrar por plataforma (windows, linux, macos)tactics: filtrar por tácticamin_score: score mínimo para incluir
Validación:
/heatmap/coverageretorna datos para todas las técnicas con scores correctos/heatmap/threat-actor/{id}resalta solo las técnicas del actor/heatmap/detection-rulescalcula correctamente la cobertura de reglas/heatmap/campaign/{id}muestra solo técnicas de la campaña/heatmap/export-navigatorgenera un JSON descargable- El JSON exportado se puede importar en ATT&CK Navigator real (verificar manualmente en https://mitre-attack.github.io/attack-navigator/)
- Los filtros de platform y tactic funcionan en todas las capas
T-222: Frontend de heatmap — Componente base
Objetivo: Crear el componente de heatmap reutilizable con renderizado eficiente de la matriz ATT&CK.
Archivos a crear:
frontend/src/components/heatmap/AdvancedHeatmap.tsxfrontend/src/components/heatmap/HeatmapCell.tsxfrontend/src/components/heatmap/HeatmapTooltip.tsxfrontend/src/components/heatmap/HeatmapLegend.tsx
⚠️ Rendimiento con 3000+ técnicas: No renderizar toda la matriz al DOM. Usar virtualización:
- Opción preferida: Usar
react-window(o@tanstack/react-virtual) para virtualizar tanto filas como columnas - Las columnas son las tácticas (14 columnas en Enterprise ATT&CK — se renderizan todas)
- Las filas son las técnicas dentro de cada táctica (esto es lo que necesita virtualización — algunas tácticas tienen 100+ técnicas)
- Implementar un layout CSS Grid donde cada columna de táctica tiene scroll virtual independiente
Añadir dependencia: @tanstack/react-virtual o react-window a package.json
Componente AdvancedHeatmap:
Props:
interface HeatmapProps {
techniques: HeatmapTechnique[]; // datos de la capa activa
onCellClick: (techniqueId: string) => void;
colorScale: (score: number) => string; // función de color
}
Funcionalidades:
- Grid con columnas = tácticas (14 columnas de Enterprise ATT&CK)
- Dentro de cada columna, lista virtualizada de técnicas
- Cada celda muestra: mitre_id + nombre truncado, coloreada por score
- Header de columna con nombre de la táctica y conteo de técnicas
- Zoom: slider que controla el tamaño de las celdas (compact/normal/expanded)
- Scroll horizontal para ver todas las tácticas
Componente HeatmapCell:
- Renderiza una celda individual
- Color de fondo según score/status
- Borde especial si
review_required = true - Indicadores opcionales (iconos pequeños): 🔴 sin tests, ⚠️ review requerida, ✅ validado
Componente HeatmapTooltip:
Al hacer hover sobre una celda:
┌─────────────────────────────────┐
│ T1059.001 - PowerShell │
│ Status: Validated ✅ │
│ Score: 85/100 │
│ Tests: 3 validated │
│ Detection Rules: 12 available │
│ Last validated: 2026-01-15 │
│ Threat Actors: APT29, APT32 │
└─────────────────────────────────┘
Componente HeatmapLegend:
- Muestra gradiente de colores con labels
- Se adapta según la capa activa
- Para coverage: rojo → amarillo → verde
- Para threat actor: gris (no aplica) → rojo (no cubierto) → verde (cubierto)
Validación:
- El heatmap renderiza todas las técnicas agrupadas por táctica
- Los colores corresponden al status/score correcto
- Los tooltips muestran información completa
- Zoom in/out cambia el tamaño de las celdas
- Scroll horizontal funciona para ver todas las tácticas
- Con 3000+ técnicas no hay lag visible (virtualización funciona)
- Click en celda ejecuta el callback correctamente
T-223: Frontend de heatmap — Selector de capas, filtros y export
Archivos a crear/modificar:
frontend/src/components/heatmap/HeatmapLayerSelector.tsxfrontend/src/components/heatmap/HeatmapFilters.tsxfrontend/src/pages/MatrixPage.tsx— rediseñar la página existente de técnicas
Componente HeatmapLayerSelector:
- Radio buttons o tabs para seleccionar la capa activa:
- 🛡️ Coverage (default)
- 👤 Threat Actor (al seleccionar, aparece un dropdown para elegir el actor)
- 🔍 Detection Rules
- 📋 Campaign (al seleccionar, aparece un dropdown para elegir la campaña)
- Al cambiar de capa, se hace fetch al endpoint correspondiente de
/heatmap/
Componente HeatmapFilters:
- Filtros inline (horizontal, encima de la matriz):
- Plataforma: checkboxes (Windows, Linux, macOS)
- Táctica: multi-select
- Status: multi-select
- Score mínimo: slider (0-100)
Página MatrixPage rediseñada:
Layout:
┌──────────────────────────────────────────────────────────┐
│ [Layer Selector] [Filters] [Export ▼] [Zoom ⊕⊖] │
├──────────────────────────────────────────────────────────┤
│ │
│ AdvancedHeatmap │
│ ┌──────┬──────┬──────┬──────┬───────────────────────┐ │
│ │Recon │Init │Exec │Pers │ ...más tácticas │ │
│ │ │Access│ │ │ │ │
│ │ T1595│ T1190│ T1059│ T1547│ │ │
│ │ T1592│ T1133│ T1203│ T1053│ │ │
│ │ ... │ ... │ ... │ ... │ │ │
│ └──────┴──────┴──────┴──────┴───────────────────────┘ │
│ │
├──────────────────────────────────────────────────────────┤
│ HeatmapLegend │
└──────────────────────────────────────────────────────────┘
Botón Export:
Dropdown con opciones:
- "Export Navigator JSON" → descarga el fichero JSON
- "Copy Navigator URL" → copia URL que abre el layer en ATT&CK Navigator web
Validación:
- Seleccionar capas diferentes cambia la visualización
- Seleccionar "Threat Actor" muestra dropdown de actores y cambia el heatmap
- Los filtros reducen las técnicas mostradas en tiempo real
- Export genera un JSON descargable
- El JSON exportado se puede importar en ATT&CK Navigator real
- La leyenda se actualiza según la capa activa
- El zoom funciona con el slider
FASE 28 — Scoring y Métricas Avanzadas
T-224: Sistema de scoring de cobertura
Objetivo: Implementar un sistema de puntuación granular de 0-100 por técnica, táctica, actor y organización.
Archivos a crear:
backend/app/services/scoring_service.pybackend/app/models/scoring_config.py(opcional, para pesos en BD)
⚠️ Pesos configurables — NO hardcodear:
Los pesos del scoring deben ser configurables. Dos opciones:
Opción A — Variables de entorno / config.py (más simple):
# config.py
class Settings(BaseSettings):
# ... existentes ...
SCORING_WEIGHT_TESTS: int = 40
SCORING_WEIGHT_DETECTION_RULES: int = 20
SCORING_WEIGHT_D3FEND: int = 15
SCORING_WEIGHT_FRESHNESS: int = 15
SCORING_WEIGHT_PLATFORM_DIVERSITY: int = 10
Opción B — Tabla en BD (más flexible, permite cambiar sin restart):
class ScoringConfig(Base):
__tablename__ = "scoring_configs"
id = Column(UUID, primary_key=True, default=uuid4)
key = Column(String, unique=True, not null) # ej: "weight_tests"
value = Column(Integer, not null) # ej: 40
description = Column(String, nullable=True)
updated_at = Column(DateTime, default=utcnow)
Usar Opción A para el MVP. Migrar a Opción B si se necesita cambiar pesos frecuentemente.
Lógica de scoring por técnica:
def calculate_technique_score(technique, db) -> dict:
"""
Retorna:
{
"total_score": 75,
"breakdown": {
"tests_validated": {"score": 35, "max": 40, "detail": "3/4 tests detected"},
"detection_rules": {"score": 15, "max": 20, "detail": "8/12 rules triggered"},
"d3fend_coverage": {"score": 10, "max": 15, "detail": "2/3 countermeasures"},
"freshness": {"score": 10, "max": 15, "detail": "Last test 45 days ago"},
"platform_diversity": {"score": 5, "max": 10, "detail": "2/3 platforms covered"}
}
}
"""
weights = settings # o cargar de BD
# Tests validados con detección
validated_tests = [t for t in technique.tests if t.state == "validated"]
detected = [t for t in validated_tests if t.detection_result == "detected"]
if validated_tests:
test_score = (len(detected) / len(validated_tests)) * weights.SCORING_WEIGHT_TESTS
else:
test_score = 0
# Reglas de detección: ratio de reglas triggered vs total
# (requiere datos de TestDetectionResult)
# D3FEND: ratio de contramedidas verificadas
# (requiere datos de DefensiveTechniqueMapping)
# Frescura: tests más recientes = más puntos
# < 90 días = 100%, 90-180 = 50%, > 180 = 0%
# Diversidad de plataformas
# Contar plataformas únicas cubiertas vs totales disponibles
total = min(test_score + detection_score + d3fend_score + freshness_score + diversity_score, 100)
return {"total_score": round(total, 1), "breakdown": {...}}
Lógica de scoring por threat actor:
def calculate_actor_coverage_score(actor, db) -> dict:
"""
Promedio ponderado de scores de las técnicas del actor.
Retorna score global + desglose por técnica.
"""
techniques = [mapping.technique for mapping in actor.technique_mappings]
scores = [calculate_technique_score(t, db)["total_score"] for t in techniques]
return {
"total_score": round(sum(scores) / len(scores), 1) if scores else 0,
"techniques_count": len(techniques),
"techniques_covered": len([s for s in scores if s > 50]),
"techniques_detail": [...]
}
Lógica de scoring global:
def calculate_organization_score(db) -> dict:
"""
Score global de la organización:
{
"overall_score": 62.5,
"total_coverage": 58.0, # promedio de todas las técnicas evaluadas
"critical_coverage": 71.0, # técnicas con severity >= high
"detection_maturity": 45.0, # basado en reglas de detección
"response_readiness": 30.0, # basado en remediaciones completadas
"techniques_evaluated": 450,
"techniques_total": 650
}
"""
Endpoints:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| GET | /scores/technique/{mitre_id} | autenticado | Score detallado con breakdown |
| GET | /scores/tactic/{tactic} | autenticado | Score promedio por táctica |
| GET | /scores/threat-actor/{id} | autenticado | Score de cobertura contra actor |
| GET | /scores/organization | autenticado | Score global de la organización |
| GET | /scores/history | autenticado | Historial de scores (query: period=30d/90d/1y) |
| GET | /scores/config | admin | Ver pesos actuales |
| PATCH | /scores/config | admin | Modificar pesos |
Validación:
- El score de técnica se calcula correctamente (0-100) con breakdown detallado
- Cambiar los pesos via config cambia los scores resultantes
- El score de threat actor refleja la cobertura real (verificar con datos de demo)
- El score de organización agrega correctamente
- El historial muestra la evolución temporal con puntos de datos
GET /scores/configretorna los pesos actuales
T-225: Métricas operativas (MTTD, MTTR, Detection Efficacy)
Objetivo: Implementar métricas operativas del equipo de seguridad.
Archivo a crear: backend/app/services/operational_metrics_service.py
Métricas a calcular:
-
MTTD (Mean Time to Detect):
- Calcular para cada test validado: tiempo entre que el test entra en
red_executingy entra enblue_evaluating - Usar timestamps del audit_log para extraer las fechas de cada transición
- Retornar media, mediana, min, max
- Calcular para cada test validado: tiempo entre que el test entra en
-
MTTR (Mean Time to Respond/Remediate):
- Para tests con
remediation_status = completed: tiempo entredetection_resultseteado yremediation_status = completed - Solo calculable para tests que tienen remediación documentada
- Retornar media, mediana, min, max
- Para tests con
-
Detection Efficacy:
detected_count / total_validated_tests * 100- Desglosar: detected, partially_detected, not_detected
-
Alert Fidelity:
- Ratio de
TestDetectionResult.triggered = Truevs total evaluados - Solo calculable si hay datos de evaluación de reglas
- Ratio de
-
Coverage Velocity:
- Contar técnicas que cambiaron a
validatedopartialpor semana - Query:
GROUP BY date_trunc('week', last_review_date) ORDER BY week
- Contar técnicas que cambiaron a
-
Validation Throughput:
- Tests que pasaron a
validatedorejectedpor semana - Query:
GROUP BY date_trunc('week', validated_at/rejected_at)
- Tests que pasaron a
-
Rejection Rate:
rejected_count / (validated_count + rejected_count) * 100- Desglosar por red_lead y blue_lead
Endpoints:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| GET | /metrics/operational | autenticado | Todas las métricas operativas actuales |
| GET | /metrics/operational/trend | autenticado | Tendencia temporal. Query: `period=30d |
| GET | /metrics/operational/by-team | autenticado | Métricas desglosadas por equipo (red vs blue) |
Formato de respuesta de /metrics/operational:
{
"mttd": {"mean_hours": 12.5, "median_hours": 8.0, "min_hours": 1.2, "max_hours": 72.0, "sample_size": 45},
"mttr": {"mean_hours": 48.0, "median_hours": 36.0, "sample_size": 20},
"detection_efficacy": {"percentage": 72.5, "detected": 29, "partially": 8, "not_detected": 3, "total": 40},
"alert_fidelity": {"percentage": 65.0, "triggered": 130, "not_triggered": 70, "total_evaluated": 200},
"coverage_velocity": {"techniques_per_week": 5.2, "trend": "improving"},
"validation_throughput": {"tests_per_week": 8.3, "trend": "stable"},
"rejection_rate": {"percentage": 15.0, "by_red_lead": 10.0, "by_blue_lead": 20.0}
}
Validación:
- MTTD se calcula correctamente a partir de timestamps del audit_log
- MTTR incluye solo tests con remediación completada
- Detection Efficacy es un porcentaje correcto con desglose
- Las tendencias muestran evolución temporal (array de puntos por semana)
- El desglose por equipo separa métricas red vs blue correctamente
- Si no hay datos suficientes para una métrica, retorna
nullen lugar de error
T-226: Dashboard ejecutivo con scores y métricas
Objetivo: Crear una vista de dashboard ejecutivo pensada para presentar a dirección.
Archivo a crear: frontend/src/pages/ExecutiveDashboardPage.tsx
Dependencia a añadir: recharts en package.json (para gráficos)
Secciones del dashboard:
-
Score Card Principal:
- Gauge circular (tipo velocímetro) con el score global de la organización
- Color: rojo (0-30), naranja (30-50), amarillo (50-70), verde (70-100)
- Texto central: score numérico
- Debajo: desglose en sub-scores (coverage, detection maturity, response readiness)
-
Trend Chart:
- Gráfico de línea (recharts
LineChart) mostrando evolución del score en los últimos 90 días - Línea principal: score global
- Líneas secundarias opcionales: sub-scores
- Tooltip con fecha y valor
- Gráfico de línea (recharts
-
Top 5 Threat Actors más relevantes:
- Cards horizontales con: nombre, bandera de país, % cobertura con barra de progreso
- Ordenados por relevancia (sectores target que coinciden con la organización)
-
Operational KPIs:
- 4 cards en fila: MTTD, MTTR, Detection Efficacy, Validation Throughput
- Cada card con: valor actual, tendencia (↑ mejorando / ↓ empeorando / → estable), sparkline mini
-
Coverage por táctica:
- Barras horizontales (recharts
BarCharthorizontal) con % de cobertura por táctica - Color de barra según porcentaje
- Barras horizontales (recharts
-
Critical Gaps:
- Tabla con las top 10 técnicas de alta severidad sin cobertura
- Columnas: MITRE ID, nombre, táctica, nº threat actors que la usan, templates disponibles
- Click en fila navega al detalle de la técnica
-
Team Performance:
- Dos columnas: Red Team | Blue Team
- Cada columna con: tests completados esta semana, tiempo medio de respuesta, rejection rate
Ruta: /executive-dashboard — accesible para roles admin, red_lead, blue_lead.
Validación:
- El dashboard carga con datos reales del backend
- El gauge del score global funciona y se colorea correctamente
- El gráfico de tendencia renderiza con datos históricos
- Los KPIs muestran valores correctos con tendencias
- Los critical gaps enlazan al detalle de la técnica
- Solo roles de liderazgo y admin pueden acceder
- El dashboard es responsive (se adapta a pantallas pequeñas)
FASE 29 — Compliance y Reportes Avanzados
T-227: Modelo de mapeo a frameworks de compliance
Objetivo: Mapear técnicas ATT&CK y controles de seguridad a frameworks de compliance.
Archivo a crear: backend/app/models/compliance.py
Campos de ComplianceFramework:
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| name | String | unique, not null (ej: "NIST 800-53") |
| version | String | nullable |
| description | Text | nullable |
| url | String | nullable |
| is_active | Boolean | default True |
| created_at | DateTime | default utcnow |
Campos de ComplianceControl:
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| framework_id | UUID | FK → compliance_frameworks.id, not null |
| control_id | String | not null (ej: "AC-2", "PR.AC-1") |
| title | String | not null |
| description | Text | nullable |
| category | String | nullable |
Campos de ComplianceControlMapping (mapeo a técnicas ATT&CK):
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| compliance_control_id | UUID | FK → compliance_controls.id, not null |
| technique_id | UUID | FK → techniques.id, not null |
Índices:
Index('ix_compliance_controls_framework', ComplianceControl.framework_id)
Index('ix_compliance_mappings_control', ComplianceControlMapping.compliance_control_id)
Index('ix_compliance_mappings_technique', ComplianceControlMapping.technique_id)
UniqueConstraint('compliance_control_id', 'technique_id', name='uq_control_technique')
⚠️ Fuente de datos para mappings NIST → ATT&CK:
Los mappings oficiales están en el repositorio del Center for Threat-Informed Defense de MITRE:
https://github.com/center-for-threat-informed-defense/attack_to_nist_mapping
El formato es un STIX bundle JSON con objetos relationship que conectan controles NIST a técnicas ATT&CK. El servicio de importación debe:
- Descargar ZIP del repositorio
- Parsear el JSON de mappings
- Crear ComplianceFramework, ComplianceControls, y ComplianceControlMappings
Servicio de importación backend/app/services/compliance_import_service.py:
def import_nist_800_53_mappings(db):
"""
1. Descargar ZIP de https://github.com/center-for-threat-informed-defense/attack_to_nist_mapping
2. Parsear el STIX bundle JSON
3. Crear framework 'NIST 800-53 Rev 5'
4. Para cada control: crear ComplianceControl
5. Para cada relationship: crear ComplianceControlMapping vinculando control → técnica
"""
Generar migración.
Validación:
- Las tablas se crean correctamente con índices
- El import de NIST 800-53 crea framework, controles y mappings
- Se puede consultar qué controles aplican a una técnica
- Se puede consultar qué técnicas cubren un control
- No se permite duplicar la misma relación control-técnica (unique constraint)
- Re-ejecutar la importación no duplica datos
T-228: Endpoints y generación de reportes de compliance
Archivo a crear: backend/app/routers/compliance.py
Endpoints:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| GET | /compliance/frameworks | autenticado | Listar frameworks disponibles |
| GET | /compliance/frameworks/{id}/status | autenticado | Estado de cada control |
| GET | /compliance/frameworks/{id}/report | autenticado | Reporte completo de compliance |
| GET | /compliance/frameworks/{id}/report/csv | autenticado | Export CSV del reporte |
| GET | /compliance/frameworks/{id}/gaps | autenticado | Controles con técnicas no cubiertas |
Lógica del status de cada control:
Para cada control del framework:
- Obtener las técnicas ATT&CK mapeadas via ComplianceControlMapping
- Para cada técnica, obtener su
status_globaly su score (de scoring_service) - Clasificar el control:
- covered (verde): todas las técnicas mapeadas tienen score >= 70
- partially_covered (amarillo): al menos una técnica tiene score >= 30 pero alguna tiene score < 70
- not_covered (rojo): todas las técnicas tienen score < 30
- not_evaluated (gris): ninguna técnica tiene tests
- Calcular score del control: promedio de scores de sus técnicas
Formato de respuesta de /frameworks/{id}/status:
{
"framework": {"id": "...", "name": "NIST 800-53 Rev 5"},
"summary": {
"total_controls": 150,
"covered": 85,
"partially_covered": 35,
"not_covered": 20,
"not_evaluated": 10,
"compliance_percentage": 56.7
},
"controls": [
{
"control_id": "AC-2",
"title": "Account Management",
"category": "Access Control",
"status": "partially_covered",
"score": 55.0,
"techniques_count": 5,
"techniques_covered": 3,
"techniques": [
{"mitre_id": "T1078", "name": "Valid Accounts", "score": 80.0, "status": "validated"},
{"mitre_id": "T1136", "name": "Create Account", "score": 30.0, "status": "in_progress"}
]
}
]
}
Formato CSV export:
control_id,title,category,status,score,techniques_total,techniques_covered,technique_ids
AC-2,Account Management,Access Control,partially_covered,55.0,5,3,"T1078,T1136,T1098,T1087,T1069"
Lógica de /gaps:
Retornar solo controles con status not_covered o partially_covered, incluyendo para cada uno:
- Las técnicas que faltan por cubrir
- Templates disponibles para esas técnicas
- Número de threat actors que usan esas técnicas (para priorizar)
Validación:
- El estado de cada control se calcula correctamente según la lógica de scoring
- El reporte incluye todos los controles del framework
- El compliance_percentage es correcto:
(covered + partially_covered*0.5) / total * 100 - El export CSV se descarga y abre correctamente en Excel
- Los gaps listan controles con técnicas no cubiertas
- Las técnicas dentro de cada control están ordenadas por score (ascendente = prioridad)
T-229: UI de Compliance
Archivos a crear:
frontend/src/pages/CompliancePage.tsxfrontend/src/components/compliance/ComplianceGauge.tsxfrontend/src/components/compliance/ControlsTable.tsx
CompliancePage:
Layout:
┌──────────────────────────────────────────────────────────────┐
│ [Framework Selector ▼ NIST 800-53] [Export CSV] [Export PDF] │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 56.7% │ │ 85 │ │ 35 │ │ 20 │ │
│ │ Overall │ │ Covered │ │ Partial │ │ Not Cov │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Filtros: [Status ▼] [Category ▼] [Search...] │ │
│ ├───────────────────────────────────────────────────────┤ │
│ │ Control │ Title │ Status │ Score │ Tech │ │
│ │ AC-2 │ Account Management │ ●Partial│ 55.0 │ 3/5 │ │
│ │ AC-3 │ Access Enforcement │ ●Covered│ 82.0 │ 4/4 │ │
│ │ AC-4 │ Information Flow │ ●NotCov │ 12.0 │ 0/3 │ │
│ │ ... │ ... │ ... │ ... │ ... │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ Expandir un control muestra sus técnicas con detalle │
└──────────────────────────────────────────────────────────────┘
Interacciones:
- Selector de framework: dropdown con frameworks disponibles
- Cards superiores: resumen con conteos y porcentaje global (gauge circular)
- Tabla de controles: expandible — click en un control muestra sus técnicas con status y score
- Filtros: status (covered/partial/not_covered), categoría, búsqueda por ID o título
- Export: CSV y JSON (el PDF se puede implementar en una fase futura)
- Click en una técnica dentro de un control navega al detalle de la técnica
Ruta: /compliance — añadir al sidebar.
Validación:
- La página muestra frameworks con métricas correctas
- El selector de framework cambia los datos
- La tabla de controles se filtra correctamente
- Expandir un control muestra sus técnicas con status
- El CSV se descarga y es correcto
- Los datos de compliance son consistentes con los scores
- La ruta aparece en el sidebar
FASE 30 — Comparación Temporal y Re-testing
T-230: Snapshots de cobertura
Objetivo: Crear snapshots periódicos del estado de cobertura para comparar en el tiempo.
Archivo a crear: backend/app/models/coverage_snapshot.py
⚠️ Evitar JSONB gigante: Almacenar el estado de 3000+ técnicas como JSONB en una sola fila generaría registros de varios MB. Con snapshots semanales automáticos, crece muy rápido. En su lugar, normalizar el almacenamiento:
Campos de CoverageSnapshot:
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| name | String | nullable (ej: "Pre-remediación Q1") |
| organization_score | Float | not null |
| total_techniques | Integer | not null |
| validated_count | Integer | not null |
| partial_count | Integer | not null |
| not_covered_count | Integer | not null |
| in_progress_count | Integer | not null |
| not_evaluated_count | Integer | not null |
| created_by | UUID | FK → users.id, nullable |
| created_at | DateTime | default utcnow |
Campos de SnapshotTechniqueState (tabla normalizada, una fila por técnica por snapshot):
| Campo | Tipo | Restricciones |
|---|---|---|
| id | UUID | PK, default uuid4 |
| snapshot_id | UUID | FK → coverage_snapshots.id, not null, ON DELETE CASCADE |
| technique_id | UUID | FK → techniques.id, not null |
| mitre_id | String | not null (desnormalizado para queries rápidos) |
| status | String | not null |
| score | Float | nullable |
Índices:
Index('ix_snapshot_technique_states_snapshot', SnapshotTechniqueState.snapshot_id)
Index('ix_snapshot_technique_states_technique', SnapshotTechniqueState.technique_id)
Generar migración.
Servicio backend/app/services/snapshot_service.py:
def create_snapshot(db, name=None, user_id=None) -> CoverageSnapshot:
"""
1. Obtener todas las técnicas con su status y score
2. Crear CoverageSnapshot con conteos agregados
3. Crear SnapshotTechniqueState para cada técnica
4. Retornar el snapshot
"""
def compare_snapshots(db, snapshot_a_id, snapshot_b_id) -> dict:
"""
Retorna:
{
"snapshot_a": {...},
"snapshot_b": {...},
"score_delta": +5.2,
"improved": [{"mitre_id": "T1059", "old_status": "not_covered", "new_status": "validated", "old_score": 0, "new_score": 85}],
"worsened": [...],
"unchanged_count": 580,
"summary": {"improved_count": 12, "worsened_count": 3, "new_count": 5}
}
"""
def cleanup_old_snapshots(db, keep_last=52):
"""Mantener solo los últimos 52 snapshots (1 año de semanales). Eliminar los más antiguos."""
Job: Programar job APScheduler semanal (domingos a las 00:00) que ejecute create_snapshot(db, name="Auto-weekly"). Incluir cleanup_old_snapshots al final del job.
Endpoints:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| GET | /snapshots | autenticado | Listar snapshots (paginado) |
| POST | /snapshots | red_lead, blue_lead, admin | Crear snapshot manual con nombre |
| GET | /snapshots/{id} | autenticado | Detalle de un snapshot |
| GET | /snapshots/compare | autenticado | Comparar dos: ?a={id}&b={id} |
| DELETE | /snapshots/{id} | admin | Eliminar snapshot |
Validación:
- Crear snapshot captura el estado actual correctamente (conteos + detalle por técnica)
- Comparar dos snapshots muestra técnicas que mejoraron, empeoraron y sin cambio
- El job semanal crea snapshots automáticamente
cleanup_old_snapshotselimina snapshots antiguos respetando el mínimo- El snapshot normalizado no genera registros gigantes (verificar tamaño con datos de demo)
T-231: UI de comparación temporal
Archivo a crear: frontend/src/pages/ComparisonPage.tsx
Contenido:
Layout:
┌──────────────────────────────────────────────────────────────┐
│ Snapshot A: [Dropdown ▼] Snapshot B: [Dropdown ▼] │
├──────────────────────────┬───────────────────────────────────┤
│ Score: 58.0 │ Score: 63.2 (+5.2 ↑) │
│ Validated: 120 │ Validated: 132 (+12) │
│ Partial: 85 │ Partial: 88 (+3) │
│ Not covered: 45 │ Not covered: 38 (-7) │
├──────────────────────────┴───────────────────────────────────┤
│ │
│ [Improved (12)] [Worsened (3)] [Unchanged (585)] │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ MITRE ID │ Name │ Before │ After │ Δ │ │
│ │ T1059 │ Command Line │ ●NotCov │ ●Valid │ ↑ │ │
│ │ T1078 │ Valid Accounts │ ●Partial│ ●Valid │ ↑ │ │
│ │ T1053 │ Scheduled Tasks │ ●Valid │ ●Partial│ ↓ │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ [Mini Heatmap Diff] (opcional: verde=mejoró, rojo=empeoró)│
└──────────────────────────────────────────────────────────────┘
Interacciones:
- Dos dropdowns para seleccionar snapshots (ordenados por fecha, más reciente primero)
- Cards side-by-side con scores y conteos, mostrando deltas
- Tabs para filtrar la tabla: Improved, Worsened, Unchanged
- Tabla con técnicas que cambiaron, mostrando estado antes/después
- Click en técnica navega al detalle
- Mini heatmap diff opcional (si hay menos de 200 técnicas con cambios)
Ruta: /comparison — accesible desde dashboard ejecutivo y sidebar.
Validación:
- Se pueden seleccionar dos snapshots
- La comparación muestra las diferencias correctamente
- Los deltas se calculan y muestran con flechas ↑↓
- Los tabs Improved/Worsened/Unchanged filtran la tabla
- Las métricas son correctas
T-232: Sistema de re-testing automático
Objetivo: Cuando un test se marca con remediación completada, crear automáticamente un re-test para verificar que la remediación fue efectiva.
Archivos a modificar:
backend/app/models/test.py— añadir campos de re-testbackend/app/services/test_workflow_service.py— lógica de re-test
Nuevos campos en Test:
| Campo | Tipo | Restricciones |
|---|---|---|
| retest_of | UUID | FK → tests.id, nullable |
| retest_count | Integer | default 0 |
⚠️ Prevención de loop infinito de re-tests:
Añadir constante configurable:
# config.py
MAX_RETEST_COUNT: int = 3 # máximo de retests automáticos por test original
Lógica:
def handle_remediation_completed(db, test, user):
"""
Se llama cuando remediation_status cambia a 'completed'.
1. Verificar que retest_count < MAX_RETEST_COUNT
2. Si no se alcanzó el límite:
- Crear nuevo test con mismos datos base (technique, platform, procedure, tool)
- Setear retest_of = test.id (o el test original si este ya es un retest)
- Setear retest_count = test.retest_count + 1
- Estado: draft
- Notificar al creador del test original y al red_tech
3. Si se alcanzó el límite:
- No crear retest
- Crear notificación: "Max retests reached for test X — manual review required"
- Log de auditoría
"""
original_test_id = test.retest_of or test.id
if test.retest_count >= settings.MAX_RETEST_COUNT:
create_notification(db, test.created_by, "max_retests_reached", ...)
return None
retest = Test(
technique_id=test.technique_id,
name=f"[Retest #{test.retest_count + 1}] {test.name}",
description=test.description,
platform=test.platform,
procedure_text=test.procedure_text,
tool_used=test.tool_used,
state=TestState.draft,
created_by=test.created_by,
retest_of=original_test_id,
retest_count=test.retest_count + 1,
)
db.add(retest)
db.commit()
# Notificar
return retest
Endpoints nuevos:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| GET | /tests/{id}/retest-chain | autenticado | Ver cadena de retests (original + todos los retests) |
UI: En el detalle del test, mostrar:
- Si es un retest: link al test original
- Si tiene retests: lista de retests con su estado
- Indicador visual: "Retest 2/3" con barra de progreso
Generar migración.
Validación:
- Completar remediación crea automáticamente un retest
- El retest tiene
retest_ofapuntando al test original (no al intermedio) - El retest tiene los mismos datos base que el original
- Se genera notificación del retest
- Al alcanzar
MAX_RETEST_COUNT, NO se crea retest y se notifica - La cadena de retests se puede consultar via endpoint
- En la UI se muestra la cadena de retests con links
FASE 31 — Scheduling y Automatización
T-233: Sistema de scheduling de campañas
Objetivo: Permitir programar campañas para ejecución periódica.
Archivos a modificar:
backend/app/models/campaign.py— nuevos campos de schedulingbackend/app/services/campaign_scheduler_service.py— nuevo
Nuevos campos en Campaign:
| Campo | Tipo | Restricciones |
|---|---|---|
| is_recurring | Boolean | default False |
| recurrence_pattern | String | nullable (weekly, monthly, quarterly) |
| next_run_at | DateTime | nullable |
| last_run_at | DateTime | nullable |
Generar migración.
Servicio backend/app/services/campaign_scheduler_service.py:
def check_and_run_recurring_campaigns(db):
"""
Job diario. Para cada campaña donde is_recurring=True y next_run_at <= now:
1. Clonar la campaña:
- Crear nueva campaña con mismos datos base + suffix " (Run {date})"
- Para cada CampaignTest de la campaña original:
- Crear nuevo Test con mismos datos base, state=draft
- Crear nuevo CampaignTest vinculando al nuevo test
2. Activar la nueva campaña (status=active)
3. Actualizar la campaña original: last_run_at=now, next_run_at=calcular_siguiente(recurrence_pattern)
4. Notificar a los equipos
5. Log de auditoría
"""
def calculate_next_run(current_date, pattern):
"""
weekly: +7 días
monthly: +30 días
quarterly: +90 días
"""
Job: Programar job APScheduler diario que ejecute check_and_run_recurring_campaigns.
Endpoints:
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
| PATCH | /campaigns/{id}/schedule | creador, admin | Configurar recurrencia |
| GET | /campaigns/{id}/history | autenticado | Historial de ejecuciones (campañas hijas) |
Formato de PATCH /campaigns/{id}/schedule:
{
"is_recurring": true,
"recurrence_pattern": "monthly",
"next_run_at": "2026-03-01T00:00:00Z"
}
Validación:
- Se puede configurar una campaña como recurrente con patrón y fecha
- El job diario detecta campañas que deben ejecutarse
- La clonación crea nueva campaña con tests nuevos en draft
- Los tests clonados tienen los mismos datos base pero IDs nuevos
last_run_atynext_run_atse actualizan correctamente- El historial lista todas las ejecuciones pasadas (campañas hijas)
- Las notificaciones se generan al crear nueva ejecución
T-234: UI de scheduling
Archivo a modificar: frontend/src/pages/CampaignDetailPage.tsx
Nuevas funcionalidades:
- Toggle "Recurring Campaign": switch que activa/desactiva recurrencia
- Al activar, aparece:
- Selector de frecuencia: Weekly / Monthly / Quarterly
- Date picker para "Next run at"
- Indicador de próxima ejecución: badge en el header "Next run: March 1, 2026"
- Tab "Execution History": tabla con ejecuciones pasadas
- Columnas: fecha, nombre, nº tests, progreso %, score obtenido, link
- Click en una ejecución navega al detalle de esa campaña
Validación:
- El toggle de recurrencia funciona (guarda via PATCH)
- Se puede seleccionar la frecuencia
- El date picker funciona para next_run_at
- La próxima ejecución se muestra en el header
- El historial lista ejecuciones pasadas con datos correctos
- Desactivar recurrencia limpia next_run_at
FASE 32 — Tests Automatizados V3
T-235: Tests de importación de fuentes
Archivo a crear: backend/tests/test_data_sources.py
⚠️ Nota sobre tests de importación externa: Los tests que dependen de descargar repositorios de GitHub son lentos e inestables (dependen de red). Implementar dos niveles:
- Tests unitarios (rápidos, sin red): mockear las descargas, testear solo la lógica de parsing
- Tests de integración (lentos, con red): marcados con
@pytest.mark.integration, excluidos por defecto
import pytest
class TestDataSourcesParsing:
"""Tests unitarios — sin acceso a red, usando fixtures de YAML/TOML de ejemplo"""
def test_sigma_yaml_parsing():
"""Parsear un YAML de Sigma de ejemplo y verificar extracción de campos"""
def test_lolbas_yaml_parsing():
"""Parsear un YAML de LOLBAS y verificar extracción de MitreID y commands"""
def test_caldera_yaml_parsing():
"""Parsear un YAML de CALDERA ability y verificar campos"""
def test_elastic_toml_parsing():
"""Parsear un TOML de Elastic y verificar extracción de KQL y threat mappings"""
def test_stix_threat_actor_parsing():
"""Parsear un bundle STIX de ejemplo y verificar extracción de intrusion-sets y relationships"""
def test_d3fend_api_response_parsing():
"""Parsear una respuesta mock de la API D3FEND"""
def test_no_duplicates_on_reimport():
"""Verificar que la lógica de deduplicación funciona con datos mock"""
@pytest.mark.integration
class TestDataSourcesIntegration:
"""Tests de integración — requieren acceso a red. Ejecutar con: pytest -m integration"""
def test_sigma_full_import():
"""Importar desde GitHub real y verificar volumen"""
def test_lolbas_full_import():
"""Importar LOLBAS completo"""
def test_caldera_full_import():
"""Importar CALDERA completo"""
def test_elastic_full_import():
"""Importar Elastic rules completo"""
Crear fixtures: backend/tests/fixtures/ con archivos de ejemplo:
sample_sigma_rule.ymlsample_lolbas_entry.ymlsample_caldera_ability.ymlsample_elastic_rule.tomlsample_stix_bundle.json(con 2-3 intrusion-sets y relationships)
Validación:
pytest tests/test_data_sources.py(sin flag integration) pasa rápido (<10s)pytest tests/test_data_sources.py -m integrationpasa (puede tardar minutos)- Cada parsing se verifica independientemente con fixtures locales
T-236: Tests de scoring, métricas y compliance
Archivo a crear: backend/tests/test_scoring_and_compliance.py
Tests:
class TestScoring:
def test_technique_score_all_detected():
"""Técnica con todos los tests detected → score alto"""
def test_technique_score_no_tests():
"""Técnica sin tests → score 0"""
def test_technique_score_partial_detection():
"""Técnica con detección parcial → score intermedio"""
def test_technique_score_freshness_penalty():
"""Tests > 180 días → penalización en freshness"""
def test_scoring_weights_configurable():
"""Cambiar pesos cambia el score resultante"""
def test_threat_actor_coverage_score():
"""Score de cobertura contra un actor con datos conocidos"""
def test_organization_score_aggregation():
"""Score global agrega correctamente los scores de técnicas"""
class TestOperationalMetrics:
def test_mttd_calculation():
"""MTTD se calcula desde timestamps del audit_log"""
def test_mttr_calculation():
"""MTTR incluye tiempo de remediación"""
def test_detection_efficacy():
"""Detection efficacy con datos de prueba conocidos"""
def test_metrics_with_no_data():
"""Métricas retornan null cuando no hay datos suficientes"""
class TestCompliance:
def test_control_fully_covered():
"""Control con todas las técnicas validated → covered"""
def test_control_partially_covered():
"""Control con técnicas mixtas → partially_covered"""
def test_control_not_covered():
"""Control con todas las técnicas sin tests → not_covered"""
def test_compliance_percentage():
"""Porcentaje global de compliance calculado correctamente"""
def test_compliance_gaps():
"""Gaps retorna solo controles no cubiertos con sus técnicas"""
Validación:
pytest tests/test_scoring_and_compliance.pypasa todos los tests- Los cálculos son correctos con datos conocidos
- Los edge cases (sin datos, datos parciales) se manejan sin errores
T-237: Tests de campañas, snapshots y re-testing
Archivo a crear: backend/tests/test_campaigns_and_snapshots.py
Tests:
class TestCampaigns:
def test_create_campaign_with_tests():
"""CRUD básico de campaña con tests ordenados"""
def test_campaign_progress_calculation():
"""Progreso se calcula según estado de tests"""
def test_generate_from_threat_actor():
"""Generación automática de campaña desde actor cubre los gaps"""
def test_circular_dependency_prevention():
"""Intentar crear dependencia circular en campaign_tests falla"""
def test_campaign_cloning():
"""Clonación de campaña recurrente crea tests nuevos con datos correctos"""
def test_campaign_scheduling_next_run():
"""next_run_at se calcula correctamente para weekly/monthly/quarterly"""
class TestSnapshots:
def test_create_snapshot():
"""Snapshot captura estado actual correctamente"""
def test_compare_snapshots_improvements():
"""Comparación detecta técnicas que mejoraron"""
def test_compare_snapshots_regressions():
"""Comparación detecta técnicas que empeoraron"""
def test_snapshot_cleanup():
"""Cleanup mantiene solo los últimos N snapshots"""
def test_snapshot_normalized_storage():
"""Verificar que el almacenamiento normalizado funciona correctamente"""
class TestRetesting:
def test_retest_created_on_remediation():
"""Completar remediación crea retest automáticamente"""
def test_retest_points_to_original():
"""Retest de un retest apunta al test original, no al intermedio"""
def test_retest_max_limit():
"""Al alcanzar MAX_RETEST_COUNT no se crea retest"""
def test_retest_chain_query():
"""Endpoint /tests/{id}/retest-chain retorna cadena completa"""
def test_retest_has_correct_data():
"""Retest tiene mismos datos base que el original"""
Validación:
pytest tests/test_campaigns_and_snapshots.pypasa todos los tests- El flujo completo de campañas funciona
- Los snapshots y comparaciones son correctos
- El re-testing respeta el límite máximo
FASE 33 — Pulido Final V3
T-238: Actualizar navegación completa
Objetivo: Integrar todas las nuevas páginas en la navegación.
Archivos a modificar:
frontend/src/App.tsxfrontend/src/components/Sidebar.tsx
Sidebar actualizado:
📊 Dashboard
📊 Executive Dashboard (leads + admin)
🔲 ATT&CK Matrix (heatmap avanzado)
🧪 Tests
├─ All Tests
├─ My Pending Tasks
└─ Test Catalog
📋 Campaigns
👤 Threat Actors
📜 Compliance
📈 Comparison (leads + admin)
📄 Reports
⚙️ System (admin)
├─ Data Sources
├─ MITRE Sync
├─ Users
└─ Audit Log
Nuevas rutas:
/executive-dashboard → ExecutiveDashboardPage
/campaigns → CampaignsPage
/campaigns/:id → CampaignDetailPage
/threat-actors → ThreatActorsPage
/threat-actors/:id → ThreatActorDetailPage
/compliance → CompliancePage
/comparison → ComparisonPage
/system/data-sources → DataSourcesPage
Visibilidad por rol:
| Ruta | admin | red_lead | blue_lead | red_tech | blue_tech |
|---|---|---|---|---|---|
| Dashboard | ✅ | ✅ | ✅ | ✅ | ✅ |
| Executive Dashboard | ✅ | ✅ | ✅ | ❌ | ❌ |
| ATT&CK Matrix | ✅ | ✅ | ✅ | ✅ | ✅ |
| Tests (all) | ✅ | ✅ | ✅ | ✅ | ✅ |
| Test Catalog | ✅ | ✅ | ✅ | ✅ | ✅ |
| Campaigns | ✅ | ✅ | ✅ | ✅ | ✅ |
| Threat Actors | ✅ | ✅ | ✅ | ✅ | ✅ |
| Compliance | ✅ | ✅ | ✅ | ✅ | ✅ |
| Comparison | ✅ | ✅ | ✅ | ❌ | ❌ |
| Reports | ✅ | ✅ | ✅ | ✅ | ✅ |
| System / Data Sources | ✅ | ❌ | ❌ | ❌ | ❌ |
Validación:
- Todas las rutas nuevas funcionan y cargan la página correcta
- El sidebar muestra items según el rol del usuario logueado
- La navegación es consistente (breadcrumbs o back links donde aplique)
- No hay rutas rotas o 404
- Un usuario red_tech no ve Executive Dashboard ni Comparison en el sidebar
- Un admin ve todo
T-239: Optimización de rendimiento
Objetivo: Asegurar que la plataforma rinde bien con volúmenes grandes de datos (3000+ técnicas, 5000+ templates, 10000+ detection rules).
Optimizaciones backend:
- Índices adicionales — Crear migración con índices que falten tras analizar queries lentas:
# Verificar con EXPLAIN ANALYZE las queries más frecuentes
# Candidatos probables:
Index('ix_detection_rules_technique_source', DetectionRule.mitre_technique_id, DetectionRule.source)
Index('ix_snapshot_technique_states_snapshot_technique', SnapshotTechniqueState.snapshot_id, SnapshotTechniqueState.technique_id)
Index('ix_campaign_tests_campaign', CampaignTest.campaign_id)
-
Paginación cursor-based en listados grandes:
- Test templates (5000+)
- Detection rules (10000+)
- Audit logs
- Reemplazar offset-based por cursor-based donde el volumen supere 1000 registros habituales
-
Caché de métricas y scores:
- Los scores de organización y métricas operativas son costosos de calcular
- Implementar caché in-memory simple con TTL (diccionario con timestamp):
_score_cache = {}
CACHE_TTL = 300 # 5 minutos
def get_organization_score_cached(db):
now = time.time()
if "org_score" in _score_cache and now - _score_cache["org_score"]["ts"] < CACHE_TTL:
return _score_cache["org_score"]["data"]
result = calculate_organization_score(db)
_score_cache["org_score"] = {"data": result, "ts": now}
return result
- Invalidar caché cuando se valida un test o cambia un score
- Queries optimizadas:
- Usar
selectinloadpara relaciones que siempre se necesitan (test.evidences, technique.tests) - Usar
subqueryloadpara relaciones grandes - Evitar N+1 queries en endpoints de listado
- Usar
Optimizaciones frontend:
-
Virtualización de tablas grandes:
- Añadir
@tanstack/react-virtual(ya añadido en T-222 para el heatmap) - Aplicar también a: tabla de test templates, tabla de detection rules, tabla de audit logs
- Umbral: virtualizar tablas que puedan superar 100 filas
- Añadir
-
Lazy loading de páginas:
// App.tsx
const ExecutiveDashboard = React.lazy(() => import('./pages/ExecutiveDashboardPage'));
const CompliancePage = React.lazy(() => import('./pages/CompliancePage'));
// etc. para todas las páginas de V3
- Envolver en
<Suspense fallback={<LoadingSpinner />}>
-
Memoización:
React.memopara HeatmapCell (se renderiza 3000+ veces)useMemopara cálculos de colores y filtros en el heatmapuseCallbackpara handlers en componentes que se renderizan muchas veces
-
Debounce en buscadores:
- Todos los campos de búsqueda con debounce de 300ms
- Usar hook custom
useDebounce(value, delay)
Validación:
- El heatmap con 3000+ técnicas renderiza sin lag perceptible (<1s para render inicial)
- Las tablas con 5000+ filas scrollean suavemente (60fps)
- Los endpoints de listado responden en < 500ms con volúmenes grandes
- El dashboard ejecutivo carga en < 3 segundos
EXPLAIN ANALYZEde las queries principales muestra uso de índices- El caché de scores funciona (segunda petición es instantánea)
- Lazy loading funciona (verificar en Network tab que los chunks se cargan bajo demanda)
T-240: Documentación completa V3
Archivos a crear/modificar:
README.md— actualizar con todas las funcionalidades V3docs/API.md— documentar todos los endpoints nuevosdocs/ARCHITECTURE.md— nuevodocs/DATA_SOURCES.md— nuevodocs/SCORING.md— nuevo
README actualizado:
Secciones a añadir:
- Descripción completa de todas las funcionalidades V3
- Diagrama de arquitectura simplificado (texto ASCII)
- Guía de inicio rápido actualizada
- Cómo importar datos de todas las fuentes (Data Sources)
- Cómo configurar campañas y threat actors
- Cómo generar reportes de compliance
- Cómo usar el heatmap avanzado
- Cómo configurar pesos de scoring
- Variables de entorno nuevas
docs/ARCHITECTURE.md:
- Diagrama de la base de datos completa (todas las tablas y relaciones)
- Diagrama de flujo de datos entre servicios
- Descripción de cada servicio y su responsabilidad
- Diagrama del pipeline de tests (draft → validated)
- Diagrama de jobs programados (APScheduler)
docs/DATA_SOURCES.md:
Para cada fuente (Atomic Red Team, Sigma, LOLBAS, GTFOBins, CALDERA, Elastic, D3FEND, MITRE CTI):
- Descripción de la fuente
- URL del repositorio
- Formato de datos (YAML, TOML, STIX, etc.)
- Cómo se parsea y qué campos se extraen
- Frecuencia de actualización recomendada
- Volumen aproximado de datos
- Troubleshooting: qué hacer si la importación falla (rate limits, formato cambiado, etc.)
docs/SCORING.md:
- Explicación del sistema de scoring
- Descripción de cada componente del score con su peso
- Cómo modificar los pesos
- Ejemplos de cálculo
- Cómo se agregan scores por táctica, actor y organización
- Explicación de cada métrica operativa (MTTD, MTTR, etc.)
Validación:
- Un nuevo desarrollador puede entender la arquitectura leyendo ARCHITECTURE.md
- DATA_SOURCES.md cubre todas las fuentes con instrucciones claras
- SCORING.md explica el sistema de scoring con ejemplos
- El README refleja todas las funcionalidades V3
- Swagger UI en /docs muestra todos los endpoints
- Siguiendo el README desde cero se puede levantar todo el proyecto con datos importados
Resumen de Fases V3
| Fase | Tareas | Descripción |
|---|---|---|
| 21 | T-200 a T-202 | Seed de demo, modelo de fuentes y detection rules |
| 22 | T-203 a T-207 | Importación de fuentes de tests y panel de gestión |
| 23 | T-208 a T-212 | Perfiles de amenaza (Threat Actor Profiles) |
| 24 | T-213 a T-214 | MITRE D3FEND: contramedidas defensivas |
| 25 | T-215 a T-216 | Reglas de detección sugeridas por test |
| 26 | T-217 a T-220 | Campañas de tests (attack chains / kill chain) |
| 27 | T-221 a T-223 | Heatmap ATT&CK avanzado (estilo Navigator) |
| 28 | T-224 a T-226 | Scoring y métricas avanzadas (MTTD, MTTR, etc.) |
| 29 | T-227 a T-229 | Compliance y reportes (NIST, DORA, NIS2, ISO 27001) |
| 30 | T-230 a T-232 | Comparación temporal y re-testing automático |
| 31 | T-233 a T-234 | Scheduling y automatización de campañas |
| 32 | T-235 a T-237 | Tests automatizados V3 |
| 33 | T-238 a T-240 | Pulido final y documentación V3 |
Total V3: 41 tareas = 41 commits mínimo Cada tarea es autocontenida y verificable antes de hacer commit.
Resumen General del Proyecto Aegis
| Bloque | Tareas | Commits | Descripción |
|---|---|---|---|
| MVP | T-001–036 | 36 | Plataforma base funcional |
| V2 | T-100–135 | 36 | Flujo Red/Blue, templates, notificaciones |
| V3 | T-200–240 | 41 | Enterprise: fuentes, scoring, compliance |
| Total | 113 tareas | 113 commits | Plataforma completa |
Análisis Competitivo: Qué Copiamos de Cada Plataforma
De Validato
- Step-by-step remediation (V2: T-130)
- Mapeo a MITRE D3FEND (V3: T-213)
- Re-testing post-remediación (V3: T-232)
- Validación continua con campañas recurrentes (V3: T-233)
- Resultados mapeados a frameworks de compliance (V3: T-227)
De Cymulate
- Full kill chain campaigns (V3: T-217)
- Reglas de detección sugeridas por test (V3: T-215)
- Múltiples fuentes de tests — 100,000+ scenarios (V3: T-201–206)
- Daily threat updates con sync automático (V3: T-207)
- Detection heatmap avanzado (V3: T-221)
- Generación de campañas desde threat actors (V3: T-218)
De Picus Security
- Detection Rule Validation (V3: T-216)
- Catálogo masivo de tests de múltiples fuentes (V3: T-201–206)
- Filtros de threat actors por sector/región (V3: T-210)
- Emulación de grupos APT específicos (V3: T-208)
- Constructor de campañas desde APT profiles (V3: T-218)
- Multi-framework compliance: NIST, DORA, ISO (V3: T-227)
De AttackIQ
- Templates pre-construidos por escenario (V2: T-103)
- MITRE ATT&CK Navigator integration con export (V3: T-221)
- Campañas recurrentes programadas (V3: T-233)
- Purple team collaboration Red/Blue (V2: core feature)
- Detection pipeline validation (V3: T-216)
- Contextual risk prioritization con scoring (V3: T-224)
Fuentes Open-Source Únicas de Aegis
- Atomic Red Team: 1,500+ tests atómicos (V2: T-108)
- SigmaHQ: 3,000+ reglas de detección (V3: T-203)
- LOLBAS + GTFOBins: 750+ técnicas living-off-the-land (V3: T-204)
- MITRE CALDERA: 400+ abilities ejecutables (V3: T-205)
- Elastic Detection Rules: 1,000+ reglas KQL (V3: T-206)
- MITRE D3FEND: 200+ contramedidas defensivas (V3: T-213)
- MITRE CTI: 140+ threat actor profiles (V3: T-209)
Lo que Aegis NO tiene (y las plataformas enterprise sí)
Estas funcionalidades requieren infraestructura de agentes/endpoints y quedan fuera del scope de Aegis como plataforma de gestión:
- Ejecución automática de ataques en endpoints (requiere agentes instalados)
- Integración directa con SIEMs (Splunk, Elastic, QRadar) para verificar alertas en tiempo real
- Integración con EDR (CrowdStrike, SentinelOne) para verificar detección automática
- Sandbox de ejecución segura (entorno aislado para ejecutar payloads)
- API de ejecución remota (ejecutar tests automáticamente en hosts)
Aegis se posiciona como plataforma de gestión y tracking del proceso de validación, no como herramienta de ejecución automática. Los equipos ejecutan manualmente (o con sus propias herramientas) y documentan resultados en Aegis.