Files
mimic/tasks/todo.md
Knacky 9ace9ac0d8 docs: sprint 2 wrap-up — README + CHANGELOG + lessons + plan final
- README: status bump to sprint 2, blueprints + workflow + MITRE section, test counts refreshed (131/63/68)
- CHANGELOG: sprint 2 entry under [Unreleased]; sprint 1 moved to its own [Sprint 1] section
- tasks/lessons.md: 5 lessons captured (3 frontend testing gotchas, agent-reuse via SendMessage, e2e refresh on placeholder supersession)
- tasks/todo.md: status flipped to 🟢 SPRINT COMPLET, execution sequence ticks updated with commit hashes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 11:41:23 +02:00

18 KiB

Sprint 2 — Simulations + MITRE ATT&CK

Branche : sprint/2-simulations Statut : 🟢 SPRINT COMPLET — 32 acceptance tests sprint 2 verts, code-review traité (2 MAJOR + 2 MINOR + 2 NITs fixés), PR prête Base : main (sprint 1 mergé en 7fc79cc) Objectif : livrer les simulations (CRUD + workflow Pending→In progress→Review required→Done) à l'intérieur d'un engagement, avec autocomplete MITRE ATT&CK alimenté par un bundle STIX local. C'est le cœur métier — l'app remplace enfin le fichier Excel partagé redteam/SOC.


1. User stories

US-7 — En tant que redteam, je crée une simulation dans un engagement

Pourquoi : c'est la feature centrale du sprint 2.

Critères d'acceptation

  • AC-7.1 : POST /api/engagements/<eid>/simulations {name} (admin|redteam) → 201 + simulation {id, engagement_id, name, status: "pending", ...}. name requis, non vide.
  • AC-7.2 : autres rôles (soc) → 403.
  • AC-7.3 : engagement inexistant → 404. Engagement existant mais aucune simulation → liste vide.
  • AC-7.4 : GET /api/engagements/<eid>/simulations (auth) → liste des simulations de l'engagement, ordonnée created_at desc.
  • AC-7.5 : page /engagements/:eid (EngagementDetailPage) remplace le placeholder Sprint 2 par une section "Simulations" : liste (colonnes: name, MITRE id, status badge, executed_at) + bouton "Nouvelle simulation" pour admin/redteam.
  • AC-7.6 : depuis cette liste, click sur une ligne → ouvre /engagements/:eid/simulations/:sid/edit (page d'édition role-aware, unique URL pour view+edit).

US-8 — En tant que redteam, je renseigne les détails techniques d'une simulation

Pourquoi : c'est la trace de ce que la redteam a exécuté.

Critères d'acceptation

  • AC-8.1 : PATCH /api/simulations/<sid> (admin|redteam) accepte les champs redteam : name, mitre_technique_id, mitre_technique_name, description, commands (texte multiligne, une commande par ligne), prerequisites, executed_at (ISO datetime), execution_result. Champs partiels OK.
  • AC-8.2 : règle d'auto-transition pending → in_progress. Trigger PRÉCIS : PATCH /api/simulations/<sid> par admin|redteam où le payload JSON contient au moins une clé parmi les champs redteam (name, mitre_technique_id, mitre_technique_name, description, commands, prerequisites, executed_at, execution_result) dont la valeur n'est ni null ni une string vide ni une liste vide, ET status courant == pending. La comparaison se fait sur le payload entrant — pas sur l'état final de la simulation. Un PATCH qui ne ré-envoie qu'un champ inchangé (ex: même name) déclenche quand même la transition, car c'est une action explicite "la redteam saisit". L'auto-transition ne se déclenche jamais sur un PATCH soc.
  • AC-8.3 : commands est stocké en colonne text (chaîne multiligne, une commande par ligne). Sérialisation API = texte brut tel que stocké. Le frontend affiche dans un <textarea>.
  • AC-8.4 : executed_at valide ISO 8601 ou null. Si invalide → 400 {error: "invalid executed_at"}.
  • AC-8.5 : page /engagements/:eid/simulations/:sid affiche un formulaire avec deux sections visibles ("Red Team" et "SOC"). Pour admin/redteam, les deux sections sont éditables. Validation client : name non vide.
  • AC-8.6 : autocomplete MITRE dans le champ "Technique" — voir US-10.

US-9 — En tant qu'analyste SOC, je remplis ma partie de la simulation

Pourquoi : le SOC documente la détection sans toucher au scope redteam.

Critères d'acceptation

  • AC-9.1 : PATCH /api/simulations/<sid> envoyé par un user soc n'accepte QUE les champs SOC : log_source, logs, soc_comment, incident_number. Si la requête contient un champ redteam → 403 {error: "soc cannot edit redteam fields"}.
  • AC-9.2 : un user soc ne peut PATCH une simulation que si son status est review_required ou done. Avant ça → 403 {error: "simulation not ready for SOC review"}.
  • AC-9.3 : page /engagements/:eid/simulations/:sid pour un user soc : la section "Red Team" est rendue en read-only (champs grisés) ; la section "SOC" est éditable.
  • AC-9.4 : si la simulation est en pending ou in_progress et qu'un soc visite la page, un bandeau "Simulation pas encore en revue — la redteam doit la marquer comme 'Review required' avant que vous puissiez intervenir" s'affiche, les champs SOC sont désactivés.

US-10 — En tant que redteam, j'autocomplète une technique MITRE ATT&CK

Pourquoi : éviter de taper l'id à la main, garantir la cohérence.

Critères d'acceptation

  • AC-10.1 : make update-mitre télécharge le bundle STIX 2.1 Enterprise depuis https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json et l'écrit dans backend/data/mitre/enterprise-attack.json. Le bundle est COMMITTÉ dans le repo (make build reste autosuffisant). make update-mitre reste l'unique méthode de rafraîchissement et le diff résultant est committé manuellement.
  • AC-10.2 : GET /api/mitre/techniques?q=<query> (auth, tous rôles) → liste max 20 résultats [{id, name, tactics: ["initial-access", ...]}]. Recherche full-text sur id (ex: "T1059") OU name (ex: "Command and Scripting Interpreter"), case-insensitive, ordonnée : match exact id > match préfixe id > match nom.
  • AC-10.3 : si le bundle local est absent → endpoint répond 503 {error: "mitre bundle not loaded"}. Le team-lead documente make update-mitre dans le README.
  • AC-10.4 : sous-techniques (id format T1059.001) incluses dans l'index.
  • AC-10.5 : composant frontend MitreTechniquePicker : input + dropdown des matches (debounce 200ms, navigation clavier ↑↓ + Enter, Escape ferme le dropdown), affichage T1059 — Command and Scripting Interpreter (initial-access). La sélection d'une suggestion remplit mitre_technique_id ET mitre_technique_name du form. Pas de fallback free-text : si l'utilisateur tape sans sélectionner, le champ technique reste vide en sortie de form (id et name null).

US-11 — En tant qu'utilisateur, je transitionne le workflow d'une simulation

Pourquoi : la coordination redteam ↔ soc passe par le statut.

Critères d'acceptation

  • AC-11.1 : POST /api/simulations/<sid>/transition {to: "review_required"} → 200, requiert status courant ∈ {pending, in_progress} et role ∈ {admin, redteam}. Refuse les autres transitions → 409 {error: "invalid transition"}.
  • AC-11.2 : POST /api/simulations/<sid>/transition {to: "done"} → 200, requiert status courant == review_required et role ∈ {admin, redteam, soc}. Autres transitions → 409.
  • AC-11.3 : aucune transition arrière (ex: done → pending) n'est permise. Pas de transition → pending ni → in_progress via cet endpoint (le passage à in_progress est strictement automatique cf AC-8.2).
  • AC-11.4 : sur la page d'édition simulation, deux boutons contextuels :
    • Pour admin/redteam, status ∈ {pending, in_progress} : bouton "Marquer en revue".
    • Pour admin/redteam/soc, status == review_required : bouton "Clôturer".
    • Sinon : boutons cachés.
  • AC-11.5 : après transition réussie, la query simulation et la liste sont invalidées (TanStack Query), le badge se met à jour.

US-12 — En tant qu'admin ou redteam, je supprime une simulation

Critères d'acceptation

  • AC-12.1 : DELETE /api/simulations/<sid> (admin|redteam) → 204.
  • AC-12.2 : soc → 403.
  • AC-12.3 : suppression d'engagement (cascade) supprime toutes ses simulations.
  • AC-12.4 : bouton "Supprimer" sur la page d'édition (admin/redteam uniquement), avec confirmation modal.

2. Brief technique — Backend Builder

Scope strict : backend/, docker/, Makefile (target update-mitre).

Livrables

Modèle Simulation (backend/app/models/simulation.py)

Champ Type Notes
id int PK
engagement_id int FK Engagement, CASCADE requis
name str, NOT NULL redteam-side
mitre_technique_id str, nullable ex "T1059" / "T1059.001"
mitre_technique_name str, nullable snapshot pour résilience aux maj MITRE
description text, nullable redteam-side
commands text, nullable chaîne multiligne, une commande par ligne — pas de JSON
prerequisites text, nullable redteam-side
executed_at datetime, nullable redteam-side
execution_result text, nullable redteam-side
log_source text, nullable soc-side
logs text, nullable soc-side
soc_comment text, nullable soc-side
incident_number str, nullable soc-side
status enum(pending/in_progress/review_required/done), défaut pending
created_at datetime
updated_at datetime, nullable mis à jour à chaque PATCH
created_by_id int FK User

Migration Alembic 0002_add_simulations.py — table simulations + FK indexes (engagement_id, created_by_id).

Endpoints (nouveau blueprint backend/app/api/simulations.py)

  • GET /api/engagements/<eid>/simulations — list, auth, all roles
  • POST /api/engagements/<eid>/simulations — create, admin|redteam
  • GET /api/simulations/<sid> — get, auth
  • PATCH /api/simulations/<sid> — update avec RBAC field-level (voir AC-8/9)
  • DELETE /api/simulations/<sid> — admin|redteam
  • POST /api/simulations/<sid>/transition — state machine
  • GET /api/mitre/techniques?q= — autocomplete (200 OK + array, 503 si bundle absent)

Serializer : retourne created_by={id, username} (pattern existant). commands → string brut (tel que stocké en DB, peut être null ou chaîne multiligne).

Service workflow (backend/app/services/simulation_workflow.py)

  • apply_patch(simulation, payload, user) :
    • sépare champs redteam vs soc
    • vérifie RBAC field-level
    • détecte auto-transition pending → in_progress (AC-8.2)
    • applique le patch + commit
  • transition(simulation, to_status, user) :
    • vérifie state machine (transitions autorisées)
    • vérifie RBAC role
    • met à jour status + updated_at

Service MITRE (backend/app/services/mitre.py)

  • Au boot de l'app : tente de charger backend/data/mitre/enterprise-attack.json en mémoire ; si absent ou parse error → flag mitre_loaded = False (logue warning, app démarre quand même).
  • Indexe les objets STIX type == "attack-pattern" : extract external_id (T-id), name, kill_chain_phases[].phase_name.
  • Fonction search(query, limit=20) : ranking par exact-id > prefix-id > substring-name.

Makefile : remplacer le no-op de update-mitre par :

MITRE_URL ?= https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json
update-mitre:
	@mkdir -p backend/data/mitre
	@curl -fsSL "$(MITRE_URL)" -o backend/data/mitre/enterprise-attack.json
	@echo "MITRE bundle updated"
	@if docker ps --format '{{.Names}}' | grep -q "^$(CONTAINER)$$"; then \
		echo "Restarting $(CONTAINER) to reload MITRE bundle..."; \
		docker restart $(CONTAINER); \
	fi

Dockerfile : copier backend/data/mitre/ dans l'image (présent dans le repo, donc fonctionne au premier build).

Bundle MITRE : committé dans le repo à backend/data/mitre/enterprise-attack.json. Le backend-builder l'inclut dans son premier commit via make update-mitre.

Tests pytest (backend/tests/)

  • test_simulations_crud.py : create + list + get + delete + cascade, RBAC create/delete.
  • test_simulations_patch.py : auto-transition pending→in_progress, RBAC field-level soc, blocage soc avant review_required (AC-9.2).
  • test_simulations_workflow.py : transitions valides/invalides, RBAC par transition.
  • test_mitre.py : load bundle (fixture mini), search ranking, endpoint 503 si pas chargé, sous-techniques incluses.

Tous les tests existants doivent rester verts. Lint ruff + mypy clean.

Règles

  • Pas de touche au frontend.
  • Pas d'invention de dépendances (pas besoin d'en ajouter).
  • Renvoyer le summary attendu (cf. .claude/agents/backend-builder.md).

3. Brief technique — Frontend Builder

Scope strict : frontend/ UNIQUEMENT. Interdiction de toucher e2e/.

Livrables

Types (frontend/src/api/types.ts) : ajouter Simulation, SimulationStatus, MitreTechnique, et les payloads PATCH/POST.

Client API (frontend/src/api/simulations.ts, frontend/src/api/mitre.ts)

  • listSimulations(engagementId), createSimulation(engagementId, {name}), getSimulation(id), updateSimulation(id, patch), deleteSimulation(id), transitionSimulation(id, to).
  • searchMitreTechniques(query).

Hooks TanStack Query (frontend/src/hooks/useSimulations.ts)

  • useEngagementSimulations(engagementId), useSimulation(id), mutations useCreateSimulation, useUpdateSimulation, useDeleteSimulation, useTransitionSimulation.
  • Invalidation : transition + update + delete invalident ["simulations", id] et ["engagements", eid, "simulations"].

Hook useMitre : useMitreSearch(query, enabled) (debounce géré côté composant, hook sans staleTime court — cache 5min).

Pages

  • EngagementDetailPage.tsx : remplacer le placeholder (lignes 74-81) par <SimulationList engagementId={eng.id} />. Conserver le reste.
  • SimulationFormPage.tsx (/engagements/:eid/simulations/new et /engagements/:eid/simulations/:sid/edit) :
    • Layout en deux cards : "Red Team" et "SOC".
    • Champs redteam : name, MitreTechniquePicker, description, commands (textarea, une commande par ligne, envoyé tel quel — pas de split), prerequisites, executed_at (datetime-local input), execution_result.
    • Champs SOC : log_source, logs, soc_comment, incident_number.
    • Boutons en footer : "Save", "Marquer en revue" (si AC-11.4), "Clôturer" (si AC-11.4), "Supprimer" (modal de confirmation, admin/redteam).
    • Mode création (new) : seul name requis ; après création, redirige sur /engagements/:eid/simulations/:sid/edit.

Composants (frontend/src/components/)

  • SimulationList.tsx : table tri par created_at desc, colonnes (Name, MITRE, Status badge, Executed at), bouton "Nouvelle" si admin/redteam, ligne cliquable → navigate edit page.
  • SimulationStatusBadge.tsx : variant du StatusBadge existant si possible (factoriser), 4 couleurs (pending=fog, in_progress=primary-soft, review_required=bloom-coral, done=storm-deep). Si le StatusBadge existant n'est pas factorisable proprement, créer un nouveau composant — pas d'over-engineering.
  • MitreTechniquePicker.tsx : input + dropdown, debounce 200ms (useDebouncedValue ou util inline), navigation clavier (↑/↓/Enter/Escape), affichage T1059 — Command and Scripting Interpreter (initial-access). Loading state inline.
  • ConfirmDialog.tsx : modal générique de confirmation (utilisée pour delete).

Routing (App.tsx)

  • Ajouter /engagements/:eid/simulations/new (auth, admin|redteam)
  • Ajouter /engagements/:eid/simulations/:sid/edit (auth, all roles, RBAC champs interne)

Tests Vitest (frontend/tests/)

  • SimulationList.test.tsx : loading/error/empty + bouton "Nouvelle" gated par role.
  • MitreTechniquePicker.test.tsx : autocomplete debounce, sélection met à jour, navigation clavier.
  • SimulationFormPage.test.tsx : rôle redteam → tous champs éditables ; rôle soc → champs RT disabled, soc-side enabled si status review_required, bandeau si pending.
  • SimulationStatusBadge.test.tsx : 4 variants.

Règles

  • Lit le summary du backend EN PREMIER (contrat API).
  • Pas d'invention d'endpoints. Mismatch → escalade au team-lead.
  • Réutiliser LoadingState, ErrorState, EmptyState, Toast, FormField, StatusBadge existants. NE PAS dupliquer.
  • Respect DESIGN.md (utiliser tokens Tailwind existants — pas de couleurs hardcodées).
  • Pas de CDN remote.

4. Definition of Done — Sprint 2

  • Tous les critères AC-7 → AC-12 passent.
  • pytest (existing 63 + nouveaux ~25) tous verts. ruff, mypy clean.
  • npm run typecheck, lint, test clean frontend.
  • Playwright suite (existing 36 + nouveaux ~15) verte.
  • make build + make start + make update-mitre + workflow simulation complet manuel OK.
  • Code-reviewer (Opus) sans BLOCKER ouvert.
  • SPEC.md (section Simulation enrichie si besoin), README.md (mention make update-mitre + workflow), CHANGELOG.md à jour.
  • PR ouverte sur sprint/2-simulations, récap synthétique team-lead, validation utilisateur.

5. Décisions arrêtées (utilisateur 2026-05-26)

  1. Source MITRE : https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json (default team-lead).
  2. MITRE bundle dans le repo : COMMITTÉ (backend/data/mitre/enterprise-attack.json versionné, make build autosuffisant).
  3. Commands storage : colonne text multiligne, une commande par ligne, transport tel quel.
  4. Workflow auto-transition pending→in_progress : déclenchée par toute PATCH admin/redteam touchant ≥1 champ redteam à valeur non vide (default team-lead).
  5. Page simulation : UNE page d'édition role-aware (/engagements/:eid/simulations/:sid/edit), pas de page détail séparée.
  6. Suppression cascade : delete engagement → delete simulations (default team-lead).
  7. SOC restriction status : soc ne peut PATCH que si status ∈ {review_required, done}.
  8. Sous-techniques MITRE : incluses dans l'autocomplete (T1059.001 visible) (default team-lead).

6. Plan d'exécution (séquence)

  1. User a validé les 8 décisions §5 (2026-05-26).
  2. Spec-reviewer : APPROVED WITH NOTES (4 items mineurs corrigés avant dispatch).
  3. Backend-builder : commit 006c4c2 (67 nouveaux tests, 130 passing).
  4. Frontend-builder : commit 765bb5a (41 nouveaux tests, 61 passing).
  5. Code-reviewer : 2 MAJOR + 4 MINOR + 3 NITs → 2 commits de fix (83bf60f backend, c9032a9+cf0e8a8 frontend).
  6. Test-verifier : 32/32 sprint 2 verts, commits da905cc + 54e90f7 (AC-4.9 refresh).
  7. 🟡 Team-lead : récap + PR en cours.

Branche unique : sprint/2-simulations.