CSV multiline injection + Markdown stored-XSS regressions caught by
security-guidance@claude-code-plugins on the 7-column refactor.
Backend fix in 3a9d9d3 (257 pytest, ruff/mypy clean). PR #9 body
counter bumped 255 → 257.
4.0 KiB
4.0 KiB
Summary
- Engagement export :
GET /api/engagements/<id>/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 ▼]surEngagementDetailPage, 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/<id>/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+mypyclean). - Frontend : 136/136 vitest (
typecheck+lintclean). - E2e Playwright : 223/223 verts — baseline sprint 5 = 201, +22 sprint 6.
Comment tester en local
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 :
- Export Markdown — login admin → engagement avec ≥ 2 simulations → header →
[Export ▼]→ Markdown. Le.mdtéléchargé contient le nom de l'engagement, ses dates, et le détail de chaque simulation RT + SOC. - 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.
- Export PDF — même flow → PDF. Le fichier doit s'ouvrir dans un viewer PDF avec un rendu propre (titres, sections, tables).
- 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. - SOC zero access — login en SOC → engagement → bouton
ExportABSENT du header. Test API direct :curl -H "Authorization: Bearer <SOC_TOKEN>" http://127.0.0.1:5000/api/engagements/1/export?format=md→403. - Engagement vide — engagement avec 0 simulations → export OK (header seul ; CSV = 1 ligne header).
- Filename normalisé — engagement nommé
"Opération Spéciale"→ filename Content-Disposition =engagement-<id>-operation-speciale-YYYYMMDD.<ext>(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-0exclu (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
mimicpersistante avec les 7 agents idle (cohérence cross-sprint à partir du sprint 7+).
🤖 Generated with Claude Code