Files
mimic/CHANGELOG.md
Knacky 6d2bb091e2 docs: sprint 4 wrap-up — CHANGELOG + README + 7 lessons + plan final
- CHANGELOG: sprint 4 entry under [Unreleased] (covers all 9 US: dark mode, MITRE matrix overhaul, tactic_ids, done read-only + Reopen, engagement auto-status, UI polish, design-reviewer agent, PR helper, screenshots mandatory). Sprint 3 moved to its own [Sprint 3] section.
- README: status bump, test counts refreshed (193/92/158).
- tasks/lessons.md: 7 sprint-4 lessons captured (git status before sprint close, endpoint round-trip mismatch caught only by e2e, ink vs slab token split, structural row layout > class tweaks, hardcoded paths in migration tests, screenshots with auth, builder cross-context summaries as accidental re-dispatch).
- tasks/todo.md: status flipped to 🟢 SPRINT COMPLET, execution sequence ticks updated with commit hashes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 21:41:47 +02:00

182 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Changelog
All notable changes to Mimic are tracked here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/).
## [Unreleased]
### 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.
- `SimulationFormPage``MitreTechniquesField` 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-alpine``python: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`.