Files
Metamorph/tasks/spec.md

189 lines
18 KiB
Markdown
Raw Normal View History

2026-05-11 06:16:00 +02:00
---
type: spec
date: "2026-05-08"
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
revised: "2026-05-15"
2026-05-11 06:16:00 +02:00
tags: [spec, ready]
status: ready
project: Metamorph
---
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
> **2026-05-15 amendment** — added 5 blue-side review fields to the M7
> scope (cf. §4 in-scope bullet on blue saisie, §F6, §8 model) and
> reworked the per-test UX to a full-bleed tabular view with inline edit.
> All other commitments stand. Detailed delta at the bottom of §4.
2026-05-11 06:16:00 +02:00
# Metamorph — Spec
> Spec finalisée après tour de questions du 2026-05-08. §12 et §13 vides : prête pour l'exécution. Le tracking quotidien bascule sur `Templates/Project.md`.
## 1. Pitch (3 lignes max)
Plateforme web collaborative purple team : la red team saisit les tests réalisés (procédure, commande, horodatage), la blue team annote en parallèle ses preuves de détection (alertes, logs, fichiers).
À la fin de la mission, Metamorph génère un slide reveal.js synthétisant les tests par catégorie MITRE ATT&CK et leur statut de détection.
Remplace l'aller-retour Excel actuel par une UI partagée temps réel, multi-rôles, avec lien d'invitation et permissions cloisonnées.
## 2. Problème
- Le workflow actuel (Excel → mail → Excel) est fastidieux, non versionné, sans contrôle d'accès, sans cohérence d'horodatage.
- L'horodatage précis et la séparation temporelle entre tests sont critiques pour que la blue team corrèle correctement ses logs.
- Aucune traçabilité des contributions red vs blue, aucune garantie d'intégrité (red peut écraser un commentaire blue).
- Les purple sont récurrents : il faut pouvoir réutiliser des batteries de tests (templates) sans recopier.
## 3. Utilisateurs & cas d'usage
- **Acteurs** : Administrateurs, Red Teamers, Blue Teamers (rôles atomiques par groupe custom — voir §5 F1).
- **Scénarios principaux** :
1. **Admin** crée des tests unitaires (templates) classifiés MITRE ATT&CK et les regroupe en scénarios réutilisables.
2. **Admin** invite des utilisateurs via lien à usage unique, leur assigne un ou plusieurs groupes (perms atomiques).
3. **Red Teamer** crée une mission, l'associe à un client/cible, sélectionne des scénarios, assigne les membres.
4. **Red Teamer** exécute les tests manuellement (sur la machine cible ou via tunnel hors plateforme), saisit dans Metamorph la commande lancée, l'output et un timestamp auto-capturé (overridable).
5. **Blue Teamer** consulte la mission (visibilité whitebox dès le début), annote chaque test : niveau de détection (taxonomie configurable), commentaires markdown, fichiers de preuves (logs, captures, EVTX).
6. **Red Teamer** génère le slide de synthèse reveal.js et l'exporte en PDF.
7. **Utilisateur invité** crée son compte via le lien d'invitation, change son mot de passe, accède aux missions où il est assigné.
8. **Red Teamer** ne peut pas modifier les champs blue (perm `mission.write_blue_fields` absente) et inversement.
## 4. Périmètre
**In scope (MVP v1)**
- Auth locale JWT (access 1h / refresh 30j), Argon2id, min 8 chars.
- Lien d'invitation à usage unique (token URL, expiration 7j, hors mail).
- Bootstrap : token d'install affiché dans les logs au 1er démarrage pour créer le 1er admin via `/setup`.
- Groupes custom + permissions atomiques (familles : user/group/invitation, test_template, scenario_template, mission, mission.write_red_fields, mission.write_blue_fields). 3 groupes pré-seedés : `admin`, `redteam`, `blueteam`.
- CRUD tests unitaires (templates) avec classification MITRE Enterprise (Tactic + Technique + Sub-technique multi-tags).
- CRUD scénarios (groupements ordonnés de tests, drag-and-drop pour la position).
- CRUD missions (nom, client/cible, dates début/fin, membres red+blue assignés, description/ROE markdown, statut `draft → in_progress → completed → archived`).
- Snapshot des templates au moment de l'instanciation dans une mission (modifier un template ne touche pas les missions existantes).
- Saisie des résultats red (texte uniquement : commande, output, commentaires) avec horodatage auto au clic « Marquer exécuté » + override manuel.
- Saisie des preuves blue : multi-fichiers (PNG/JPG/PDF/TXT/LOG/JSON/CSV/EVTX/ZIP, max 25 Mo/fichier, SHA256 stocké) + commentaires markdown + niveau de détection (taxonomie custom paramétrable par admin, seed par défaut : `detected_blocked / detected_alert / logged_only / not_detected`).
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
- **Saisie côté blue — fiche de review étendue (amendement 2026-05-15)** : en plus du commentaire et du niveau de détection, la fiche de review d'un test capture les 5 champs additionnels que la blue maintenait en Excel — **`log_source`** (texte court : Firewall / NDR / Proxy / AV / EDR / …), **`siem_logs`** (texte long, extrait brut de logs collectés au SIEM), **sous-record cyber-incident** `(incident_at: timestamptz, incident_number: texte court, incident_recipient_email: email)` qui matérialise l'alerte envoyée à l'équipe SOC. Tous ces champs sont blue-side et gated par `mission.write_blue_fields`.
2026-05-11 06:16:00 +02:00
- Workflow par test instance : `pending → executed → reviewed_by_blue` + voies `skipped / blocked`.
- Visibilité mission : whitebox totale pour la blue team dès la création (pas de masquage des procédures).
- Édition concurrente : last-write-wins + indicateur « modifié par X il y a Ns » via polling léger. Conflits red/blue impossibles par construction (champs disjoints).
- Notifications in-app uniquement (badge + liste), pas de SMTP.
- Génération slide reveal.js standalone (un fichier HTML autoportant) basé sur `tasks/design.md`, avec export PDF côté client (bouton intégré). Catégorisation par défaut MITRE Tactic, regroupement custom optionnel par mission.
- i18n FR + EN avec switch utilisateur.
- Soft delete partout + bouton « purge définitive » admin.
- Export d'une mission : JSON complet (API + UI) et CSV des résultats agrégés.
- Logs JSON structurés sur stdout, niveau configurable via `LOG_LEVEL`.
- Single-tenant + isolation stricte par mission : un utilisateur non-admin ne liste que les missions où il est membre.
**Out of scope v1 — explicitement exclu**
- Tunnel C2/ligolo (binaires, orchestration, exécution distante).
- Intégration Keycloak / OIDC.
- Audit log immuable et versioning des contenus.
- 2FA (TOTP/WebAuthn).
- SMTP / envoi de mail (notifications, invitations).
- Antivirus / scan ClamAV des uploads.
- Multi-tenancy / workspaces.
- Notifications mail.
- Logos / branding personnalisable.
**Nice-to-have — backlog v2+**
- Bascule auth vers Keycloak (OIDC, SSO).
- API d'ingestion pour qu'un C2 externe pousse les résultats automatiquement (hooks d'intégration).
- Audit log détaillé + versioning par champ critique.
- 2FA TOTP self-service.
- Notifications mail optionnelles.
- Intégration des binaires tunnel fournis par l'utilisateur pour l'exécution automatisée.
- Métriques Prometheus.
## 5. Exigences fonctionnelles
- **F1** — Gestion users/groupes/invitations par admin avec permissions atomiques (familles listées en §4).
- **F2** — CRUD tests unitaires templates avec MITRE ATT&CK (Tactic+Technique+Sub-technique multi), procédure markdown/code, prérequis, résultat attendu red, détection attendue blue, niveau OPSEC (low/med/high), tags libres, IOCs attendus. **Représentation UI du picker MITRE** : matrice flat fidèle à `attack.mitre.org/#`
- **Full-bleed** : le picker s'étend sur toute la largeur du viewport (s'échappe du `max-w-page` global du layout) pour exposer un maximum de cellules sans scroll.
- **15 colonnes** equal-width via `grid-template-columns: repeat(N, minmax(7rem, 1fr))` ; scroll horizontal seulement en dernier recours sur viewport étroit (<≈1680px).
- **Wrap word-only** : `overflow-wrap: normal` + `hyphens: none` — les noms cassent uniquement sur les espaces, jamais au milieu d'un mot.
- **Headers** = nom de la tactic seul + compteur de techniques en 10px ; l'`external_id` `TA00xx` n'apparaît qu'au hover (title) et dans les chips de sélection.
- **Cellules** = nom de la technique seul (idem pour `T1xxx` au hover) ; chevron `▸ N / ▾ N` qui déplie inline les sub-techniques dans la colonne.
- **Police** sans-serif uniforme `text-xs` (12px) pour cells + headers, `10px` pour les sub-counts.
- **Click** sur une cellule = (dé)sélection ; selection multi-niveaux (tactic / technique / sub-technique) cumulative ; chips de sélection en haut avec `external_id · name` cliquables pour retirer.
2026-05-11 06:16:00 +02:00
- **F3** — CRUD scénarios = liste ordonnée (drag-and-drop) de tests unitaires.
- **F4** — CRUD missions (métadonnées §4) composées d'un ou plusieurs scénarios, snapshot des templates à l'instanciation.
- **F5** — Saisie côté red : commande lancée, output texte, commentaires markdown, statut, timestamp auto+override.
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
- **F6** — Saisie côté blue : niveau de détection (enum custom plateforme), commentaires markdown, multi-fichiers (whitelist), **source de log** (texte court, max 120 caractères, free-form en v1 — promu en taxonomie M8+), **extrait SIEM** (texte long, max 200_000 caractères côté API), **sous-record cyber-incident** dont les 3 champs sont **indépendants et tous optionnels** : `incident_at` (timestamptz, exige un offset explicite — `Z` ou `+HH:MM` ; les naïves sont rejetées en 400), `incident_number` (texte court, max 120), `incident_recipient_email` (texte avec validation RFC-shape permissive — autorise `.local` / `.corp` / `.test` pour les domaines internes). UI: **vue tabulaire pleine largeur d'écran** (échappe le `max-w-page` du layout) à raison d'**un tableau par scénario, une ligne par test**. **Colonnes** : `Test | Procédure | Exécution | Source de log | Commentaires | Logs SIEM | Cyber Incident`. Le `detection_level` est rendu **dans la cellule Commentaires** sous forme de pill colorée au-dessus du commentaire (pas de 8ᵉ colonne). **Édition inline** : double-clic sur une ligne → un *seul* row passe en édition à la fois (les autres restent en lecture, le double-clic d'une autre ligne propose de save/discard la précédente) ; les cellules deviennent des inputs gated par les perms red/blue de l'utilisateur ; **Esc** annule (revert vers le snapshot serveur), **Save** commit, **clic en dehors** prompt si dirty. La page détail d'un test (`/missions/{id}/tests/{test_id}`) reste accessible pour l'upload de preuves (dropzone + table).
2026-05-11 06:16:00 +02:00
- **F7** — Génération slide reveal.js standalone + export PDF client, groupé par MITRE Tactic (custom optionnel).
- **F8** — Notifications in-app (badge + flux) à chaque transition de statut d'un test concernant l'utilisateur.
- **F9** — Export mission : JSON complet (API + UI), CSV agrégé.
- **F10** — Soft delete + purge admin.
- **F11** — Switch i18n FR/EN par utilisateur (préférence persistée).
refactor(m4): flatten the MITRE picker into the attack.mitre.org matrix The hierarchical 3-column drill-down was hard to scan and forced a stateful walk per tag. Replaced with a flat, columns-as-tactics matrix that mirrors attack.mitre.org/# — every cell is a one-click select target, with inline sub-technique expand via a `+N` chevron. - New endpoint GET /api/v1/mitre/matrix returns the full grid (tactics → techniques → sub-techniques nested) in a single ~55 KB response, so the SPA renders the whole matrix without firing 15 parallel queries. Two pytest tests added (nested structure + auth required). - MitreTagPicker.tsx rewritten as a horizontal-scrolling matrix: - Click a tactic header → select the tactic (cyan filled). - Click a technique cell → select the technique (orange filled). - Click the `+N` chevron → expand sub-techniques inline within the column. - Click a sub-technique → select (purple filled). - Single Filter field matches on external_id or name across all kinds. - Selection chips at the top, clickable to remove. - `aria-pressed` on every clickable cell for screen readers and Playwright. - e2e test updated to walk the new flow (click cell → assert aria-pressed, expand chevron, click sub, verify chip + JSON preview, filter to T1078). - Spec §F2 + §F12 + todo.md M4 entry updated to make the matrix layout the canonical UI for MITRE tagging (so future spec-reviewer passes accept it). - testing-m4.md walkthrough rewritten for the flat picker. DoD post-refactor: make test-api → 53 passed (was 51), make e2e → 34 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 18:32:20 +02:00
- **F12** — Sync MITRE ATT&CK Enterprise : dataset STIX embarqué (seed) + job admin manuel pour re-puller depuis github.com/mitre/cti. Le picker (cf. F2) se base sur un endpoint `GET /mitre/matrix` qui retourne la grille complète (tactics → techniques → sub-techniques) en un seul appel.
2026-05-11 06:16:00 +02:00
## 6. Exigences non fonctionnelles
- **NF-perf** : UI fluide, pagination côté API au-delà de 50 éléments par liste, lazy-loading des fichiers de preuves.
- **NF-platform** : Debian x64 dernière stable, déploiement docker-compose (api Flask + Postgres + front nginx statique).
- **NF-network** : connectivité requise vers la DB Postgres (réseau interne compose). TLS terminé par un reverse proxy externe (à l'opérateur de la prod). Pas de connectivité sortante requise sauf sync MITRE manuelle.
- **NF-state** : PostgreSQL pour toutes les données structurées (volume Docker `metamorph_db`). Fichiers de preuves stockés sous `/data/evidence/<mission_id>/<test_id>/<sha256>` (volume Docker `metamorph_evidence`). Rétention indéfinie tant que non purgée.
- **NF-observability** : logs JSON sur stdout (champs : ts, level, msg, request_id, user_id, action), `LOG_LEVEL` env. Pas de métriques Prometheus en v1.
- **NF-security** : Argon2id, JWT signés HS256 (clé via env `JWT_SECRET`), CSRF non requis (Bearer token), CORS strict (origin du front uniquement), rate-limit basique sur `/auth/*` (10 req/min/IP). Permissions vérifiées côté serveur sur chaque endpoint, pas seulement côté UI.
- **NF-i18n** : tous les libellés UI passent par un fichier de traduction. Données MITRE conservées en EN (officielles).
## 7. Contraintes techniques
- **Backend** : Python 3.12+, Flask, SQLAlchemy + Alembic (migrations), psycopg2/psycopg3, pyjwt, argon2-cffi, marshmallow ou pydantic v2 pour la validation.
- **Frontend** : React 18 + Vite + TypeScript + TailwindCSS + TanStack Query + react-router. Tokens design (couleurs, typo, espacements de `tasks/design.md`) traduits en `tailwind.config.ts` + composants RTOps réutilisables.
- **Slide** : reveal.js (CDN récupéré et servi en statique par le front), génération côté client à partir des données de l'API.
- **DB** : PostgreSQL 16+.
- **Build** : Linux. Livraison docker-compose (api, db, front-static-nginx). Dockerfile multi-stage par service. Makefile pour `dev`, `build`, `up`, `migrate`, `seed-mitre`.
- **Dépendances JS** : limitées au strict nécessaire ; chaque lib pinned. Bundle Vite, pas de CDN runtime.
- **i18n** : `react-i18next` côté front, `flask-babel` côté back pour les messages d'erreur API.
- **Logs** : `python-json-logger` ou équivalent.
## 8. Entrées / sorties / données
- **Inputs** :
- UI : saisie red (texte), saisie blue (texte + uploads multipart), uploads de fichiers (validation MIME + extension).
- Seed : dataset STIX MITRE ATT&CK Enterprise au premier `up` (ou commande `flask metamorph seed-mitre`).
- **Outputs** :
- Slide reveal.js HTML standalone (un fichier `.html` autoportant, généré côté serveur ou côté client à partir des données API).
- Export JSON mission complet (sans binaires de preuves).
- Export CSV des résultats agrégés (test, mission, statut, niveau détection, timestamp).
- **Modèle de données** (entités principales — détail dans `tasks/todo.md`) :
- `users`, `groups`, `permissions`, `user_groups`, `group_permissions`, `invitations`
- `mitre_tactics`, `mitre_techniques`, `mitre_subtechniques`
- `test_templates`, `scenario_templates`, `scenario_template_tests` (jointure ordonnée)
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
- `missions`, `mission_members`, `mission_scenarios` (snapshot), `mission_tests` (snapshot + state d'exécution + annotations red `red_command/red_output/red_comment_md` + annotations blue `blue_comment_md/detection_level_id/blue_log_source/blue_siem_logs/blue_incident_at/blue_incident_number/blue_incident_recipient_email`), `mission_categories` (custom)
2026-05-11 06:16:00 +02:00
- `evidence_files` (FK `mission_test_id`, sha256, mime, size, path)
- `notifications` (in-app)
- `detection_levels` (taxonomie custom, seedée avec 4 niveaux par défaut)
- `settings` (clés plateforme, ex: `mitre_last_sync`)
## 9. Interfaces
- **UI Web** (seule interface utilisateur). Design : `tasks/design.md` strictement (palette, typo JetBrains Mono / IBM Plex Sans, cards bordées par accent, comment-style headings `// Section`).
- **API REST JSON** consommée par le front (préfixe `/api/v1`). Auth Bearer JWT. Endpoints : `/auth/*`, `/users`, `/groups`, `/invitations`, `/test-templates`, `/scenario-templates`, `/missions`, `/missions/:id/tests/:test_id`, `/missions/:id/tests/:test_id/evidence`, `/missions/:id/export.json`, `/missions/:id/export.csv`, `/missions/:id/slide.html`, `/mitre/sync`, `/notifications`, `/detection-levels`, `/settings`. Schéma OpenAPI généré (flask-smorest ou apispec).
- **CLI Flask** (admin opérations) : `flask metamorph create-admin` (fallback), `flask metamorph seed-mitre`, `flask metamorph print-install-token`, `flask metamorph purge-soft-deleted`.
## 10. Critères de succès / Definition of Done
1. Premier boot : token d'install affiché dans les logs, accès `/setup` permet de créer le 1er admin.
2. Admin crée un groupe custom et lui attribue des permissions atomiques (write_red_fields uniquement, par exemple).
3. Admin envoie un lien d'invitation, l'utilisateur s'enregistre avec succès et hérite des bons groupes.
4. Admin importe la matrice MITRE et crée un test unitaire avec Tactic+Technique+Sub-technique. *(≥ 14 tactics Enterprise — la v19 du pin actuel en ship 15.)*
2026-05-11 06:16:00 +02:00
5. Admin compose un scénario de 3 tests ordonnés via drag-and-drop.
6. Red Teamer crée une mission avec scénario, assigne 1 red + 1 blue.
7. Red Teamer marque un test « exécuté », saisit commande+output, timestamp auto capturé.
8. Blue Teamer voit la notification, annote avec niveau de détection + 2 fichiers de preuves (PDF + .evtx, < 25 Mo).
9. Red Teamer ne peut pas (HTTP 403) écrire dans les champs blue ; idem inverse.
10. Red Teamer génère le slide reveal.js, vérifie le rendu (catégorisation MITRE, accents couleur design.md), exporte en PDF côté navigateur.
11. Admin exporte la mission en JSON et CSV.
12. Admin soft-delete un test ; admin purge définitivement.
13. Switch i18n FR ↔ EN persiste entre sessions.
14. `docker compose up` depuis zéro produit un déploiement fonctionnel sur Debian x64.
15. Logs API en JSON sur stdout, lisibles avec `journalctl`/`docker logs`.
## 11. Risques & inconnues
- **Techniques**
- Génération slide reveal.js « standalone » avec données dynamiques : à valider qu'on inline correctement les données et ressources sans dépendre du back une fois exporté.
- Performances upload preuves multi-fichiers (25 Mo × N) : streaming côté Flask + limite globale par requête à fixer.
- Snapshot vs référence : bien isoler les tables `mission_tests` des `test_templates` à l'instanciation pour ne pas drift.
- **OPSEC** : faible. La plateforme est utilisée en interne avec consentement (purple team avec blue informée).
- **Inconnues levées** : tunnel/C2 reporté en v2, cloisonnement multi-tenant non requis, audit log non requis pour MVP.
## 12. Hypothèses à valider
*(vide — toutes les zones de flou ont été levées par le tour de questions du 2026-05-08)*
## 13. Questions ouvertes pour Claude
*(vide — prêt à passer en exécution)*
---
## Liens
- Project tracking : [[Projects/Metamorph]]
- Design system : `tasks/design.md`
- Plan d'exécution : `tasks/todo.md` (à créer)
- Wiki connexes :
- Troubleshooting :