## Summary - **Engagement export** : `GET /api/engagements//export?format=md|csv|pdf` — clôt la boucle « remplace l'Excel partagé RT ↔ SOC » du SPEC. - **3 formats livrés** : Markdown (table GFM 7 colonnes), CSV (7 colonnes machine-readable, défense formula-injection), PDF (table HTML→PDF via WeasyPrint). - **Schéma fixe 7 colonnes FR** uniforme MD/CSV/PDF (décision post-review, pre-merge) : `Scénario`, `Test`, `Source de log`, `Commentaires SOC`, `Exécution` (multi-ligne sans labels — `executed_at` → `commands` → `execution_result`), `Logs remontés au SIEM`, `Cyber incident`. Champs retirés intentionnellement : status, MITRE techniques/tactics, prerequisites, id, created_at, updated_at. - **UI** : split-button dropdown `[Export ▼]` sur `EngagementDetailPage`, 3 items. Les **deux moitiés ouvrent le menu** (différence sémantique vs sprint 5 où la gauche naviguait blank — il n'y a pas de format "défaut" évident). - **RBAC SOC zero access** : admin + redteam exportent ; SOC ne voit pas le bouton (DOM-absent) et tous endpoints `/api/engagements//export*` → 403. - **Security MEDIUM fix mid-sprint** : CSV formula injection défusée par `_csv_safe()` (apostrophe-prefix sur `=`/`+`/`-`/`@`/`\t`/`\r`). Le red team aurait pu injecter une formule qui s'exécute chez le SOC à l'ouverture de l'Excel. ## Test plan - **Backend** : **257/257** pytest (`ruff` + `mypy` clean). - **Frontend** : **136/136** vitest (`typecheck` + `lint` clean). - **E2e Playwright** : **223/223** verts — baseline sprint 5 = 201, +22 sprint 6. ## Comment tester en local ```bash make build && make start # auto-podman, +50 MB d'image (deps WeasyPrint) make create-admin USER=alice PASS=changeme8 # si premier setup # Ouvrir http://127.0.0.1:5000 (IPv4 explicite si IPv6 par défaut) ``` Scénarios : 1. **Export Markdown** — login admin → engagement avec ≥ 2 simulations → header → `[Export ▼]` → Markdown. Le `.md` téléchargé contient le nom de l'engagement, ses dates, et le détail de chaque simulation RT + SOC. 2. **Export CSV** — même flow → CSV. Ouvre dans LibreOffice : 1 ligne header + N lignes simulations, commands multilines correctement échappés, colonnes RT et SOC visibles. 3. **Export PDF** — même flow → PDF. Le fichier doit s'ouvrir dans un viewer PDF avec un rendu propre (titres, sections, tables). 4. **CSV formula injection (sécurité)** — crée une simulation avec `name = "=cmd|'/c calc'!A1"`, exporte le CSV, ouvre dans Excel/LibreOffice. La cellule doit afficher le texte littéral `=cmd|'/c calc'!A1` (apostrophe forcé), pas exécuter la formule. 5. **SOC zero access** — login en SOC → engagement → bouton `Export` ABSENT du header. Test API direct : `curl -H "Authorization: Bearer " http://127.0.0.1:5000/api/engagements/1/export?format=md` → `403`. 6. **Engagement vide** — engagement avec 0 simulations → export OK (header seul ; CSV = 1 ligne header). 7. **Filename normalisé** — engagement nommé `"Opération Spéciale"` → filename Content-Disposition = `engagement--operation-speciale-YYYYMMDD.` (NFKD strip des accents). ## Notes - **Endpoint unique** avec query param `format`, pas 3 routes séparées — 1 RBAC à protéger, 1 test d'intégration RBAC. - **PDF pipeline** : WeasyPrint (Python HTML→PDF). Le PDF est généré depuis les MÊMES DONNÉES que le Markdown (pas depuis le string Markdown) via `_render_engagement_html()`. CSS inline ≤ 30 lignes. - **Dockerfile** : +6 libs minimales pour WeasyPrint (`libcairo2 libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b libfontconfig1 shared-mime-info`). `libgdk-pixbuf-2.0-0` exclu (text-only PDF, vérifié `weasyprint --info`). - **Process wins sprint 6** : SPEC.md committed en commit #1 du sprint (recurrence 4 sprints enfin tuée) ; spec-reviewer 2-pass APPROVED avant dispatch backend (0 addendum mid-implementation, comme sprint 5) ; team `mimic` persistante avec les 7 agents idle (cohérence cross-sprint à partir du sprint 7+). 🤖 Generated with [Claude Code](https://claude.com/claude-code)