Files
Metamorph/tasks/todo.md
Knacky 7dbe2dbc28 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

20 KiB
Raw Blame History

type, date, tags, status, project, spec
type date tags status project spec
todo 2026-05-08
todo
plan
in_progress Metamorph 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 ☐

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.


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_detectedmissed, 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 clonemake 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.