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

22 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 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.