docs(m4): CHANGELOG, README, lessons, spec drift fix, todo tick
- CHANGELOG: added M4 section listing endpoints, CLI, volume, persisted settings, picker, and the post-spec-review fixes (custom-URL integrity requirement + /diag/reset consistency + spec drift). Includes the intentional decisions paragraph (seed-time download not image-baked, read endpoints unauthenticated-perm-wise, stdlib over httpx). - README: status bumped to M0–M4, added MITRE quickstart (make seed-mitre + air-gapped path with --source /data/mitre/<file> + --skip-checksum), testing-m<N>.md pointer updated to testing-m4.md, roadmap line. - tasks/spec.md §10 #4: amended "14 tactics Enterprise" → "≥14 tactics Enterprise (la v19 du pin actuel en ship 15)". - tasks/lessons.md: 7 M4 lessons captured (stdlib STIX parsing, decoupling DoD asserts from upstream versions, subtechnique parent resolution, single- transaction safety, custom-URL footgun mitigation, /diag/reset consistency, named-volume permission caveat, podman build cache surprise). - tasks/todo.md: M4 marked ☑. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
34
CHANGELOG.md
34
CHANGELOG.md
@@ -4,6 +4,40 @@ All notable changes to this project will be documented here. Format: [Keep a Cha
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added — M4 (MITRE ATT&CK Enterprise)
|
||||
- **STIX 2.1 parser + upsert** (`app/services/mitre_seed.py`): stdlib-only (`urllib.request` + `hashlib`), pinned to Enterprise v19.0 (`enterprise-attack-19.0.json`, sha256 `df520ea0…`). Parses 25k+ STIX objects → 15 tactics, 222 techniques, 475 sub-techniques in ~1.1 s. Skips revoked + deprecated, resolves sub-technique parents via `relationship[subtechnique-of]` with a `T1003.001 → T1003` dotted-id fallback, copies kill-chain phases into the `mitre_technique_tactics` M2M.
|
||||
- **CLI**: `flask metamorph seed-mitre [--source <path|url>] [--checksum-sha256 <hex>] [--skip-checksum]` (`app/cli.py`). `make seed-mitre` wraps it.
|
||||
- **REST endpoints** (`app/api/mitre.py`):
|
||||
- `GET /api/v1/mitre/tactics`, `/mitre/techniques?tactic=…&q=…`, `/mitre/subtechniques?technique=…&q=…` (paginated, search on name/external_id).
|
||||
- `GET /api/v1/mitre/status` (last_sync, version, source_url, defaults).
|
||||
- `POST /api/v1/mitre/sync` (perm `mitre.sync`) — re-pull on demand.
|
||||
- **Persisted metadata** in `settings`: `mitre_last_sync`, `mitre_version`, `mitre_source_url`.
|
||||
- **Compose volume `metamorph_mitre`** mounted at `/data/mitre/` in the api container — caches the downloaded bundle across restarts. Owned by `metamorph:metamorph`.
|
||||
- **Frontend**:
|
||||
- `<MitreTagPicker>` component: 3-column tactic → technique → sub-technique with multi-select chips, autocomplete on each column. Returns `MitreTag[]` (`kind`, `id`, `external_id`, `name`), ready for M5 templates.
|
||||
- `/mitre` showcase page with status card, admin-gated **Trigger sync** button, picker preview, and `<pre>` payload preview.
|
||||
- Nav adds **MITRE** link for any logged-in user.
|
||||
- **Testing**:
|
||||
- `backend/tests/test_mitre.py` — **12 pytest** (parser, idempotence, checksum mismatch, persisted settings, endpoint variants, perm enforcement) using a hand-crafted minimal STIX bundle (no network in tests).
|
||||
- `e2e/tests/m4-mitre.spec.ts` — **6 Playwright** against the live stack (calls `/mitre/sync` once in `beforeAll`).
|
||||
- `tasks/testing-m4.md`.
|
||||
|
||||
### Fixed (post-M4 spec-review pass)
|
||||
- **Sync integrity guarantee**: `seed_mitre()` now refuses a custom URL without either `expected_sha256` or an explicit `allow_unverified=true`. Closes a "typo in `mitre_source_url` setting routes the seed to attacker JSON" footgun. CLI surfaces this via `--checksum-sha256` / `--skip-checksum`; API via `{"source", "expected_sha256", "allow_unverified"}` body.
|
||||
- **`/diag/reset` consistency**: now truncates the `mitre_*` tables alongside `settings` so `GET /mitre/status` and `GET /mitre/tactics` agree after a reset (previously: catalogue rows persisted, but `mitre_last_sync` got wiped → status lied).
|
||||
- **Spec drift §10 #4**: amended "14 tactics" → "≥ 14 tactics (v19 ships 15)" to reflect MITRE v8+ reality.
|
||||
|
||||
### Decisions (intentional)
|
||||
- **Bundle "embarqué" interpreted as seed-time download + named-volume cache**, not "binary baked into the Docker image". Keeps the image ~150 MB, makes version bumps a constant edit, plays nicely with `make seed-mitre` re-runs. Air-gapped operators copy the file into the volume + pass `--source /data/mitre/<file>`.
|
||||
- **Read endpoints unauthenticated-perm-wise but auth-required**: MITRE data is public reference material — no `mitre.read` perm. Status endpoint is similarly open (under `@require_auth`) to keep `/mitre/status` simple for the UI badge.
|
||||
- **No `requests` / `httpx` dep added**: stdlib `urllib.request` is enough and avoids inflating the image.
|
||||
|
||||
### Validated end-to-end (M4 DoD)
|
||||
- `make clean && make up && make migrate && make seed-mitre` → 15 tactics / 222 techniques / 475 sub-techniques / 254 links / 0 orphans / ~1.1 s.
|
||||
- `make test-api` → **51 pytest pass** (1 health + 8 schema + 15 auth + 15 RBAC + 12 MITRE) in ~5 s.
|
||||
- `make e2e` → **34 Playwright pass** (8 M0 + 4 M1 + 8 M2 + 8 M3 + 6 M4) in ~18 s.
|
||||
- Spec-reviewer PASS after fixes applied.
|
||||
|
||||
### Added — M3 (RBAC: groups, permissions, users)
|
||||
- **Permission catalogue** (`app/services/permissions_seed.py`): 31 atomic codes across 10 families (`user`, `group`, `invitation`, `test_template`, `scenario_template`, `mission`, `detection_level`, `setting`, `mitre.sync`). Seeded at boot **and** after `/setup` to handle a freshly truncated DB. Idempotent + additive on system groups (never removes a perm).
|
||||
- **Default group bindings**: `admin` = all 31 codes; `redteam` = 8 (catalogue read + mission.{read,create,update,archive,write_red_fields} + detection_level.read); `blueteam` = 5 (catalogue read + mission.{read,write_blue_fields} + detection_level.read).
|
||||
|
||||
Reference in New Issue
Block a user