Files
Metamorph/tasks/todo.md
Knacky 447f15213a feat(m7): blue review fields + spec amendment + reviewer follow-ups
User feedback after the M7 ship: blue team's Excel workflow had 5 extra
fields we didn't capture. Per-test page also doesn't match their
workflow — they need a tabular view, one table per scenario.

Spec
- tasks/spec.md amended (`revised: 2026-05-15`): §4 in-scope, §F6, §8
  model bullet. §F6 now pins the column matrix, single-row-edit
  semantics, Esc-cancel, blur-confirm, and reconciles detection_level
  as a pill inside the Commentaires cell (no 8th column).
- tasks/todo.md M7 section grew an "Amendement 2026-05-15" sub-block
  tracking backend ☑ and frontend ☐.

Backend
- Migration c2a8f4b1d6e9: 5 nullable columns on mission_tests
  (blue_log_source, blue_siem_logs, blue_incident_at,
  blue_incident_number, blue_incident_recipient_email).
- _BLUE_FIELDS extended; update_mission_test_fields propagates each
  field; MissionTestDetailView + MissionTestView (the nested view in
  GET /missions/{id}) surface every annotation field, plus
  last_actor_*, updated_at, detection_level_key — O(1) batch lookup
  for detection-level keys and last-actor users keeps it scalable.
- UpdateMissionTestPayload accepts each field with length caps
  (120/200_000/120/255).

Reviewer follow-ups applied
- blue_incident_at + executed_at now reject naïve datetimes
  (_ensure_aware_datetime) — Postgres would otherwise interpret
  them in the session TZ, defeating the M7 verbatim-time contract.
- blue_incident_recipient_email goes through a permissive RFC-shape
  regex (_validate_email_shape) so internal/lab TLDs like .local
  / .corp / .test pass — Pydantic EmailStr is too strict (lessons.md
  M2 trap).
- Project-wide: switched `e.errors()` to
  `e.errors(include_context=False, include_url=False)` because the
  AfterValidator-raised ValueError lands in ctx and Flask can't
  serialize it.

Tests
- 5 new pytest cases: blue user writes the 5 new fields, red user is
  individually 403'd on each, round-trip via GET, naïve datetime
  rejected, email shape validated (.local accepted, bad shape 400).
- 138 pytest green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:45:18 +02:00

297 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
type: todo
date: "2026-05-08"
tags: [todo, plan]
status: in_progress
project: Metamorph
spec: tasks/spec.md
---
# Metamorph — Plan d'implémentation
> Découpage en 14 milestones livrables indépendamment. Chaque milestone a une **DoD** vérifiable. Cocher au fil de l'eau, documenter les écarts dans `CHANGELOG.md`, retours d'expérience dans `tasks/lessons.md`.
## Convention
- ☐ = à faire · ☑ = fait · ⚠ = bloqué (commenter) · ↻ = en cours
- Branches : `feature/m<N>-<slug>` · commits : `feat(m<N>): …` / `fix(m<N>): …`
- Chaque PR doit : passer lint/typecheck, mettre à jour `CHANGELOG.md`, mettre à jour `README.md` si surface utilisateur.
- **Chaque milestone livre un fichier `tasks/testing-m<N>.md`** (procédure manuelle + automatisée) **et au moins un spec Playwright `e2e/tests/m<N>-*.spec.ts`**.
- À la fin de chaque milestone : lancer le subagent `spec-reviewer` (HARD RULE 4 du CLAUDE.md global) avant de marquer le milestone done.
---
## M0 — Bootstrap repo & infra ☐
**But** : squelette buildable de bout en bout sans aucune feature métier.
-`backend/` (Flask 3, Python 3.12, `pyproject.toml` avec uv ou poetry, structure `app/{api,core,db,models,services,i18n}`)
-`frontend/` (Vite + React 18 + TS strict, Tailwind 3, ESLint + Prettier, alias `@/`)
- ☐ Tokens design `tasks/design.md` traduits en `frontend/tailwind.config.ts` (palette CSS vars, typo JetBrains Mono / IBM Plex Sans, radii 3/4/6/10).
- ☐ Composants UI de base : `<Card>`, `<Tag>`, `<SectionHeader>` (avec `// `), `<FlowNode>`, `<Button>` — fidèles au design.
-`docker-compose.yml` : services `api`, `db` (postgres:16-alpine), `front` (nginx servant le bundle Vite).
- ☐ Dockerfile multi-stage par service ; volumes nommés `metamorph_db`, `metamorph_evidence`.
-`Makefile` : `dev`, `build`, `up`, `down`, `migrate`, `seed-mitre`, `lint`, `test`.
- ☐ Pré-commit hook : `ruff` (back), `eslint`+`tsc --noEmit` (front).
-`README.md` minimal (run en dev, run en prod, variables d'env attendues).
-`.gitignore` : `.env`, `*.exe`, `*.dll`, `__pycache__/`, `node_modules/`, `dist/`, `data/`.
-`.env.example` documenté (`POSTGRES_*`, `JWT_SECRET`, `LOG_LEVEL`, `FRONT_ORIGIN`).
- ☐ Logs JSON structurés sur stdout (`python-json-logger`).
**DoD** : `make up` démarre les 3 conteneurs ; `curl http://localhost:${HOST_FRONT_PORT:-8080}/api/v1/health` renvoie `{ "status": "ok", "version": "..." }` (proxifié par nginx via `api:8000`) ; le front sur `:8080` affiche une page d'accueil au design RTOps ; **`make e2e` passe les 8 tests Playwright** ; rapport HTML dans `e2e/playwright-report/`. Procédure complète : `tasks/testing-m0.md`. *En prod, la TLS est terminée par un reverse proxy externe (cf. spec §6 NF-network) — la stack compose ne sert que du HTTP.*
---
## M1 — Schéma DB & migrations Alembic ☐
**But** : modèle de données complet versionné, sans logique métier.
- ☐ Configurer SQLAlchemy 2.x + Alembic.
- ☐ Tables auth/RBAC : `users`, `groups`, `permissions`, `user_groups`, `group_permissions`, `invitations`, `refresh_tokens`.
- ☐ Tables MITRE : `mitre_tactics`, `mitre_techniques`, `mitre_subtechniques` (avec `external_id`, `name`, `description`, `url`).
- ☐ Tables templates : `test_templates`, `test_template_mitre_tags` (jointure many-to-many tactic/technique/subtechnique), `scenario_templates`, `scenario_template_tests` (avec `position`).
- ☐ Tables missions : `missions`, `mission_members`, `mission_scenarios` (snapshot), `mission_tests` (snapshot + state), `mission_test_mitre_tags`, `mission_categories` (custom).
- ☐ Tables exécution : `evidence_files` (FK `mission_test_id`, `sha256`, `mime`, `size_bytes`, `storage_path`, `original_filename`).
- ☐ Tables paramétrage : `detection_levels` (clé, label_fr, label_en, color_token, position, is_default), `settings` (key/value).
- ☐ Table notifications : `notifications` (FK user, type, payload JSONB, read_at, created_at).
- ☐ Soft delete : colonne `deleted_at` partout sauf tables jointures simples ; index partiel `WHERE deleted_at IS NULL`.
- ☐ Audit minimal : `created_at`, `updated_at` partout.
- ☐ Migration initiale Alembic + commande `make migrate`.
**DoD** : `make migrate` applique le schéma sur une DB vide ; `\dt` montre toutes les tables ; les contraintes FK et les index sont en place.
---
## M2 — Auth, bootstrap, invitations ☑
**But** : un humain peut s'inscrire et se connecter.
- ☐ Hash mot de passe : `argon2-cffi` (params modérés, `time_cost=2, memory_cost=64MB`).
- ☐ JWT : `pyjwt`, HS256, claims `sub`, `iat`, `exp`, `type` (access|refresh), `jti`. Access 1h, refresh 30j.
- ☐ Stockage refresh tokens en DB (rotation à chaque usage, révocation au logout).
- ☐ Endpoints : `POST /auth/login`, `POST /auth/refresh`, `POST /auth/logout`, `GET /auth/me`, `POST /auth/change-password`.
- ☐ Bootstrap : commande `flask metamorph print-install-token` génère + persiste un token unique au 1er démarrage (si table `users` vide), écrit dans les logs au boot.
- ☐ Endpoint `POST /setup` : consomme le token d'install, crée le 1er admin (groupe `admin` seedé).
- ☐ Invitations : `POST /invitations` (admin, génère token 7j), `GET /invitations/{token}` (preview), `POST /invitations/{token}/accept` (création compte avec password choisi).
- ☐ Middleware d'auth Flask (`@require_auth`, `@require_perm("...")`).
- ☐ Rate-limit `flask-limiter` sur `/auth/*` (10/min/IP).
- ☐ Front : pages `/login`, `/setup`, `/register?token=…`, `/profile`. Stockage access en mémoire, refresh en cookie HTTPOnly Secure SameSite=Strict.
- ☐ Hook React `useAuth()` + interceptor TanStack Query (refresh auto sur 401).
- ☐ CORS strict (origin `FRONT_ORIGIN`).
**DoD** : `flask metamorph print-install-token` → /setup → création admin → login → /auth/me OK ; admin crée invitation → user s'inscrit via lien → login OK ; `/auth/refresh` renouvelle correctement.
---
## M3 — RBAC : groupes, permissions, gestion users ☑
**But** : admin peut composer des groupes custom et y assigner des users.
- ☐ Seed des permissions atomiques (familles spec §4) :
- `user.{read,create,update,delete}`, `group.{read,create,update,delete}`, `invitation.{create,revoke,read}`
- `test_template.{read,create,update,delete}`, `scenario_template.{read,create,update,delete}`
- `mission.{read,create,update,archive,delete}`, `mission.write_red_fields`, `mission.write_blue_fields`
- `detection_level.{read,update}`, `setting.{read,update}`, `mitre.sync`
- ☐ Seed des 3 groupes par défaut (`admin` = toutes, `redteam` = templates(read) + missions(read,create,update) + write_red_fields, `blueteam` = templates(read) + missions(read) + write_blue_fields).
- ☐ Endpoints CRUD `groups`, `permissions` (lecture seule), `users` (admin), `users/{id}/groups` (assign).
- ☐ Décorateur `@require_perm` qui vérifie l'union des perms via tous les groupes du user.
- ☐ Front : page Admin > Users (liste, recherche, modale d'édition des groupes), Admin > Groups (CRUD + multi-select des perms), Admin > Invitations (liste, créer, révoquer).
- ☐ UI : on n'affiche pas les actions interdites (mais le serveur reste l'arbitre).
**DoD** : un admin peut créer un groupe `pentest-2026-Q2` avec uniquement `mission.read` + `mission.write_red_fields`, l'attribuer à Bob ; Bob voit les missions auxquelles il est membre mais ne peut pas écrire dans les champs blue (HTTP 403 au niveau API).
---
## M4 — MITRE ATT&CK Enterprise ☑
**But** : le référentiel ATT&CK est interrogeable et tagué sur les tests.
- ☐ Téléchargement initial du STIX bundle Enterprise depuis `github.com/mitre/cti` (vérifier hash, pin une version).
- ☐ Parser STIX → tables `mitre_tactics` / `mitre_techniques` / `mitre_subtechniques` (extraire `external_id` ATT&CK, `name`, `description`, `url`, relations technique↔tactic).
- ☐ Commande `flask metamorph seed-mitre [--source <path|url>]`.
- ☐ Endpoint `POST /mitre/sync` (perm `mitre.sync`) qui re-pull depuis l'URL configurée (setting `mitre_source_url`).
- ☐ Persister `mitre_last_sync` dans `settings`.
- ☐ Endpoint `GET /mitre/tactics`, `/mitre/techniques?tactic=…`, `/mitre/subtechniques?technique=…` (pagination + recherche full-text simple sur `name`).
- ☐ Front : composant `<MitreTagPicker>` — matrice flat type `attack.mitre.org/#` (colonnes = tactics, cellules = techniques, chevron `+N` qui déplie les sub-techniques inline). Click = (dé)sélection, multi-niveaux cumulatif, chips en haut, recherche par `external_id` ou `name`. Alimenté par `GET /mitre/matrix` (one-shot, ~55 KB).
**DoD** : après `make seed-mitre`, `GET /mitre/tactics` retourne 14 tactics Enterprise ; le picker permet de tagger un test avec « TA0002 / T1059.001 » et l'enregistrement est persistant.
---
## M5 — Templates : tests unitaires & scénarios ☑
**But** : admin peut bâtir le catalogue réutilisable.
- ☐ Modèle `test_template` : nom, description, objectif, procédure (markdown), prérequis (markdown), résultat attendu red, détection attendue blue, niveau OPSEC (`enum low/med/high`), tags libres (array text), IOCs attendus (array text), tags MITRE (multi).
- ☐ Endpoints CRUD `/test-templates` avec validation pydantic.
- ☐ Modèle `scenario_template` : nom, description, liste ordonnée de tests (`position`).
- ☐ Endpoints CRUD `/scenario-templates`, `PUT /scenario-templates/{id}/tests` (réordonnancement).
- ☐ Front : page Admin > Tests (liste filtrable par tactic / OPSEC / tag), modale d'édition (form complet avec markdown editor — `@uiw/react-md-editor` ou équivalent léger).
- ☐ Front : page Admin > Scénarios, drag-and-drop avec `@dnd-kit/sortable`.
- ☐ Filtres : recherche full-text sur nom/desc, facettes MITRE/OPSEC/tags.
**DoD** : admin crée 5 tests + 1 scénario de 3 tests réordonnés ; recharge la page → ordre persistant ; suppression soft-delete d'un template n'efface pas les scénarios.
---
## M6 — Missions & snapshot ☑
**But** : transformer les templates en missions vivantes.
- ☐ Modèle `mission` : nom, client/cible (texte), date_start, date_end, status (`enum draft/in_progress/completed/archived`), description (markdown), `visibility_mode` figé à `whitebox` v1.
-`mission_members` : (mission_id, user_id, role_hint `red|blue`) — rôle hint informatif, l'autorisation reste portée par les permissions.
- ☐ Lors de la création/modification d'une mission, sélection de scénarios → **snapshot** : copie complète des `scenario_templates` et `test_templates` dans `mission_scenarios` / `mission_tests` (y compris tags MITRE).
-`mission_tests` ajoute : `state` (`enum pending/executed/reviewed_by_blue/skipped/blocked`), `executed_at` (nullable), `executed_at_override` (bool), `red_command`, `red_output`, `red_comment`, `blue_comment`, `detection_level_id` (nullable).
- ☐ Endpoints : `POST /missions`, `GET /missions` (filtré par perms + membership pour les non-admin), `GET /missions/{id}` (avec scénarios+tests), `PUT /missions/{id}` (métadonnées + ajout de scénarios → snapshot), `POST /missions/{id}/transition` (drift de status), `DELETE /missions/{id}` (soft).
- ☐ Front : page Missions (liste + filtres status/client/dates), création (wizard 3 étapes : meta → scénarios → membres), vue mission (header + onglets Tests / Membres / Synthèse / Export).
- ☐ Vue mission : tableau des tests avec colonnes Tactic | Test | Statut | Niveau de détection | Last update, actions selon perms.
**DoD** : red crée une mission avec 1 scénario de 3 tests, ajoute Alice (red) et Bob (blue) ; modification ultérieure d'un test_template ne change rien dans la mission (snapshot préservé).
---
## M7 — Saisie red & blue sur un test ☑ (+ amendement 2026-05-15 ↻)
**But** : exécution de la mission, le cœur du produit.
- ☑ Modale ou page dédiée `Mission > Test #N` avec deux zones distinctes (red / blue), bordures accentuées par couleur (rouge / cyan).
- ☑ Côté red : champ commande (mono), output (mono multiline), commentaire markdown, bouton « Marquer exécuté » qui set `state=executed` + `executed_at=now()` ; édition de `executed_at` derrière un toggle « override ».
- ☑ Côté blue : sélecteur `detection_level`, commentaire markdown, zone d'upload multi-fichiers (drag-and-drop).
- ☑ Upload preuves : `POST /missions/{id}/tests/{test_id}/evidence` (multipart, validation extension+MIME+taille≤25Mo, calcul SHA256, stockage `/data/evidence/<mission_id>/<test_id>/<sha256>{ext}`).
-`GET /evidence/{id}` (download, vérif perm) ; `DELETE /evidence/{id}` (soft).
- ☑ Permissions : tout endpoint d'écriture vérifie `mission.write_red_fields` ou `mission.write_blue_fields` selon le champ touché ; les deux peuvent coexister sur un même groupe (pas exclusifs en code).
- ☑ Bouton « Statut » avec choix `executed`, `reviewed_by_blue`, `skipped`, `blocked` (transitions contrôlées : pending↔skipped/blocked, executed→reviewed_by_blue).
- ☑ Indicateur « modifié par X il y a Ns » : polling `GET /missions/{id}/activity?since=…` toutes les 15 s tant que la page est active.
**DoD** : red et blue saisissent en parallèle sans conflit ; un user sans `write_blue_fields` reçoit 403 sur les champs blue ; un fichier .evtx de 24 Mo est uploadé, un de 26 Mo est rejeté ; le hash SHA256 est correct.
### Amendement 2026-05-15 — fiche de review blue étendue + vue tabulaire
- ☑ Migration `c2a8f4b1d6e9` : 5 nouvelles colonnes sur `mission_tests``blue_log_source` (varchar 120), `blue_siem_logs` (text), `blue_incident_at` (timestamptz), `blue_incident_number` (varchar 120), `blue_incident_recipient_email` (varchar 255).
- ☑ Service `mission_tests` : `_BLUE_FIELDS` étendu aux 5 nouveaux champs, `update_mission_test_fields` accepte chaque kwarg, perm gating identique (red user → 403 sur chaque champ).
- ☑ Service `missions` : `MissionTestView` (vue nested dans `GET /missions/{id}`) inclut désormais toutes les annotations red/blue + `last_actor_*` + `updated_at` + `detection_level_key`, avec batch-lookup détection-level/users pour rester O(1) en nombre de tests.
- ☑ API : `UpdateMissionTestPayload` + serializer `_serialize_test` / `_serialize_test_detail` mis à jour, validation length-cap par champ.
- ☑ Tests pytest : 3 nouveaux (`test_blue_user_writes_new_blue_review_fields`, `test_red_user_cannot_write_new_blue_review_fields`, `test_blue_review_fields_survive_round_trip_via_get`) — 136 verts.
- ☐ Frontend : vue tabulaire pleine largeur dans l'onglet **tests** du detail page, un tableau par scénario, colonnes `Test | Procédure | Exécution | Source de log | Commentaires | Logs SIEM | Cyber Incident`, double-clic = mode édition inline gated par perms. La page `/missions/<id>/tests/<test_id>` reste pour l'upload de preuves.
- ☐ Docs : CHANGELOG section dédiée, testing-m7.md mise à jour pour la matrice de colonnes + le workflow d'édition inline.
**DoD amendement** : blue user double-clique sur une ligne, saisit `log_source` + `siem_logs` + le sous-record incident, sauve ; rafraîchir la mission → tout est persisté ; red user double-clique sur la même ligne → ne peut éditer que `Exécution` (`executed_at`, `red_command`).
---
## M8 — Niveaux de détection custom ☐
**But** : la taxonomie d'icônes du slide est paramétrable.
- ☐ Seed initial : `detected_blocked` (red), `detected_alert` (orange), `logged_only` (yellow), `not_detected` (rose).
- ☐ Endpoints `/detection-levels` : list, create, update (label_fr, label_en, color_token, position, is_default).
- ☐ Garde-fou : empêcher la suppression si utilisé dans des `mission_tests` (proposer désactivation).
- ☐ Front : page Admin > Settings > Detection Levels (table + modale, picker de color_token parmi les 10 accents du design).
**DoD** : admin renomme `not_detected``missed`, ajoute `false_positive` avec accent purple ; les missions existantes affichent les nouveaux libellés ; un blueteamer voit la nouvelle option dans le sélecteur.
---
## M9 — Notifications in-app ☐
**But** : red et blue savent quand l'autre a agi.
- ☐ Service `notify(user_id, type, payload)` appelé sur transitions clés : `test_executed`, `test_reviewed_by_blue`, `evidence_added`, `mission_status_changed`.
- ☐ Endpoints `GET /notifications?unread_only=…`, `POST /notifications/{id}/read`, `POST /notifications/read-all`.
- ☐ Front : badge dans le header avec compteur, dropdown listant les 20 dernières + lien vers la mission/test.
- ☐ Polling `GET /notifications?unread_only=true` toutes les 30 s (ou WebSocket plus tard, hors scope).
**DoD** : Bob (blue) reçoit un badge « Test #4 prêt à review » 30 s max après qu'Alice (red) clique « Marquer exécuté ».
---
## M10 — Génération du slide reveal.js ☐
**But** : livrable client de fin de mission.
- ☐ Backend : endpoint `GET /missions/{id}/slide.html` qui calcule l'agrégat (tests groupés par MITRE Tactic, comptages par detection_level, plus regroupements custom si configurés).
- ☐ Côté serveur, on émet **un seul fichier HTML standalone** : reveal.js inliné (CSS + JS), tokens design.md inlinés, données JSON inlinées, **aucune ressource externe**.
- ☐ Layout : slide titre, slide « Méthodologie », une slide par Tactic avec liste des techniques/tests + icône colorée par detection_level, slide synthèse (matrice tactic × detection_level), slide annexes (preuves référencées par titre, sans binaires).
- ☐ Bouton « Export PDF » dans le slide → `print-pdf` reveal.js (`window.print()` + media query reveal).
- ☐ Front : page Mission > Synthèse avec preview iframe + bouton « Télécharger HTML ».
- ☐ Conformité design : `// ` headings en cyan, accents par detection_level, JetBrains Mono partout.
**DoD** : on télécharge `mission-X.html`, on l'ouvre offline dans Firefox/Chrome, navigation reveal OK, export PDF côté navigateur produit un PDF lisible.
---
## M11 — Exports JSON & CSV ☐
**But** : sortie des données brutes pour archivage.
-`GET /missions/{id}/export.json` : mission + scénarios + tests + niveaux de détection + métadonnées preuves (sans binaires, mais avec hash + filename).
-`GET /missions/{id}/export.csv` : une ligne par test (cols : test_name, mitre_tactic, mitre_technique, mitre_subtechnique, executed_at, status, red_command, detection_level, blue_comment_excerpt).
- ☐ Front : boutons d'export sur la page mission, headers `Content-Disposition: attachment`.
**DoD** : `curl -OJ` sur les deux endpoints donne deux fichiers cohérents et complets ; le JSON peut être réimporté dans le futur (laisser cet import en backlog).
---
## M12 — Soft delete & purge admin ☐
**But** : aucune perte de donnée par accident, ménage explicite.
- ☐ Toutes les `DELETE` du back deviennent `UPDATE deleted_at`.
- ☐ Tous les `GET` filtrent `deleted_at IS NULL` par défaut, paramètre `?include_deleted=true` réservé aux admins.
- ☐ Endpoint `POST /admin/purge` (perm admin) avec body `{entity, ids}` qui DELETE physiquement (suppression fichiers preuves incluse).
- ☐ Commande `flask metamorph purge-soft-deleted --older-than 30d` (manuelle, pas de cron auto).
- ☐ Front : page Admin > Trash (filtrée par entity), bouton « Restaurer » + bouton « Purger ».
**DoD** : suppression d'un test depuis l'UI → disparait des listes mais reste en DB ; admin peut le restaurer ; admin peut le purger définitivement, le fichier evidence associé disparait du disque.
---
## M13 — i18n FR / EN ☐
**But** : commutation de langue par utilisateur.
- ☐ Backend : `flask-babel`, deux locales `fr` / `en`. Messages d'erreur API via `gettext`. Fichier `messages.pot` extrait via `pybabel extract`.
- ☐ Frontend : `react-i18next`, namespaces par page, fichiers `frontend/src/i18n/{fr,en}/*.json`.
- ☐ Préférence user : champ `users.locale` (default `fr`), endpoint `PATCH /auth/me {locale}`, switch dans le header.
- ☐ Données MITRE conservées en EN (officielles, non traduites).
- ☐ Tous les libellés UI passent par `t('…')` — interdit le texte en dur.
**DoD** : Bob change sa langue en EN, recharge → toute l'UI en EN sauf les noms ATT&CK ; un message d'erreur API arrive aussi en EN.
---
## M14 — Polish, sécu, observabilité, doc ☐
**But** : prêt pour livraison.
- ☐ Logs JSON : `request_id`, `user_id`, `path`, `method`, `status`, `duration_ms`, `action` (libre côté service).
- ☐ Audit minimal : logger toute action sensible (`auth.login`, `mission.create`, `evidence.delete`, `admin.purge`).
- ☐ Rate-limit confirmé sur `/auth/*` et `/invitations/*`.
- ☐ Headers sécu : `Strict-Transport-Security` (si reverse proxy le pose, sinon doc), `Content-Security-Policy` strict côté front, `X-Frame-Options: DENY`, `Referrer-Policy: same-origin`.
- ☐ Validation : tailles max body globale (Flask `MAX_CONTENT_LENGTH`), schéma pydantic strict partout.
-`README.md` complet (déploiement, env, premier admin, sync MITRE, backup volumes).
-`CHANGELOG.md` à jour (Conventional changelog).
- ☐ Critères §10 de la spec : check 1 par 1 sur une démo end-to-end documentée dans `tasks/lessons.md`.
- ☐ Tests : pytest pour la logique critique (auth, RBAC, snapshot, upload, exports). Smoke E2E Playwright (non bloquant, but nice).
**DoD** : démo from-scratch sur Debian 13 — `git clone``make up` → setup admin → invite users → crée mission → exécute → annote → génère slide → export. Tous les 15 critères §10 spec validés.
---
## Backlog v2+ (rappel pour ne pas oublier)
- Bascule auth Keycloak/OIDC.
- API d'ingestion C2 externe (push automatique des résultats).
- Audit log détaillé + versioning par champ.
- 2FA TOTP self-service.
- Notifications mail.
- Intégration tunnel C2 (binaires fournis).
- Métriques Prometheus.
- Multi-tenancy / workspaces.
- Branding configurable (logos, couleurs).
---
## Hygiène de session
- Au début de chaque session : relire `tasks/lessons.md`, `CHANGELOG.md`, ce fichier.
- À la fin : mettre à jour les ☐/☑, ajouter une entrée `CHANGELOG.md`, capturer les apprentissages dans `tasks/lessons.md`.
- Pour tout doute architectural : repasser par AskUserQuestion avant d'ouvrir un éditeur.