Files
mimic/CHANGELOG.md
Knacky 4d9447082f docs: sprint 6 amendment — 7-column schema in CHANGELOG + PR body
Post-review user decision (2026-06-08) switched the export payload to a
fixed 7-column FR handoff schema (Scénario / Test / Source de log /
Commentaires SOC / Exécution / Logs remontés au SIEM / Cyber incident).

Logged in CHANGELOG [Unreleased] Changed section with commit refs
(SPEC fdab324, backend 7335b9f, e2e aeb4bdb) and updated PR #9 body
counters: 255 pytest (was 253), 136 vitest unchanged, 223 e2e
unchanged.
2026-06-08 19:23:02 +02:00

34 KiB
Raw Blame History

Changelog

All notable changes to Mimic are tracked here.

The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

[Unreleased]

Added — Sprint 6 (Engagement export)

Backend (253 pytest passing — 226 sprint-1-to-4 + 28 sprint 5 + 5 sprint 5 post-code-review + 23 sprint 6 + 1 CSV-injection defense-in-depth test)

  • backend/app/services/export.py (new, 302 lines) — 3 pure render functions (render_engagement_markdown, render_engagement_csv, render_engagement_pdf) + filename slugifier (_export_filename) + HTML helper for the PDF pipeline + CSV formula-injection defense helper (_csv_safe).
  • New endpoint GET /api/engagements/<int:eid>/export?format=md|csv|pdf extended on the existing engagements_bp. Decorator @role_required("admin", "redteam") (SOC → 403). 400 on missing/unknown format, 404 on unknown engagement. Returns the rendered file body with Content-Type matching the format and Content-Disposition: attachment; filename="engagement-<id>-<slug>-YYYYMMDD.<ext>".
  • Filename slugifier uses unicodedata.normalize('NFKD', ...).encode('ascii', 'ignore') to strip accents (Opérationoperation) and falls back to "unnamed" when the slug is empty after stripping.
  • Markdown rendering uses fenced code blocks with ~~~bash (tildes, not backticks) so backticks in commands don't break the fence. SOC fields are always rendered, even when blank (consistency for handoff). _creator() helper renders the username string only (not the {id, username} dict).
  • CSV rendering uses stdlib csv.writer (handles multiline / quotes / commas natively). _csv_safe() prefixes a single apostrophe to any string starting with =, +, -, @, \t, or \r — defuses Excel / LibreOffice / Google Sheets formula injection on the SOC analyst's machine when they open the exported CSV. Applied to all user-controlled string fields; ISO dates and the enum status value are exempted.
  • PDF rendering via WeasyPrint (Python HTML→PDF). The PDF is generated from the same engagement DATA as the Markdown (not from the Markdown string) via _render_engagement_html() and weasyprint.HTML(string=html).write_pdf(). CSS inline (≤ 30 lines). All user-controlled fields HTML-escaped via stdlib html.escape().
  • docker/Dockerfile python stage now installs minimal WeasyPrint deps: libcairo2 libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b libfontconfig1 shared-mime-info. libgdk-pixbuf-2.0-0 deliberately excluded (text-only PDF).
  • weasyprint>=60.0 added to backend/requirements.txt.
  • No DB schema change. No migration.

Frontend (136 vitest passing — 121 sprint-1-to-5 + 12 sprint 6 + 3 sprint 6 coverage-gap fix)

  • frontend/src/components/ExportEngagementButton.tsx (new) — split-button dropdown [Export ▼] with Download + ChevronDown lucide icons. Both halves open the dropdown (no default left-click action — different semantic from sprint 5's NewSimulationDropdown where left navigates blank), because there is no obvious default format among MD/CSV/PDF. Loading state per-item, toast on error. Click-outside + Escape close (reuses the useEffect + pointerdown + keydown pattern from NewSimulationDropdown). data-testid="export-dropdown" for e2e selection. Visual: shares btn-outline class with the neighbour Edit button.
  • frontend/src/api/exports.ts (new) — downloadEngagementExport(engagementId, format) with responseType: 'blob'. Reads Content-Disposition: attachment; filename="...", falls back to engagement-<id>.<ext> when the header is absent or malformed. Throws an Error on non-2xx (caller catches and toasts). Helper parseContentDispositionFilename().
  • frontend/src/pages/EngagementDetailPage.tsx (edited) — integrates <ExportEngagementButton engagementId={engagement.id} /> in the header next to the Edit CTA. Gated by canEditEngagements from useAuth (admin + redteam).
  • New test file frontend/tests/exports.test.ts covers the API client directly via axios-mock-adapter (the component test file mocks downloadEngagementExport entirely, so the fallback logic inside exports.ts wasn't reachable from there — new file lets the real function run for 3 dedicated tests).

Acceptance tests (Playwright, 223 passed — baseline sprint 5 = 201, +22 sprint 6)

  • 3 new spec files (one per US): us29-export-formats.spec.ts (8 tests), us30-export-rbac.spec.ts (3 tests), us31-export-robustness.spec.ts (5 tests).
  • No regression on sprints 15: full pre-sprint-6 suite still green.

Security

  • CSV formula injection (MEDIUM) flagged by security-guidance@claude-code-plugins automated review during the sprint, fixed mid-sprint (commit 57dbd14). 3 dedicated unit tests cover the apostrophe-prefix on =, @ triggers and the no-op on safe strings.
  • Defense-in-depth: a property test (test_export_filename_never_contains_quote_or_crlf) asserts the slugifier output never contains ", \r, or \n — guards against Content-Disposition header injection if someone later weakens the slug regex.

Changed

  • 2026-06-07 — SPEC.md § Export d'engagement added (between § Templates de simulations and § Stacks techniques). Committed as the first sprint commit (7aaa5cc), applying the fix-candidate from sprint 5's recurrent "SPEC.md uncommitted at sprint close" lesson. Four-sprint recurrence finally broken.
  • 2026-06-08 — Team mimic (persistent .claude/teams/mimic/config.json) instantiated with the full 7-agent project roster (backend-builder, frontend-builder, spec-reviewer, code-reviewer, design-reviewer, test-verifier, devil-advocate). Agents are spawned with an idle prompt at sprint start and woken via SendMessage per phase — flip vs the old "spawn-with-task-only" policy that hit the "team roster is flat" gotcha when respawning. Persistent across sprints from sprint 7+.
  • 2026-06-08 (post-review, pre-merge) — Export schema switched to a fixed 7-column handoff layout uniform across MD/CSV/PDF. Columns (FR headers): Scénario, Test, Source de log, Commentaires SOC, Exécution (multi-line concat without labels — executed_atcommandsexecution_result), Logs remontés au SIEM, Cyber incident. Removed from the export (intentional, handoff-focused): simulation status, MITRE techniques/tactics, prerequisites, id, created_at, updated_at. Markdown switched from narrative-per-simulation to a GFM table. PDF switched from sectioned HTML to a single <table>. SPEC fdab324, backend refactor 7335b9f, e2e adaptation aeb4bdb. Final counters: backend 255 pytest, frontend 136 vitest, e2e 223 Playwright.

[Sprint 5] — Simulation templates + instantiation + nav + dropdown (merged 2026-05-28)

Added — Sprint 5 (Simulation templates)

Backend (226 pytest passing — 193 sprint-1-to-4 + 28 sprint 5 + 5 post-code-review)

  • SimulationTemplate model (table simulation_templates) — UNIQUE constraint on name, JSON techniques + tactic_ids (default [], NOT NULL via server_default), Text fields description / commands / prerequisites, FK created_by_id to users, created_at / updated_at.
  • Alembic migration 0005_simulation_templates.py — CREATE TABLE (SQLite native, no batch); downgrade via DROP TABLE.
  • 5 new endpoints under /api/templates, all gated @role_required("admin", "redteam") (SOC → 403):
    • GET /api/templates — list, sorted name ASC, serialized with enriched techniques: [{id, name, tactics}] and tactics: [{id, name}].
    • POST /api/templates — create. name required (400 if empty), unique (409 via IntegrityError catch, no pre-check race). technique_ids / tactic_ids validated upfront — type check isinstance(list) (400 with friendly message) THEN resolved against the bundle / _TACTIC_IDS (400 with id on unknown).
    • GET /api/templates/<tid> — single, 404 on miss.
    • PATCH /api/templates/<tid> — partial update. Same validations. 409 on name conflict; no-op rename (name == current) returns 200.
    • DELETE /api/templates/<tid> — 204. No cascade to instantiated simulations (decoupling guarantee).
  • POST /api/engagements/<eid>/simulations extended with optional template_id. When provided:
    • Template loaded (404 on miss).
    • Fields copied directly onto the new Simulation ORM object (techniques, tactic_ids, description, commands, prerequisites, and name if missing from body).
    • Explicit non-call to apply_patch() / _resolve_* helpers — avoids re-hitting the MITRE bundle AND avoids triggering the auto-transition pending → in_progress. Status stays pending, engagement stays planned (no _maybe_activate_engagement call). Decorrelation: no template_id FK on Simulation, deep copy of JSON arrays.
  • New helpers in mitre.py reused / re-exposed; new serialize_template() in serializers.py mirrors serialize_simulation (minus SOC fields, status, executed_at) and uses the shared _enrich_techniques + _enrich_tactics (no duplication).
  • All migration tests (0003, 0004, 0005) now use Path(__file__).resolve().parent.parent / "migrations" / "versions" / "..." — sprint 4's hardcoded-path MAJOR is closed for the third sprint running.

Frontend (121 vitest passing — 92 sprint-1-to-4 + 26 sprint 5 + 3 post-code-review)

  • New page TemplatesListPage (/admin/templates, admin+redteam only) — table (Name / MITRE count / Created by / Updated / Actions), + New CTA with Plus icon.
  • New page TemplateFormPage (/admin/templates/new and /admin/templates/:id/edit) — single-column FormField stack (sidesteps the multi-column grid trap that broke AC-17.3 on UsersAdminPage). Includes MitreTechniquePicker + MitreMatrixModal inline (NOT MitreTechniquesField — that one auto-saves; template form needs batched save). Delete via ConfirmDialog.
  • New component TemplatePickerModal — modal listing all templates (Name / MITRE count / Created by). Empty state when useTemplates() returns []: "No templates available — Create one from the Templates page."
  • New nav link "Templates" in Layout.tsx topbar — visible to admin + redteam only, masked for SOC. Mirrors the pattern used by the "Users" link.
  • SimulationList "New" button refactored into a split-button dropdown: [+ New] [▼]. Primary half → /.../simulations/new (blank). Dropdown → "Blank" + "From template…". Open dropdown closes on click-outside or Escape (sprint 3 picker pattern). Empty-state SimulationList now also exposes the same dropdown (so users can instantiate from a template on a fresh engagement without creating a blank first).
  • dark:shadow-floating-dark consistently applied to the new dropdown and TemplatePickerModal — matches the sprint 4 shadow token model. dark:hover:bg-fog on dropdown items for contrast.
  • New types: SimulationTemplate, SimulationTemplateCreateInput, SimulationTemplatePatchInput. SimulationCreateInput extended with template_id?: number.
  • New TanStack Query hooks (useTemplates, useTemplate, useCreateTemplate, useUpdateTemplate, useDeleteTemplate) with cache invalidation on mutations.
  • API client frontend/src/api/templates.ts — 5 calls to /api/templates*. (Sprint-5 in-flight bug : initial commit 90fc5ba used /simulation-templates paths everywhere; caught immediately, fixed in 2b70011.)

Acceptance tests (Playwright, 201 passed)

  • 3 new spec files (one per US): us26-templates-crud.spec.ts (22 tests), us27-instantiate-from-template.spec.ts (14 tests), us28-templates-nav.spec.ts (8 tests).
  • Coverage gaps from code-reviewer filled: bidirectional template↔instance decorrelation, dropdown click-outside + Escape, SOC + template_id 403.
  • Sprint 2/3 spec adapts: us4-engagements.spec.ts and us7-simulation-create.spec.ts now use getByTestId('new-simulation-btn') instead of getByRole('link', /new simulation/) — the link became a split-button dropdown.
  • 1 pre-existing flaky in us3-users-admin AC-3.4 (DB contamination across runs) — predates sprint 5, unrelated.

Changed

  • 2026-05-28 — SPEC.md § Templates de simulations added (between § Fonctionnement and § Authentification & rôles). Spells out the decoupling rule and the SOC-zero-access RBAC.
  • 2026-05-28 — POST /api/engagements/<eid>/simulations API contract: name is now optional when template_id is provided (falls back to template.name).

[Sprint 4] — UI polish + workflow tightening + dark mode + process hygiene (merged 2026-05-28)

Added — Sprint 4 (UI polish + workflow tightening + dark mode + process hygiene)

Backend (193 pytest passing — 192 sprint-1-to-3 + 1 sprint-4)

  • Simulation.tactic_ids JSON column (default [], NOT NULL via server_default). Sprint 3's techniques array is joined by a parallel tactics field in the serialized response.
  • Alembic migration 0004_simulation_tactic_ids.py — simple ADD COLUMN (SQLite native); downgrade via batch_alter_table.
  • PATCH /api/simulations/<sid> accepts {tactic_ids: ["TA0007", ...]} (TA-format, validated against the hardcoded _TACTIC_IDS map — no MITRE bundle dependency for tactics since TA-ids are a stable MITRE standard). Dedup via dict.fromkeys. SOC sending tactic_ids → 403. Auto-transition pending → in_progress extended to non-empty tactic_ids.
  • Done is now terminal: PATCH /api/simulations/<sid> on a done simulation → 409 {error: "simulation is done — reopen first"} (applies to all 3 roles, prioritised over RBAC field-level).
  • Reopen transition: POST /api/simulations/<sid>/transition {to: "review_required"} from done → 200, open to admin + redteam + soc. Implemented as a special case before the _ALLOWED_TRANSITIONS dict lookup; other transitions from done (→ pending / → in_progress / → done) remain forbidden (409 via dict miss).
  • Engagement auto-status: when any simulation transitions to in_progress (auto or manual), if engagement.status == planned → engagement passes to active in the same DB transaction. No auto-rollback. The _maybe_activate_engagement helper modifies and db.session.add()s only — the caller commits (no double-commit).
  • GET /api/mitre/matrix tactic_id field now returned in TA-format ("TA0007") instead of the internal slug ("discovery"). Aligns with the PATCH endpoint contract — frontend can round-trip the same tactic_id between matrix display and PATCH body. Spec-drift caught by the e2e test-verifier (AC-21.6 defect).
  • Internal helpers : _TACTIC_IDS (TA-id → short-name, 12 entries, non-sequential), _SLUG_TO_TA_ID (reverse), lookup_tactic(), get_tactic_name().
  • Migration tests now derive paths from __file__ instead of hardcoded worktree absolute paths (recurring sprint-3 issue resolved).

Frontend (92 vitest passing — typecheck + lint clean)

  • Dark mode: full Tailwind darkMode: 'class' plumbing, themed surface tokens via CSS variables under :root / .dark in index.css. Three-state cycle (light / dark / system) toggle in the topbar with lucide-react icons (Sun / Moon / Monitor). Persisted under localStorage key mimic-theme (default system, follows prefers-color-scheme). Dedicated useTheme() hook orchestrates the cycle + media-query listener.
  • Slab token split: a new slab / slab-text / slab-muted token family stays fixed #111827 / #f9fafb / #6b7280 regardless of theme. Used for permanently-dark surfaces (utility strip, footer, modal backdrop) that must NOT invert in dark mode. The themed ink token is now strictly for text. .btn-ink uses @apply bg-slab (single source of truth).
  • Modal backdrop: new .modal-backdrop CSS class (fixed rgba(0,0,0,0.6)) replaces bg-ink/60 (which inverted in dark mode). Applies to MitreMatrixModal and ConfirmDialog.
  • Badge contrast in dark mode: SimulationStatusBadge and StatusBadge use text-white (fixed) on colored backgrounds instead of text-canvas / text-ink-on (which inverted). Toast error uses bg-slab text-slab-text.
  • Dark mode shadows: new soft-lift-dark and floating-dark token variants, applied to cards and modals via dark:shadow-* so the lift remains visible on dark canvas. Hairlines bumped (#4b5563) for better separator visibility.
  • MITRE matrix modal overhaul: 12-column CSS grid (repeat(12, minmax(0, 1fr))), no horizontal scroll at 1280×720. Compact technique cells (text-[12px], hairline borders). Sticky tactic headers (uppercase, count badge). Sub-techniques expand/collapse preserved from sprint 3.
  • Tactic selection in matrix: clicking a tactic header toggles its selection in addition to techniques + sub-techniques. Tactic chips render with bg-primary text-canvas (filled), distinct from technique chips (bg-primary-soft text-primary-deep). Apply emits one combined PATCH {technique_ids, tactic_ids} — no two sequential calls.
  • MITRE input redesign: replaces the prior Add technique + Quick search button pair with an inline autocomplete input + matrix icon button to the right. Chips display the reference only (T1059.001 or TA0007); full technique name surfaces on title= hover. Empty state minimal.
  • done simulation UI: form fields are fully disabled, MitreTechniquesField is read-only (chips without ×, input + matrix icon hidden), action bar shows ONLY a Reopen button (visible to all 3 roles per RBAC). Save / Mark for review / Close / Delete are hidden in the done state. A "this simulation is done and read-only" banner replaces them.
  • UsersAdminPage Create account form alignment (3rd attempt — finally pixel-perfect): refactored from FormField + items-end to an explicit 3-row grid (labels / inputs+button / hints) using grid-rows-[auto_auto_auto]. Labels share row 1, inputs + button share row 2, hint sits alone in row 3 — the browser cannot misalign cells of different heights.
  • EngagementsListPage dedup: single + New CTA (header + empty-state share the same label).
  • Engagement query invalidation: useUpdateSimulation and useTransitionSimulation now invalidate both the simulation queries AND ["engagement", engagement_id] + ["engagements"] so the engagement status badge updates without a full reload after the auto-transition.

Process hygiene (US-24)

  • New agent definition .claude/agents/design-reviewer.md — read-only, runs AFTER frontend-builder and BEFORE code-reviewer. Audits alignment, DESIGN.md token usage, light/dark consistency, typography, whitespace rhythm, responsive sanity at 1280×720, button convention, V1 a11y, and inter-screen coherence.
  • Updated .claude/agents/frontend-builder.md Definition of Done — screenshots are now MANDATORY (one per feature/state introduced or modified, light + dark when theming is in scope). A "Dev server not started" line is a hard block.

Infra hygiene (US-25)

  • scripts/open-pr.sh — wraps POST /api/v1/repos/{owner}/{repo}/pulls on the Gitea REST API. Reads credentials from ~/.git-credentials (same source as git push — no token in env). Detects host/owner/repo from git remote get-url origin. Validates args, prints PR URL.
  • New Makefile target open-pr TITLE="..." BODY=path/to/body.md [BASE=main] wrapping the script. Team-lead PR creation is now automated.
  • README Make targets table documents the new target.

Acceptance tests (Playwright, 158 passed)

  • 7 new spec files (one per testable US): us17-ui-polish, us18-done-readonly-reopen, us19-engagement-auto-status, us20-matrix-fits-modal, us21-tactic-selection, us22-mitre-input-redesign, us23-dark-mode.
  • Coverage gaps from code-reviewer filled: +N suffix when techniques + tactics are mixed in the SimulationList MITRE column ; Tab focus-trap cycle in MitreMatrixModal ; dark-mode localStorage persistence across reload.
  • AC-21.6 defect caught by the e2e (matrix returned slug tactic_id, PATCH expected TA-format) was bounced to backend-builder and resolved within the sprint.

Changed

  • 2026-05-27 — SPEC.md § Fonctionnement clarified: done is terminal, only Reopen (open to all 3 roles) returns to review_required. Engagement auto-flips planned → active on first simulation in_progress, never the reverse.
  • 2026-05-27 — SPEC.md § Référentiel MITRE: added the sprint 4 tactic_ids (separated from technique_ids).
  • 2026-05-27 — SPEC.md § UI/UX (new section): theming (light/dark/system, default = system), button convention (icon + ≤8-char label), modal focus trap V1.
  • 2026-05-27 — SPEC.md § Workflows: design-reviewer agent inserted between frontend-builder and code-reviewer. PR creation now via make open-pr.
  • 2026-05-27 — Carry-over commit: sprint 3 § Simulation multi-techniques edit had been left uncommitted at sprint 3 close; applied at sprint 4 start so SPEC.md and the shipped code finally agree (lesson logged in tasks/lessons.md).

[Sprint 3] — Multi-technique simulations + MITRE matrix modal (merged 2026-05-27)

Added — Sprint 3 (Multi-technique simulations + MITRE matrix modal)

Backend (164 pytest passing)

  • Simulation.techniques JSON column replaces the scalar mitre_technique_id / mitre_technique_name pair. Stored as [{"id", "name"}]; tactics are derived at serialize time from the MITRE service (snapshot pattern survives bundle updates).
  • Alembic migration 0003_simulation_techniques_array.py — reversible upgrade (backfill from scalars → drop scalars → enforce NOT NULL via batch_alter_table) and symmetric downgrade.
  • PATCH /api/simulations/<sid> now accepts {technique_ids: ["T1059", "T1059.001", ...]} (flat list of T-IDs, parents and subs at the same level). Server validates each ID against the bundle (400 on unknown), deduplicates while preserving order, resolves names, and rejects SOC payloads (403). Returns 503 if the bundle isn't loaded.
  • GET /api/mitre/matrix — new endpoint returning the full Enterprise tree [{tactic_id, tactic_name, techniques: [{id, name, subtechniques: [{id, name}]}]}]. Tactics in canonical order (Initial Access → Impact). Techniques sorted alphabetically per tactic; sub-techniques nested under their parent via dot-ID detection.
  • mitre_svc extended with get_tactics(id), lookup_name(id), get_matrix(), and a TACTIC_NAMES constant fixing the cosmetic "Command And Control""Command and Control" (MITRE canonical capitalisation).
  • REDTEAM_FIELDS | {"technique_ids"} SOC gate in simulation_workflow.apply_patch preserves the sprint 2 field-level RBAC pattern.
  • Auto-transition pending → in_progress extended: triggers when technique_ids is non-empty (consistent with the "non-empty value" rule from sprint 2). Empty list does not trigger.

Frontend (86 vitest passing)

  • MitreTechniquesField orchestrates multi-technique selection with auto-save — every add (Quick Search / matrix Apply) and every remove (× on tag chip) triggers a PATCH via useUpdateSimulation. Toast feedback on success/error; UI disabled during the in-flight PATCH; silent dedup if the user re-adds an already-present technique.
  • MitreTechniqueTag — chip component (bg-primary-soft text-primary-deep rounded-full) with an × remove button.
  • MitreMatrixModal — full-width modal, one column per tactic (220px fixed), horizontal scroll. Each technique top-level is clickable (toggle); a chevron expands/collapses sub-techniques rendered in cascade. Search filter (case-insensitive on id + name) auto-expands the parent of a matched sub-technique. Tactic header shows a "N selected" counter (parents + subs). Footer: Cancel + "Apply N technique(s)" (or "Clear all" when N=0 and there's an existing selection). Focus trap V1: search input auto-focus on open, Tab cycles within the modal, Escape and backdrop click both = Cancel.
  • MitreTechniquePicker (sprint 2) clean-rewritten to a one-shot onSelect({id, name}) signature; no incoming value props. The picker resets after each selection — the parent (MitreTechniquesField) handles append + dedup.
  • SimulationList MITRE column displays T1059 +2 when 3 techniques are selected (first id + remainder counter) or when empty.
  • SimulationFormPageMitreTechniquesField replaces the old standalone MitreTechniquePicker. The technique state moves out of the RT form (independent auto-save cycle); the Save Red Team button still batches the other RT fields.

Acceptance tests (Playwright)

  • 4 new spec files: us13-multi-techniques.spec.ts, us14-techniques-tags.spec.ts, us15-mitre-matrix-modal.spec.ts, us16-regression-sprint2.spec.ts — all ACs (AC-13.1 → AC-16.3) pass.
  • Sprint 2 specs us8-simulation-redteam-fill.spec.ts and us10-mitre-autocomplete.spec.ts adapted to the new techniques: [] array (no more scalar field assertions).

Changed

  • 2026-05-27 — SPEC.md § Simulation: "Type d'attaque MITRE correspondant" (singular) → "Types d'attaque MITRE correspondants (multi-techniques) — sélectionnables par autocomplete OU via la matrice ATT&CK affichée en modale. Sub-techniques supportées."
  • 2026-05-27 — Breaking API change: mitre_technique_id and mitre_technique_name removed from the Simulation payload (both directions). Replaced by techniques: [{id, name, tactics}] in responses and technique_ids: string[] in PATCH requests. No backwards-compatibility shim (no external consumer at this stage).

[Sprint 2] — Simulations + MITRE ATT&CK (merged 2026-05-27)

Added — Sprint 2 (Simulations + MITRE ATT&CK)

Backend (Flask + SQLAlchemy, 131 pytest passing)

  • Simulation model with redteam-side (name, mitre_technique_id, mitre_technique_name, description, commands, prerequisites, executed_at, execution_result) and SOC-side (log_source, logs, soc_comment, incident_number) fields, plus status enum (pending / in_progress / review_required / done), FK to Engagement (cascade delete) and User (creator).
  • Alembic migration 0002_add_simulations.py.
  • 7 new endpoints: GET/POST /api/engagements/<eid>/simulations, GET/PATCH/DELETE /api/simulations/<sid>, POST /api/simulations/<sid>/transition, GET /api/mitre/techniques?q=.
  • simulation_workflow service: field-level RBAC (SOC blocked when status ∈ {pending, in_progress}; SOC rejected if payload contains a redteam field), state machine (only forward transitions, validated by role), and auto-transition pending → in_progress when admin/redteam saves any non-empty redteam field.
  • mitre service: STIX 2.1 Enterprise bundle loaded at boot, indexed by T-id + name + tactic. Ranked search (exact-id > prefix-id > substring-name), max 20 results. Includes sub-techniques (T1059.001). Boot-safe: missing/corrupt bundle logs a warning and the endpoint returns 503 instead of crashing the app.
  • make update-mitre is now a real target — fetches the upstream STIX bundle and restarts the container if running. Bundle is committed at backend/data/mitre/enterprise-attack.json (~46 MB) so make build stays self-contained.
  • Upfront validation of executed_at (no partial mutation on parse failure).

Frontend (React + TanStack Query, 63 vitest passing)

  • SimulationList component rendered inside EngagementDetailPage (replaces the Sprint 1 placeholder). Columns: name, MITRE id, status badge, executed_at. Row click → SPA navigation via useNavigate (no full reload).
  • SimulationFormPage (/engagements/:eid/simulations/new and /engagements/:eid/simulations/:sid/edit): single role-aware page with two cards ("Red Team" / "SOC"). Redteam/admin can edit all fields; SOC sees the redteam card as read-only and the SOC card disabled (with an explanatory banner) until status reaches review_required. Footer surfaces context-appropriate transition buttons ("Marquer en revue" / "Clôturer") and a confirmation modal for delete.
  • MitreTechniquePicker: debounced (200 ms) autocomplete input with keyboard navigation (↑↓ / Enter / Escape), listbox accessibility, and an inline 503 error path. Selection populates both mitre_technique_id and mitre_technique_name. A hasHydratedFromProps ref prevents the input from being wiped mid-stroke when the parent emits onChange(null, null).
  • SimulationStatusBadge: 4 variants mapped to DESIGN.md tokens (bg-fog, bg-primary-soft, bg-bloom-coral, bg-storm-deep). Sibling of the existing StatusBadge rather than a forked generic — the two badges share visual scaffolding but their enums diverge.
  • ConfirmDialog: generic modal used by the delete flow.
  • TanStack Query hooks: useEngagementSimulations, useSimulation, useCreateSimulation, useUpdateSimulation, useDeleteSimulation, useTransitionSimulation, useMitreSearch. Mutations invalidate both the simulation detail key and the engagement-scoped list key.

Acceptance tests (Playwright, 68/68 passing)

  • 6 new spec files (one per user story US-7 → US-12), 32 tests, all green.
  • us4-engagements.spec.ts AC-4.9 assertion refreshed: the Sprint 1 placeholder text was correctly replaced by the new SimulationList (the test now asserts the new heading + "New simulation" link).
  • Sprint 1 docker-hardcoded tests (us1, us6) now resolve thanks to the podman auto-detect added to those specs in the same sprint — full suite is green on both docker and podman hosts.
  • E2e assertions translated to match the i18n cleanup (French → English) shipped in the post-QA fix.

Post-QA fixes (2026-05-26)

  • All French labels in the frontend translated to English (convention: anglais partout). Affected: SimulationList, SimulationFormPage, ConfirmDialog strings.
  • UsersAdminPage "Create account" form: grid alignment fixed — the password field's hint="≥ 8 characters" was pushing labels out of alignment with items-end. Now uses items-start + self-end button wrapper so labels sit at the same baseline and the Create button stays bottom-aligned.
  • SimulationFormPage "Execution result" field: switched from single-line TextInput to multiline TextArea (5 rows).
  • SimulationFormPage actions reorganised: single sticky action bar at the bottom of the page replaces the previous split between RT-card footer, SOC-card footer, and workflow div. Layout: Save Red Team · Save SOC · | · Mark for review · Close · (right-aligned) Delete.

Changed

  • 2026-05-26 — make update-mitre upgraded from no-op placeholder to a real curl + optional container restart (Sprint 1 marker resolved).
  • 2026-05-26 — EngagementDetailPage no longer renders the "Simulations à venir au Sprint 2" placeholder; it embeds <SimulationList> instead.
  • 2026-05-26 — Makefile now auto-detects the container engine (CONTAINER_CMD ?= docker || podman) instead of hard-coding docker. Override with make <target> CONTAINER_CMD=podman or export CONTAINER_CMD=…. The matching e2e tests (us1, us6) were updated to mirror the same detection so they pass on podman-only machines without an explicit MIMIC_CONTAINER_CMD export.

[Sprint 1] — Auth + CRUD Engagement (merged 2026-05-26)

Added

Backend (Flask + SQLAlchemy + SQLite, 63 pytest passing)

  • User model with admin / redteam / soc enum, argon2 password hashing.
  • Engagement model with planned / active / closed status, FK to creator user.
  • JWT Bearer auth (PyJWT, HS256, 60-min TTL), @login_required and @role_required(*roles) decorators.
  • 13 API endpoints: /api/auth/{login,logout,me}, /api/users CRUD (admin-only with last-admin protection), /api/engagements CRUD (RBAC per role), /api/health.
  • Alembic migration applied at container boot by docker/entrypoint.sh.
  • flask create-admin CLI with duplicate-username and short-password validation.
  • Engagement serializer returns created_by={id, username} (not bare User object).
  • SPA fallback returns JSON 404 for unknown /api/* paths (no HTML leakage).

Frontend (React + Vite + TailwindCSS + TanStack Query, 20 vitest passing)

  • Inter font bundled locally via @fontsource-variable/inter (no CDN at runtime).
  • Tailwind config maps the DESIGN.md token system (palette, typography, spacing, radii).
  • Pages: LoginPage, EngagementsListPage, EngagementFormPage (new+edit), EngagementDetailPage (Sprint 2 placeholder), UsersAdminPage.
  • Components: Layout, ProtectedRoute (auth + role gate), StatusBadge, FormField, LoadingState/ErrorState/EmptyState, Toast + provider.
  • Axios client with Bearer interceptor; 401 → token purge + redirect /login + "Session expirée" toast (AC-2.6); 403 → "Accès refusé" toast (AC-3.7).
  • TanStack Query hooks: useAuth, useEngagements, useUsers, useToast.

Deployment

  • Single-container docker/Dockerfile (multistage: node:20-alpinepython:3.12-slim).
  • docker/entrypoint.sh running flask db upgrade && flask run.
  • Makefile with build, start, stop, restart, update, logs, create-admin, update-mitre (no-op placeholder for Sprint 2), test-backend, test-frontend, test-e2e, clean.
  • .env.example documenting MIMIC_JWT_SECRET, MIMIC_DB_PATH, MIMIC_PORT.
  • SQLite persisted at /data/mimic.sqlite, volume mimic-data survives make restart.

Acceptance tests (Playwright, 36 specs, all 27 ACs covered)

  • e2e/ scaffold: playwright.config.ts, fixtures/{auth,api}.ts, 6 spec files (one per user story).
  • Suite is portable via MIMIC_CONTAINER_CMD / MIMIC_BASE_URL env vars (works with docker or podman).

Docs

  • README.md with quick-start, architecture overview, project layout, make target reference, and dev workflow.
  • pyrightconfig.json at repo root pointing the Python LSP to backend/.venv and adding the worktree root to extraPaths for absolute imports.

Changed

  • 2026-05-26 — admin role widened in SPEC.md § Décisions techniques. The initial draft restricted admin to user-management only; after the Sprint 1 plan review surfaced the operational pain (admin would need a second redteam account just to manage engagements), the user decided to make admin a super-user that cumulates redteam rights on engagements/simulations.

Removed

  • none

[Sprint 0] — Bootstrap (merged 2026-05-26)

Added

  • Initial SPEC.md covering project scope, simulation model, workflow, stack, and agent team.
  • Technical decisions section in SPEC.md: 3-role auth (admin/redteam/soc), JWT Bearer, single-container Flask+React, local MITRE STIX bundle, minimal Engagement model, admin bootstrap via Makefile target.
  • Sub-agent definitions under .claude/agents/ for backend-builder, frontend-builder, spec-reviewer (project override of the built-in, covers plan-vs-spec and code-vs-spec), code-reviewer, test-verifier, devil-advocate.
  • Project tracking scaffold: tasks/todo.md, tasks/lessons.md, CHANGELOG.md, .gitignore.