Two follow-up tweaks per user feedback ("wrap sur les mots, agrandit le
cadre"):
- Full-bleed wrapper: the matrix breaks out of the page's max-w-page (1400px)
constraint via `margin: 0 calc(50% - 50vw)` + `width: 100vw`, mirroring the
60px page padding internally. On wide viewports the picker now uses the
ENTIRE viewport width, so column widths grow proportionally — names that
used to wrap on 3 lines now fit on 1-2.
- Word-only wrapping: replaced `break-words` (overflow-wrap: break-word,
which falls back to mid-word breaks) with `break-normal hyphens-none`
(overflow-wrap: normal + word-break: normal). Cells break only at word
boundaries; if a single word is longer than the cell it overflows
visually rather than splitting `Aut\nhentication`-style. The grid is
configured `minmax(7rem, 1fr)` so the minimum column is wide enough for
every single word in MITRE v19 names, and stretches with available space.
- Spec §F2 rewritten as a bullet contract locking in: full-bleed, 15 cols
minmax(7rem, 1fr), word-only wrap, font sans 12px / count 10px, headers/
cells show name-only with external_id on hover + chips. Future spec-reviewer
passes can grade against this.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
15 KiB
type, date, tags, status, project
| type | date | tags | status | project | ||
|---|---|---|---|---|---|---|
| spec | 2026-05-08 |
|
ready | Metamorph |
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 :
- Admin crée des tests unitaires (templates) classifiés MITRE ATT&CK et les regroupe en scénarios réutilisables.
- Admin invite des utilisateurs via lien à usage unique, leur assigne un ou plusieurs groupes (perms atomiques).
- Red Teamer crée une mission, l'associe à un client/cible, sélectionne des scénarios, assigne les membres.
- 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).
- 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).
- Red Teamer génère le slide de synthèse reveal.js et l'exporte en PDF.
- Utilisateur invité crée son compte via le lien d'invitation, change son mot de passe, accède aux missions où il est assigné.
- Red Teamer ne peut pas modifier les champs blue (perm
mission.write_blue_fieldsabsente) 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). - Workflow par test instance :
pending → executed → reviewed_by_blue+ voiesskipped / 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-pageglobal 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_idTA00xxn'apparaît qu'au hover (title) et dans les chips de sélection. - Cellules = nom de la technique seul (idem pour
T1xxxau hover) ; chevron▸ N / ▾ Nqui déplie inline les sub-techniques dans la colonne. - Police sans-serif uniforme
text-xs(12px) pour cells + headers,10pxpour 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 · namecliquables pour retirer.
- Full-bleed : le picker s'étend sur toute la largeur du viewport (s'échappe du
- 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.
- F6 — Saisie côté blue : niveau de détection (enum custom plateforme), commentaires markdown, multi-fichiers (whitelist).
- 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).
- 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/matrixqui retourne la grille complète (tactics → techniques → sub-techniques) en un seul appel.
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 Dockermetamorph_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_LEVELenv. 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 entailwind.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-i18nextcôté front,flask-babelcôté back pour les messages d'erreur API. - Logs :
python-json-loggerou é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 commandeflask metamorph seed-mitre).
- Outputs :
- Slide reveal.js HTML standalone (un fichier
.htmlautoportant, 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).
- Slide reveal.js HTML standalone (un fichier
- Modèle de données (entités principales — détail dans
tasks/todo.md) :users,groups,permissions,user_groups,group_permissions,invitationsmitre_tactics,mitre_techniques,mitre_subtechniquestest_templates,scenario_templates,scenario_template_tests(jointure ordonnée)missions,mission_members,mission_scenarios(snapshot),mission_tests(snapshot + state d'exécution),mission_categories(custom)evidence_files(FKmission_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.mdstrictement (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
- Premier boot : token d'install affiché dans les logs, accès
/setuppermet de créer le 1er admin. - Admin crée un groupe custom et lui attribue des permissions atomiques (write_red_fields uniquement, par exemple).
- Admin envoie un lien d'invitation, l'utilisateur s'enregistre avec succès et hérite des bons groupes.
- 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.)
- Admin compose un scénario de 3 tests ordonnés via drag-and-drop.
- Red Teamer crée une mission avec scénario, assigne 1 red + 1 blue.
- Red Teamer marque un test « exécuté », saisit commande+output, timestamp auto capturé.
- Blue Teamer voit la notification, annote avec niveau de détection + 2 fichiers de preuves (PDF + .evtx, < 25 Mo).
- Red Teamer ne peut pas (HTTP 403) écrire dans les champs blue ; idem inverse.
- 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.
- Admin exporte la mission en JSON et CSV.
- Admin soft-delete un test ; admin purge définitivement.
- Switch i18n FR ↔ EN persiste entre sessions.
docker compose updepuis zéro produit un déploiement fonctionnel sur Debian x64.- 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_testsdestest_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 :