feat(c2): integrate Mythic command and control (sprint 8) #11
66
SPEC.md
66
SPEC.md
@@ -59,8 +59,70 @@ CSV : exactement 1 ligne d'en-tête + 1 ligne par simulation. Markdown : en-têt
|
|||||||
Prévoir un module d'authentification : dans un premier temps local à la bdd.
|
Prévoir un module d'authentification : dans un premier temps local à la bdd.
|
||||||
|
|
||||||
Dans un premier temps, il s'agit juste de notifier manuellement de l'exécution et les résultats des tests.
|
Dans un premier temps, il s'agit juste de notifier manuellement de l'exécution et les résultats des tests.
|
||||||
Dans un second temps, 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 simulation au travers du C2.
|
Dans un second temps, 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 simulation au travers du C2.
|
||||||
|
|
||||||
|
## Intégration C2 (Sprint 8+)
|
||||||
|
|
||||||
|
Couche d'intégration C2 permettant d'exécuter les commandes d'une simulation à travers un Command & Control distant, suivre l'avancement des tâches en quasi-temps réel, et importer l'historique d'exécutions existant. **Implémentation de référence : Mythic 3.x**, derrière une interface `C2Adapter` mince qui ne ferme pas la porte à un C2 maison ultérieur.
|
||||||
|
|
||||||
|
**RBAC C2 = ressource Red Team uniquement** (précédent Templates + Export) : admin et redteam ont accès complet (config + exécution + import). SOC retourne 403 sur tous les endpoints C2 (pas de nav link, pas d'affichage du panneau C2).
|
||||||
|
|
||||||
|
**Configuration par engagement** : chaque engagement possède au plus une `c2_config` (URL Mythic + API token + flag `verify_tls`). Le token est **chiffré au repos** via `cryptography.Fernet` ; la clé est dérivée de l'env var `MIMIC_ENCRYPTION_KEY` (variable obligatoire pour activer la fonctionnalité C2 — jamais hardcodée, conforme à la règle OPSEC zero-secret-in-code). Le token n'est jamais renvoyé en clair par l'API — `GET /api/engagements/<id>/c2-config` retourne `has_token: bool` uniquement. Mise à jour via `PUT` ; suppression via `DELETE`. La suppression d'un engagement supprime en cascade sa `c2_config`.
|
||||||
|
|
||||||
|
**Sélection d'adapter** via l'env var `MIMIC_C2_ADAPTER` :
|
||||||
|
- `mythic` (défaut) : adapter Mythic réel (GraphQL via Hasura).
|
||||||
|
- `fake` : adapter en mémoire déterministe utilisé pour la validation Playwright et le dev local sans instance Mythic.
|
||||||
|
|
||||||
|
**Modèle de données — additions** :
|
||||||
|
|
||||||
|
`c2_config` (1 ligne par engagement au max) :
|
||||||
|
| Colonne | Type | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | int PK | |
|
||||||
|
| `engagement_id` | int FK `engagements.id` ON DELETE CASCADE, **UNIQUE** | |
|
||||||
|
| `url` | text | endpoint Mythic, ex. `https://lab.internal:7443` |
|
||||||
|
| `api_token_encrypted` | text | Fernet ciphertext, jamais en clair |
|
||||||
|
| `verify_tls` | bool, défaut `true` | `false` autorisé pour labs auto-signés |
|
||||||
|
| `created_at`, `updated_at` | datetime | |
|
||||||
|
|
||||||
|
`c2_task` (lien simulation ↔ tâche Mythic) :
|
||||||
|
| Colonne | Type | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | int PK | |
|
||||||
|
| `simulation_id` | int FK `simulations.id` ON DELETE CASCADE | |
|
||||||
|
| `mythic_task_display_id` | int | identifiant côté Mythic |
|
||||||
|
| `callback_display_id` | int | callback Mythic sur lequel la tâche tourne |
|
||||||
|
| `command` | text | commande envoyée |
|
||||||
|
| `params` | text nullable | paramètres associés |
|
||||||
|
| `status` | text | statut brut Mythic (`submitted`, `completed`, `error`, …) |
|
||||||
|
| `completed` | bool | `true` quand la tâche est terminée |
|
||||||
|
| `output` | text nullable | sortie décodée (base64 → utf-8 ; binaire → préfixe `<binary>` + hex) |
|
||||||
|
| `source` | enum `mimic` \| `import` | tâche lancée depuis Mimic ou importée a posteriori |
|
||||||
|
| `created_at` | datetime | |
|
||||||
|
| `completed_at` | datetime nullable | timestamp de complétion |
|
||||||
|
|
||||||
|
**Endpoints C2** (tous admin+redteam ; SOC = 403) :
|
||||||
|
- `GET /api/engagements/<id>/c2-config` — `{has_token, url, verify_tls}` (jamais le token en clair).
|
||||||
|
- `PUT /api/engagements/<id>/c2-config` — `{url, api_token?, verify_tls}`.
|
||||||
|
- `DELETE /api/engagements/<id>/c2-config`.
|
||||||
|
- `POST /api/engagements/<id>/c2-config/test` — test de connectivité via l'adapter, renvoie `{ok, error?}`.
|
||||||
|
- `GET /api/engagements/<id>/c2/callbacks` — callbacks actifs de l'instance Mythic configurée.
|
||||||
|
- `POST /api/simulations/<id>/c2/execute` `{callback_display_id, commands: [str]}` — une tâche Mythic par commande, stockées dans `c2_task` (source=`mimic`). **Auto-transition** : si la simulation est `pending`, elle passe à `in_progress` (même règle que l'édition manuelle RT — cf. § Fonctionnement).
|
||||||
|
- `GET /api/simulations/<id>/c2/tasks` — poll-on-read : à la lecture, rafraîchit le statut et l'output des `c2_task` non terminées depuis Mythic, applique le mapping de sortie (voir ci-dessous) à la simulation pour chaque tâche qui vient de se terminer (idempotent — appliqué une seule fois par tâche).
|
||||||
|
- `GET /api/engagements/<id>/c2/callbacks/<cid>/history?page=` — historique paginé des tâches d'un callback, pour l'import.
|
||||||
|
- `POST /api/simulations/<id>/c2/import` `{task_display_ids: [int]}` — import sélectif de tâches (source=`import`) + mapping de sortie.
|
||||||
|
|
||||||
|
**Mapping de sortie vers la simulation** (appliqué une fois par tâche, lors de la complétion ou de l'import) :
|
||||||
|
- `simulation.execution_result` reçoit en append le bloc `\n$ <command>\n<output>\n` (préserve l'existant, jamais d'écrasement).
|
||||||
|
- `simulation.executed_at` est renseigné depuis le timestamp de la première tâche complétée si le champ est vide ; sinon non modifié.
|
||||||
|
- `simulation.commands` reçoit en append la commande si elle n'y figure pas déjà (déduplication ligne par ligne).
|
||||||
|
|
||||||
|
**Suivi temps réel** : polling court — le frontend re-fetch `GET /api/simulations/<id>/c2/tasks` toutes les **2 500 ms** via TanStack Query `refetchInterval` tant qu'une tâche attachée n'est pas terminée ; le polling s'arrête automatiquement quand toutes les tâches sont `completed`. Pas d'infrastructure ajoutée côté serveur (pas de WebSocket, pas de scheduler).
|
||||||
|
|
||||||
|
**UI** : les contrôles C2 vivent dans la carte Red Team de l'écran simulation — bouton `[Execute via C2]` ouvrant une modale (picker de callback + textarea de commandes pré-remplie depuis `commands`), panneau des tâches attachées sous la carte, et modale d'import historique. Configuration C2 visible/éditable depuis l'écran de détail/édition d'engagement.
|
||||||
|
|
||||||
|
**Validation** : MVP entièrement mocké — pytest utilise un adapter mocké (zéro HTTP live), Playwright utilise l'adapter `fake` (déterministe). Le branchement contre une instance Mythic réelle est repoussé au premier usage opérationnel et peut nécessiter un patch mineur du contrat GraphQL.
|
||||||
|
|
||||||
## Stacks techniques
|
## Stacks techniques
|
||||||
* **FrontEnd** : WebUI
|
* **FrontEnd** : WebUI
|
||||||
- Stacks standard : ReactJS, Vite, TailWind etc...
|
- Stacks standard : ReactJS, Vite, TailWind etc...
|
||||||
|
|||||||
204
tasks/todo.md
204
tasks/todo.md
@@ -1,153 +1,95 @@
|
|||||||
# Sprint 7 — Design Refresh: Terminal-SOC Aesthetic
|
# Sprint 8 — C2 Layer: Mythic Integration
|
||||||
|
|
||||||
> Branch : `sprint/7-design` · Worktree : `.claude/worktrees/sprint-7-design` · Base : `main` @ `e27babe`
|
> Branch : `sprint/8-c2` · Worktree : `.claude/worktrees/sprint-8-c2` · Base : `main` @ `6ca614a`
|
||||||
|
|
||||||
## §0 — Binding decisions (locked with user, 2026-06-09)
|
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."
|
||||||
|
|
||||||
1. **Visual direction**: Bloomberg / Terminal-SOC — dense, brutalist, semantic colors strong, no ornament.
|
## §0 — Binding decisions (locked with user, 2026-06-10)
|
||||||
2. **Border-radius**: **0 everywhere** except status pills (`rounded-pill`) and avatars (round). All buttons, cards, modals, inputs, dropdowns, tables, tags → angular.
|
|
||||||
3. **Palette**: KEEP current (`#024ad8` primary blue, `slab #111827`, `canvas/paper/cloud/fog/ink` light+dark vars). ADD semantic tokens `success-green` + `warn-amber` (confirmed scope add — needed for SOC-grade status legibility on dashboards and badges).
|
|
||||||
4. **Scope**: Refonte globale en 1 sprint (all 8 pages + 17 components + tokens + DESIGN.md).
|
|
||||||
5. **Monospace**: data-only — JetBrains Mono for IDs, dates ISO, commands, execution output, MITRE techniques, metrics. Inter stays for body/labels/headers.
|
|
||||||
6. **Mono font**: JetBrains Mono, bundled locally via `@fontsource-variable/jetbrains-mono` (consistent with existing Inter bundle).
|
|
||||||
7. **Modes**: KEEP light + dark both. Toggle stays.
|
|
||||||
8. **Animations**: **Brutalist — zero transition**. Remove all `transition-*` utilities, focus rings sharp, hover instantaneous.
|
|
||||||
9. **Display scale reduction**: locked. `display-xxl 72→40`, `display-xl 56→32`, `display-lg 44→28`, `display-md 32→24`, `display-sm 24→20`, `display-xs 20→16`. Headers stay modest in terminal aesthetic — no editorial flourish at hero scale.
|
|
||||||
|
|
||||||
## §1 — Pre-work checks (team-lead, before dispatch)
|
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 `$ <command>` header line; `executed_at` is set from the first task's timestamp if empty; the command is appended to `commands` if not already present.
|
||||||
|
|
||||||
- [ ] Confirm `tasks/lessons.md` has nothing contradicting this brief
|
## §1 — Mythic adapter contract (pinned from MythicMeta/Mythic_Scripting client source)
|
||||||
- [ ] Verify uncommitted `.claude/agents/frontend-builder.md` patch (Skill mandatory) is restored in worktree — sprint hygiene
|
|
||||||
- [ ] Send plan to **spec-reviewer** for 2-pass approval (vs SPEC.md, vs §0 binding decisions). MUST be APPROVED before any code touches `frontend/`.
|
|
||||||
- [ ] After approval: dispatch frontend-builder with this todo as brief.
|
|
||||||
|
|
||||||
## §2 — Sprint hygiene (commit #1)
|
- Transport: POST `https://<host>:7443/graphql`, header `apitoken: <token>` (Hasura behind nginx).
|
||||||
|
- Issue task: mutation `createTask(callback_id: <display_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.
|
||||||
|
|
||||||
- [ ] `chore(agents): frontend-builder must invoke Skill frontend-design before UI work` — lands BEFORE design work so the agent itself triggers the Skill on first call this sprint.
|
`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`
|
||||||
|
|
||||||
## §3 — Foundation: DESIGN.md + tokens (commits #2–#4)
|
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).
|
||||||
|
|
||||||
### §3.1 DESIGN.md rewrite (commit #2)
|
## §2 — Backend (backend-builder) — milestones M1→M4
|
||||||
|
|
||||||
- [ ] Replace current HP-catalog doc (346 lines, off-brand) with terminal-SOC spec covering:
|
**Data model (1 Alembic migration):**
|
||||||
- **Overview**: brutalist BAS Purple Team console, angular surfaces, semantic color signals, data-monospace hybrid
|
- `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.
|
||||||
- **Colors**: keep all existing tokens with **role redefinition** for terminal-SOC context. Primary = neutral action. Bloom-deep/coral = destructive/alert. ADD `success` (green) + `warn` (amber) — locked §0 D3 — with light + dark variants and WCAG AA contrast on slab and canvas surfaces
|
- `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.
|
||||||
- **Typography**: Inter (body/headers/labels) + JetBrains Mono (data). Concrete tier table with size/weight/line-height
|
|
||||||
- **Layout**: tighter spacing (replace `section 80px` → `section 48px`; halve card padding on dense surfaces)
|
|
||||||
- **Shapes**: ALL radii = 0 except `rounded-pill` reserved for status badges and avatars
|
|
||||||
- **Components**: re-document `btn-*`, `text-input`, `card-*`, `badge-*`, `nav-*`, `modal-*` with brutalist specs (no shadow, hairline borders, zero transition)
|
|
||||||
- **Do's/Don'ts**: zero rounded on conteneurs; zero transitions; semantic colors only on status surfaces; mono ONLY for data, never headers
|
|
||||||
- **Iteration guide**
|
|
||||||
- [ ] Doc lives in English (in-repo).
|
|
||||||
|
|
||||||
### §3.2 Tailwind token refresh (commit #3)
|
**Endpoints (all admin+redteam; SOC → 403):**
|
||||||
|
- `GET/PUT/DELETE /api/engagements/<id>/c2-config` — GET returns `has_token: bool`, never the token.
|
||||||
|
- `POST /api/engagements/<id>/c2-config/test` — connectivity check via adapter.
|
||||||
|
- `GET /api/engagements/<id>/c2/callbacks` — active callbacks.
|
||||||
|
- `POST /api/simulations/<id>/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/<id>/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/<id>/c2/callbacks/<cid>/history?page=` — paginated callback history for import.
|
||||||
|
- `POST /api/simulations/<id>/c2/import` `{task_display_ids: [int]}` — import selected tasks (source=import) + decision 11 mapping.
|
||||||
|
|
||||||
- [ ] `frontend/tailwind.config.ts`:
|
**Milestones:** M1 crypto service (Fernet) + migration + config CRUD + test endpoint → M2 callbacks + execute → M3 poll-on-read status/output + mapping → M4 history + import.
|
||||||
- `borderRadius`: keep `none: 0`, keep `pill: 9999px`. Drop or stop using `xs/sm/md/lg/xl` for surfaces — keep only if a badge variant needs `2px`
|
**Tests:** pytest with mocked adapter (~35-45 new), zero live HTTP. Crypto round-trip, RBAC 403 matrix, mapping idempotence, migration up/down.
|
||||||
- ADD `fontFamily.mono`: `['JetBrains Mono Variable', 'JetBrains Mono', 'ui-monospace', 'monospace']`
|
|
||||||
- ADD semantic colors (locked §0): `success: { DEFAULT, soft }` (green) + `warn: { DEFAULT, soft }` (amber). Pull dark-mode variants from CSS vars too. Suggested anchors — `success #16a34a` (dark `#22c55e`), `warn #d97706` (dark `#f59e0b`); design-reviewer audits WCAG AA at both modes.
|
|
||||||
- Reduce `display-*` scale (locked §0): `display-xxl 72px → 40px`, `display-xl 56→32`, `display-lg 44→28`, `display-md 32→24`, `display-sm 24→20`, `display-xs 20→16` — terminal headers are modest
|
|
||||||
- Drop `tracking[0.7px]` and uppercase from `button-md` (still ALLCAPS via class but no letter-spacing)
|
|
||||||
- Drop shadow tokens or keep but ensure no component class applies them
|
|
||||||
- [ ] `frontend/src/styles/index.css`:
|
|
||||||
- Drop `font-size: 16.5px` root bump (back to `16px` standard)
|
|
||||||
- Set body `line-height: 1.4`, tighten headings to 1.1
|
|
||||||
- Rewrite `.btn-primary/ink/outline/outline-ink`: `rounded-none`, NO `transition-colors`, keep `uppercase`, drop `tracking-[0.7px]`, keep `h-11` (touch target)
|
|
||||||
- Rewrite `.text-input`: `rounded-none`, focus border-primary sharp (no halo), no transition
|
|
||||||
- Rewrite `.card-product` and any `.card-*`: `rounded-none`, no shadow, 1px hairline border for separation
|
|
||||||
- Rewrite `.badge-pill-*`: keep `rounded-pill` ONLY here (status badges); strip uppercase if applied
|
|
||||||
- Rewrite `.modal-backdrop`: same dark backdrop, no rounded for the modal frame itself
|
|
||||||
- ADD `.mono` utility or rely on Tailwind's `font-mono` (preferred) for data cells
|
|
||||||
|
|
||||||
### §3.3 JetBrains Mono bundle (commit #4)
|
## §3 — Frontend (frontend-builder)
|
||||||
|
|
||||||
- [ ] `cd frontend && npm i @fontsource-variable/jetbrains-mono`
|
- **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.
|
||||||
- [ ] `frontend/src/styles/fonts.css`: add `@import '@fontsource-variable/jetbrains-mono'`
|
- **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.
|
||||||
- [ ] No CDN. Confirms via `npm ls @fontsource-variable/jetbrains-mono`.
|
- **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.
|
||||||
|
|
||||||
## §4 — Component sweep (commit #5)
|
## §4 — Docs & hygiene
|
||||||
|
|
||||||
Rule: `rounded-*` → `rounded-none` unless explicitly an avatar or status pill; remove `transition-*`; data text → `font-mono`.
|
- **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.
|
||||||
|
|
||||||
- [ ] `Layout.tsx`: nav-bar/utility-strip already angular — confirm. Remove `transition-colors` on theme button and hover-underline transitions. Mono font for any data label exposed (e.g. user.role pill).
|
## §5 — Pipeline & sequencing
|
||||||
- [ ] `StatusBadge.tsx`: KEEP rounded → switch to `rounded-pill` (it's a status pill, locked exception). Audit semantic mapping (planned/active/closed → semantic tokens once added).
|
|
||||||
- [ ] `SimulationStatusBadge.tsx`: same — `rounded-pill`, semantic colors aligned with new tokens.
|
|
||||||
- [ ] `FormField.tsx`: angular inputs (already via `.text-input` recipe — confirm).
|
|
||||||
- [ ] `EmptyState.tsx`: angular wrapper. No rounded illustration container.
|
|
||||||
- [ ] `ErrorState.tsx`: angular. Bloom-deep border-left if signalling.
|
|
||||||
- [ ] `LoadingState.tsx`: drop any rounded spinner background. Spinner shape ok.
|
|
||||||
- [ ] `ConfirmDialog.tsx`: angular modal. Buttons via new `.btn-*` recipes.
|
|
||||||
- [ ] `Toast.tsx`: angular. Semantic color border-left strip.
|
|
||||||
- [ ] `ExportEngagementButton.tsx` (sprint 6): angular dropdown menu. Audit `rounded-*` in the menu/item classes.
|
|
||||||
- [ ] `MitreMatrixModal.tsx`: angular modal. Cells already grid — confirm no rounded.
|
|
||||||
- [ ] `MitreTechniquePicker.tsx`: angular dropdown.
|
|
||||||
- [ ] `MitreTechniquesField.tsx`: angular chips.
|
|
||||||
- [ ] `MitreTechniqueTag.tsx`: angular tag (NOT pill — terminal tag, not a status). Decide once and apply consistently across MITRE surfaces.
|
|
||||||
- [ ] `TemplatePickerModal.tsx`: angular modal.
|
|
||||||
- [ ] `SimulationList.tsx`: angular table. Data cells (commands, executed_at, MITRE techniques) → `font-mono`.
|
|
||||||
- [ ] `ProtectedRoute.tsx`: no visual surface, skip.
|
|
||||||
|
|
||||||
## §5 — Page sweep (commit #6)
|
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).
|
||||||
|
|
||||||
For each page: header/body/footer review, replace rounded card containers with angular hairline-bordered containers, ensure data cells use mono.
|
## §6 — Risks
|
||||||
|
|
||||||
- [ ] `LoginPage.tsx`: angular form card. Drop ornament.
|
- **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.
|
||||||
- [ ] `EngagementsListPage.tsx`: angular table container (currently `.card-product` with rounded-xl). Data cells (dates) → mono.
|
- **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 `<binary> ` so `execution_result` never silently corrupts.
|
||||||
- [ ] `EngagementDetailPage.tsx`: angular header section. Engagement metadata (start/end dates, IDs, created_at) in mono. Simulations table covered via SimulationList.
|
- **R3 — secret at rest**: Fernet + env key; key rotation out of scope (documented limitation).
|
||||||
- [ ] `EngagementFormPage.tsx`: angular form. Date inputs ok.
|
- **R4 — polling load**: poll-on-read touches only incomplete tasks of the open simulation — bounded.
|
||||||
- [ ] `SimulationFormPage.tsx`: angular form. Commands textarea → mono.
|
- **R5 — e2e drift**: sprint 7 redesign never re-validated by Playwright; the full re-run in §5.5 surfaces any breakage — budget fix time.
|
||||||
- [ ] `TemplatesListPage.tsx`: angular list.
|
|
||||||
- [ ] `TemplateFormPage.tsx`: angular form. Commands field → mono.
|
|
||||||
- [ ] `UsersAdminPage.tsx`: angular table. Username column → mono (it's an ID).
|
|
||||||
|
|
||||||
## §6 — Test refresh (commit #7)
|
## §7 — Definition of Done
|
||||||
|
|
||||||
- [ ] `cd frontend && npm run test -- --run` — identify failing assertions on class names (`rounded-xl`, `card-product`, etc.). Update tests to use semantic queries (role, name, data-testid) where possible; if test asserts on visual class, update assertion to the new class.
|
- pytest green (256 baseline on main + new), vitest green (139 baseline + new), Playwright full suite green (223 baseline + new C2 specs).
|
||||||
- [ ] No new vitest tests added (visual sprint, behavior unchanged).
|
- spec-reviewer (plan) + design-reviewer (UI) + code-reviewer (diff) all APPROVED.
|
||||||
- [ ] Playwright e2e: should be `data-testid`-driven — run full suite to confirm no regression. If breakage, fix the testid not the test logic.
|
- SPEC.md, README.md, CHANGELOG.md updated. No secret, key or IP hardcoded anywhere.
|
||||||
|
- PR #11 opened via `make open-pr`.
|
||||||
## §7 — Reviews
|
|
||||||
|
|
||||||
- [ ] **spec-reviewer** (pre-dispatch, §1): plan validated vs SPEC.md and §0 binding decisions
|
|
||||||
- [ ] **frontend-builder** (§2-§6): implements, runs typecheck/lint/test, delivers screenshots for design-reviewer (every page + key states, light+dark)
|
|
||||||
- [ ] **design-reviewer** (post-frontend): reviews screenshots + diff vs new DESIGN.md. Brutalist consistency, mono-discipline (only data), zero-rounded discipline.
|
|
||||||
- [ ] **code-reviewer** (post-design): reviews frontend diff for duplication, lost reuse, dead code.
|
|
||||||
- [ ] **test-verifier**: skipped this sprint (no new US, no behavior change).
|
|
||||||
- [ ] **backend-builder**: idle, no work this sprint.
|
|
||||||
|
|
||||||
## §8 — Git workflow
|
|
||||||
|
|
||||||
- [ ] Branch: `sprint/7-design` (already created from origin/main)
|
|
||||||
- [ ] Commits: conventional, one per logical group (§2 to §7)
|
|
||||||
- [ ] PR via `make open-pr` (Gitea pattern, per memory)
|
|
||||||
- [ ] PR body in `tasks/pr-body-sprint-7.md`
|
|
||||||
- [ ] CHANGELOG.md sprint 7 section
|
|
||||||
|
|
||||||
## §9 — Risks & mitigations
|
|
||||||
|
|
||||||
- **R1 — Tests break en masse**: many vitest specs may assert on class strings (e.g., `rounded-xl` on cards). Mitigation: update assertions to semantic queries; budget half a phase to test repair.
|
|
||||||
- **R2 — Dark mode contrast lost**: angular + new semantic colors may break WCAG AA contrast on dark slab. Mitigation: design-reviewer audits both modes; adjust the dark variant hex to meet WCAG AA. Rollback the success/warn family only if no accessible green/amber is achievable on the dark slab.
|
|
||||||
- **R3 — Mono overflow**: JetBrains Mono is wider than Inter at same px. Cell widths in tables may overflow. Mitigation: keep `table-layout: fixed` and `word-break: break-word` (pattern reused from PDF export CSS sprint 6).
|
|
||||||
- **R4 — DESIGN.md rewrite churn**: replacing 346 lines is a big diff. Mitigation: rewrite atomically in commit #2, keep token names consistent so downstream commits don't drift.
|
|
||||||
- **R5 — User taste mismatch**: "Bloomberg/SOC" may not match user's mental image. Mitigation: design-reviewer screenshots → user check-in BEFORE merge.
|
|
||||||
|
|
||||||
## §10 — Definition of Done
|
|
||||||
|
|
||||||
- [ ] All §0 decisions reflected in DESIGN.md + tokens + components + pages
|
|
||||||
- [ ] `npm run typecheck` clean
|
|
||||||
- [ ] `npm run lint` clean
|
|
||||||
- [ ] `npm run test -- --run` all green
|
|
||||||
- [ ] Backend untouched — `git diff origin/main -- backend/` empty
|
|
||||||
- [ ] Playwright e2e green (223 baseline preserved)
|
|
||||||
- [ ] Screenshots delivered (light + dark) for every page + key states
|
|
||||||
- [ ] DESIGN.md rewritten, no HP/Forma/wordmark/chevron references
|
|
||||||
- [ ] CHANGELOG.md sprint 7 section
|
|
||||||
- [ ] PR opened on Gitea
|
|
||||||
- [ ] User merges PR → sprint closed → team idle ready for sprint 8
|
|
||||||
|
|
||||||
## §11 — Lessons being applied from prior sprints
|
|
||||||
|
|
||||||
- **SPEC/DESIGN commit-first**: DESIGN.md rewrite is commit #2 (after sprint hygiene). No design churn mid-sprint.
|
|
||||||
- **spec-reviewer 2-pass**: APPROVED before dispatch, not after.
|
|
||||||
- **Team idle policy**: 6 agents already mounted, no shutdown until PR merged.
|
|
||||||
- **frontend-builder MUST invoke `Skill frontend-design`** before UI work (the patch commits as #1, takes effect immediately for the same sprint).
|
|
||||||
|
|||||||
Reference in New Issue
Block a user