From a9fe2fc5288568a35f5f4bc6a6c8c210894510d1 Mon Sep 17 00:00:00 2001 From: Knacky Date: Thu, 11 Jun 2026 10:53:16 +0200 Subject: [PATCH] docs(sprint-9): plan UI 2-col engagement + global contrast pass --- tasks/todo.md | 132 +++++++++++++++++++++----------------------------- 1 file changed, 55 insertions(+), 77 deletions(-) diff --git a/tasks/todo.md b/tasks/todo.md index 9c9d4e4..49d6d85 100644 --- a/tasks/todo.md +++ b/tasks/todo.md @@ -1,95 +1,73 @@ -# Sprint 8 — C2 Layer: Mythic Integration +# Sprint 9 — UI: engagement 2-col + global contrast pass -> Branch : `sprint/8-c2` · Worktree : `.claude/worktrees/sprint-8-c2` · Base : `main` @ `6ca614a` +**Base**: `sprint/8-c2` (sprint 8 not yet merged on origin/main, but its `C2ConfigCard` is the right pane). +**Scope**: frontend-only. No backend, no schema. No new features. -SPEC.md phase 2: "après que la V1 soit terminée, nous ajouterons une couche permettant -de se connecter à un C2 (mythic ou maison) afin d'exécuter des simulations au travers du C2." +--- -## §0 — Binding decisions (locked with user, 2026-06-10) +## Decisions (locked) -1. **C2 target**: Mythic 3.x, behind a thin `C2Adapter` interface (keeps the door open for a custom C2 later). -2. **Scope**: FULL bidirectional — execute + near-real-time tracking + history import. User explicitly overrode the "one-shot first" recommendation; overrun risk accepted (see R1). -3. **C2 config**: per-engagement (URL + API token). Token is write-only at the API level — never returned in clear. -4. **UI anchor**: execution lives in the simulation screen (Red Team card). -5. **Realtime mechanism**: short polling. Frontend: TanStack Query `refetchInterval` 2 500 ms while any attached task is incomplete. Backend: poll-on-read — refreshes non-completed tasks from Mythic when the task list is read. No scheduler, no new infra. -6. **Secret storage**: API token Fernet-encrypted in SQLite. Key from env var `MIMIC_ENCRYPTION_KEY` (mandatory to enable C2 features, never hardcoded — OPSEC rule). -7. **History import**: BOTH — auto-attach of tasks launched from Mimic AND manual browse/select of the callback's task history. -8. **Validation**: fully mocked — no dev Mythic instance available. pytest uses a mocked adapter; e2e uses a built-in `FakeAdapter` selected via `MIMIC_C2_ADAPTER=fake`. -9. **RBAC**: C2 is a RedTeam resource — admin + redteam full access, SOC gets 403 on every C2 endpoint (same precedent as Templates and Export). -10. **Workflow tie-in**: launching a C2 execution auto-transitions the simulation `pending → in_progress` (same rule as a manual RT edit). -11. **Output mapping**: when a task completes (or is imported), its output is APPENDED to `execution_result` prefixed by a `$ ` header line; `executed_at` is set from the first task's timestamp if empty; the command is appended to `commands` if not already present. +1. **Engagement page** : passer en **2 colonnes** sur desktop (`lg:grid-cols-2`), `[engagement form | C2ConfigCard]`. Mobile/tablet : stack vertical (comportement actuel). +2. **Contraste global** : le problème est que `canvas` (page bg) et `paper` (card bg) sont **tous deux `#ffffff`** en light mode. Les cartes ne ressortent que par leur hairline 1px → fatigue oculaire confirmée par l'utilisateur. +3. **Fix retenu** : **tinter le canvas light** d'un neutre froid très pâle. `paper` reste blanc pur. Les cartes "lèvent" naturellement sans casser le brutalisme. + - Proposition : `canvas` light `#f3f5f8` (gris-bleu très pâle, cohérent avec l'electric blue), `paper` light `#ffffff`. + - Dark mode **inchangé** (`canvas #111827` / `paper #1f2937` déjà différenciés). +4. **Pas de shadow**, pas de radius. La brutalité reste intacte — seul le contraste de surface change. +5. **Hairline** : à vérifier sur le nouveau canvas. Si nécessaire, passer `hairline` light de la valeur actuelle à un poil plus sombre pour préserver la lisibilité du bord sur tinted canvas. Mais éviter si la lecture est déjà bonne. -## §1 — Mythic adapter contract (pinned from MythicMeta/Mythic_Scripting client source) +--- -- Transport: POST `https://:7443/graphql`, header `apitoken: ` (Hasura behind nginx). -- Issue task: mutation `createTask(callback_id: , command, params, tasking_location: "command_line")`. -- List callbacks: query on `callback` table — `id, display_id, active, host, user, domain, last_checkin`. -- Task status: query on `task` table — `display_id, status, completed` (client lib uses a subscription; we poll the query instead, per decision 5). -- Task output: query on `response` table ordered by id — `response_text` is **base64-encoded**, decode + concatenate. -- History: query `task` filtered by `callback_display_id`, paginated, with command + status + timestamps. +## Task A — EngagementFormPage 2-col -`C2Adapter` interface (`backend/app/services/c2/adapter.py`): -- `test_connection() -> C2Health` -- `list_callbacks() -> list[C2Callback]` -- `create_task(callback_display_id, command, params) -> int` (task display_id) -- `get_task(task_display_id) -> C2TaskStatus` -- `get_task_output(task_display_id) -> str` (decoded) -- `list_callback_tasks(callback_display_id, page, page_size) -> C2TaskPage` +**File** : `frontend/src/pages/EngagementFormPage.tsx` -Implementations: `MythicAdapter` (requests, `verify_tls` flag from config — lab instances run self-signed), `FakeAdapter` (deterministic in-memory data, selected by `MIMIC_C2_ADAPTER=fake`, also powers e2e). +- Remplacer le wrapper `
` par un container plus large + grid 2-col responsive. +- Header reste en haut, full width. +- Body : `grid grid-cols-1 lg:grid-cols-2 gap-xl` avec : + - Col gauche : `
` engagement (déjà en `card-product`). + - Col droite : `` (seulement quand `editing && canEditEngagements`). +- Si pas en edit (création) : col droite vide → garder la grid mais que la col gauche se déploie via `lg:col-span-2` (pour pas avoir un vide à droite). Acceptable alternative : `flex` + `max-w-2xl` quand non-editing. +- Pas de modif sur la logique de form, validation, mutations. -## §2 — Backend (backend-builder) — milestones M1→M4 +## Task B — Contrast pass (tokens) -**Data model (1 Alembic migration):** -- `c2_config`: id, engagement_id FK **unique** (`ON DELETE CASCADE`, same precedent as `simulations.engagement_id`), url, api_token_encrypted, verify_tls bool (default true), created_at, updated_at. -- `c2_task`: id, simulation_id FK (`ON DELETE CASCADE`), mythic_task_display_id int, callback_display_id int, command text, params text nullable, status text, completed bool, output text nullable, source enum(`mimic`,`import`), created_at, completed_at nullable. +**Files** : +- `DESIGN.md` § Surface : mettre à jour `canvas` light = `#f3f5f8`, conserver `paper` light = `#ffffff`. Documenter dans la même section que "canvas tints lift paper cards in light mode without violating brutalism". +- Token source de vérité (Tailwind config ou CSS vars). Localiser et appliquer la même MAJ. Probablement `frontend/tailwind.config.js` ou un `frontend/src/styles/tokens.css` / `index.css`. +- Vérifier qu'aucun composant ne hardcode `#ffffff` pour la page bg (devrait utiliser `bg-canvas`). +- Tests CSS smoke : `bg-canvas` continue de matcher, dark mode inchangé. -**Endpoints (all admin+redteam; SOC → 403):** -- `GET/PUT/DELETE /api/engagements//c2-config` — GET returns `has_token: bool`, never the token. -- `POST /api/engagements//c2-config/test` — connectivity check via adapter. -- `GET /api/engagements//c2/callbacks` — active callbacks. -- `POST /api/simulations//c2/execute` `{callback_display_id, commands: [str]}` — one Mythic task per command, rows in `c2_task` (source=mimic), auto-transition pending→in_progress. -- `GET /api/simulations//c2/tasks` — poll-on-read: refresh incomplete tasks from Mythic; on completion fetch output and apply decision 11 mapping (idempotent, applied once). -- `GET /api/engagements//c2/callbacks//history?page=` — paginated callback history for import. -- `POST /api/simulations//c2/import` `{task_display_ids: [int]}` — import selected tasks (source=import) + decision 11 mapping. +## Task C — Visual regression check -**Milestones:** M1 crypto service (Fernet) + migration + config CRUD + test endpoint → M2 callbacks + execute → M3 poll-on-read status/output + mapping → M4 history + import. -**Tests:** pytest with mocked adapter (~35-45 new), zero live HTTP. Crypto round-trip, RBAC 403 matrix, mapping idempotence, migration up/down. +- `pnpm vitest run` clean. +- `pnpm tsc --noEmit` clean. +- `pnpm lint` clean. +- Screenshots avant/après (au moins) : + - EngagementsListPage (cards-on-canvas) + - EngagementDetailPage + - EngagementFormPage (edit, avec C2ConfigCard à droite) + - SimulationFormPage (déjà 2-col sprint 7, vérifier que le tinted canvas n'écrase pas) + - LoginPage +- Dark mode : passe rapide pour confirmer aucune régression. -## §3 — Frontend (frontend-builder) +--- -- **Engagement detail/form**: "C2 configuration" card — URL, token (password input, write-only placeholder when `has_token`), verify-TLS checkbox, [Test connection] with result feedback. Admin+redteam only. -- **SimulationFormPage RT card**: [Execute via C2] button (hidden when no config; disabled when done) → modal: callback picker table (display_id, host, user, active, last_checkin — font-mono data cells) → commands textarea prefilled from `rt.commands` → Launch. -- **C2 tasks panel** (under RT card): table of attached tasks — command (mono), status badge (semantic tokens), completed_at (mono). `refetchInterval` 2 500 ms while any incomplete; stops when all done. -- **Import history modal**: callback picker → paginated task list with checkboxes → [Import selected]. -- Terminal-SOC compliance: rounded-none, zero transitions, hairline borders, mono for data only. Status colors via `success`/`warn` semantic tokens. -- Vitest ~15-20 new tests. `data-testid` on every new interactive surface for e2e. +## Sequencing -## §4 — Docs & hygiene +1. **frontend-builder** : Task A + B + C. Une seule passe, commits atomiques. +2. **design-reviewer** : revue visuelle après merge des commits builder. Focus : + - Lecture confortable cards-on-tinted-canvas. + - Hairline encore visible. + - Dark mode inchangé. + - Pas de régression sur components qui pourraient ré-utiliser `bg-canvas` pour autre chose (dropdowns, modals). -- **SPEC.md C2 section = FIRST commit** of the sprint (sprint 5/6 lesson: never close a sprint with SPEC uncommitted). -- README: `MIMIC_ENCRYPTION_KEY`, `MIMIC_C2_ADAPTER` env vars + docker compose example. -- CHANGELOG sprint 8 entry at close. +--- -## §5 — Pipeline & sequencing +## Definition of Done -1. spec-reviewer pre-pass on this plan (before any code). -2. backend-builder M1+M2 → API contract frozen → frontend-builder starts (parallel with backend M3+M4). -3. design-reviewer on new UI surfaces (screenshots come from the Playwright run — e2e bootstraps its own users, which sidesteps the sprint-7 credentials wall). -4. **code-reviewer must be respawned** (killed 2026-06-10 after idle-loop malfunction) before the review phase. -5. test-verifier: new C2 Playwright specs (fake adapter) + **full 223-spec re-run** — also clears the sprint 7 e2e debt (suite never re-ran after the design refresh). - -## §6 — Risks - -- **R1 — scope**: full bidirectional in one sprint. Mitigation: M1→M4 are ordered; M4 (history import) is the cut line if we overrun. User accepted explicitly. -- **R2 — schema fidelity**: no live Mythic to validate against; adapter pinned to the official `MythicMeta/Mythic_Scripting` client source on **master @ 2026-06-10** (verified via raw.githubusercontent.com). First real connection may need a small patch. README records the exact source URL alongside the "may need a patch" note. Adapter must defensively handle `response_text` base64 decode failures (binary output): on `binascii.Error` keep raw bytes hex-encoded with a prefix ` ` so `execution_result` never silently corrupts. -- **R3 — secret at rest**: Fernet + env key; key rotation out of scope (documented limitation). -- **R4 — polling load**: poll-on-read touches only incomplete tasks of the open simulation — bounded. -- **R5 — e2e drift**: sprint 7 redesign never re-validated by Playwright; the full re-run in §5.5 surfaces any breakage — budget fix time. - -## §7 — Definition of Done - -- pytest green (256 baseline on main + new), vitest green (139 baseline + new), Playwright full suite green (223 baseline + new C2 specs). -- spec-reviewer (plan) + design-reviewer (UI) + code-reviewer (diff) all APPROVED. -- SPEC.md, README.md, CHANGELOG.md updated. No secret, key or IP hardcoded anywhere. -- PR #11 opened via `make open-pr`. +- EngagementFormPage en édition : 2 colonnes desktop, stack mobile. +- Page bg différencié de card bg en light mode (eyes confort). +- Vitest + typecheck + lint verts. +- Design-reviewer APPROVED. +- Screenshots livrés ou écueil documenté. +- Commits conventional, branche `sprint/9-ui-contrast`.