feat: sprint 6 — engagement export (md/csv/pdf) #9
Reference in New Issue
Block a user
Delete Branch "sprint/6-export"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
GET /api/engagements/<id>/export?format=md|csv|pdf— clôt la boucle « remplace l'Excel partagé RT ↔ SOC » du SPEC.[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)./api/engagements/<id>/export*→ 403._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
ruff+mypyclean) — 226 sprint 1-5 baseline + 23 sprint 6 (endpoint + render + RBAC + security + filename defense-in-depth) + 4 post-code-review.typecheck+lintclean) — 121 baseline + 12 sprint 6 + 3 coverage-gap.us298 tests,us303 tests,us315 tests + 6 supportants).Comment tester en local
Scénarios :
[Export ▼]→ Markdown. Le.mdtéléchargé contient le nom de l'engagement, ses dates, et le détail de chaque simulation RT + SOC.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.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."Opération Spéciale"→ filename Content-Disposition =engagement-<id>-operation-speciale-YYYYMMDD.<ext>(NFKD strip des accents).Notes
format, pas 3 routes séparées — 1 RBAC à protéger, 1 test d'intégration RBAC._render_engagement_html(). CSS inline ≤ 30 lignes.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).mimicpersistante avec les 7 agents idle (cohérence cross-sprint à partir du sprint 7+).🤖 Generated with Claude Code
- New module backend/app/services/export.py with render_engagement_markdown, render_engagement_csv, render_engagement_pdf, _render_engagement_html helper, and _export_filename slugifier (NFKD + fallback "unnamed"). - Extend engagements_bp with GET /api/engagements/<int:eid>/export?format=md|csv|pdf, gated @role_required("admin","redteam"). Returns 400 on missing/unknown format, 404 on unknown engagement, correct Content-Type + Content-Disposition headers. - Reuses _enrich_techniques and _enrich_tactics from serializers.py; resilient to MITRE bundle not loaded (returns empty tactics, no crash). - Adds weasyprint>=60.0 to backend/requirements.txt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>Add @page { size: A4 landscape } to _CSS, reduce font-size to 11px, and set table-layout: fixed + word-break: break-word so 7 columns fit without overflow. Unit test asserts the landscape rule is present in the rendered HTML. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>