feature/m4-mitre #1
Reference in New Issue
Block a user
Delete Branch "feature/m4-mitre"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
- GET /api/v1/mitre/tactics, /techniques?tactic=&q=, /subtechniques?technique=&q= (paginated, ILIKE search on name + external_id, @require_auth only — MITRE is public reference material). - GET /api/v1/mitre/status: last_sync, version, source_url + the pinned defaults (default_url, default_version) for the SPA badge. - POST /api/v1/mitre/sync: @require_perm("mitre.sync"). Body supports {source, expected_sha256, allow_unverified} — defaults inherit the pin. - /diag/reset now also TRUNCATEs the mitre_* tables alongside settings so a freshly-reset stack has GET /mitre/status and GET /mitre/tactics agree ("no data, no last_sync"). Previously the catalogue persisted while the metadata was wiped, leaving status to lie. The e2e suite re-syncs in beforeAll. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Visual parity pass against attack.mitre.org/# per user feedback ("trop dense, illisible, je veux la même représentation"): - Layout switched from flex+fixed-width 224px columns to a CSS grid of `repeat(N, minmax(0, 1fr))` so the 15 tactic columns share the container width equally. No more horizontal scroll on a standard desktop. - Cells now show NAME ONLY (matches mitre.org). The external_id (TA00xx / T1xxx / T1xxx.xxx) is preserved in the chip selection bar at the top and in the `title` hover tooltip on every cell — surfaces on demand, doesn't consume cell real estate. - Font: switched to `font-sans` (IBM Plex Sans) at `text-xs` (12px) across cells, matching the mitre.org typography. Headers use the same family at the same size with a 10px sub-line for the technique count. - Chevron icons: ▸ (collapsed) / ▾ (expanded) — small, sub-technique count rendered inline beside the chevron. - Helper line below the matrix tells the user where the IDs went. Spec §F2 + testing-m4.md walkthrough rewritten to lock the new sizing rules in (font-xs, no external_id in cells, hover/chip for the ID, no horizontal scroll). spec-reviewer will see the matching contract. DoD: make e2e → 34 passed. Selectors (data-testid + aria-pressed) unchanged so the existing M4 e2e test still walks the new layout end-to-end. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Two follow-up tweaks per user feedback ("wrap sur les mots, agrandit le cadre"): - Full-bleed wrapper: the matrix breaks out of the page's max-w-page (1400px) constraint via `margin: 0 calc(50% - 50vw)` + `width: 100vw`, mirroring the 60px page padding internally. On wide viewports the picker now uses the ENTIRE viewport width, so column widths grow proportionally — names that used to wrap on 3 lines now fit on 1-2. - Word-only wrapping: replaced `break-words` (overflow-wrap: break-word, which falls back to mid-word breaks) with `break-normal hyphens-none` (overflow-wrap: normal + word-break: normal). Cells break only at word boundaries; if a single word is longer than the cell it overflows visually rather than splitting `Aut\nhentication`-style. The grid is configured `minmax(7rem, 1fr)` so the minimum column is wide enough for every single word in MITRE v19 names, and stretches with available space. - Spec §F2 rewritten as a bullet contract locking in: full-bleed, 15 cols minmax(7rem, 1fr), word-only wrap, font sans 12px / count 10px, headers/ cells show name-only with external_id on hover + chips. Future spec-reviewer passes can grade against this. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Spec-reviewer PASS pointed two factual nits: - MitrePage helper text still referenced the old 3-column drill-down ("Pick a tactic on the left, then a technique..."). Reworded for the flat matrix with the ▸ glyph + hover-for-id idiom. - testing-m4.md + CHANGELOG were stale at 51/12; the actual counts are 53/14 after the GET /mitre/matrix tests landed. Reconciled. No code-path change, no e2e fallout — DoD remains 53 pytest + 34 Playwright. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Six post-code-review fixes, applied before opening the PR per project workflow (spec-review + code-review both gate the merge): 1. SSRF allowlist on `/mitre/sync`. Host must be in MITRE_ALLOWED_HOSTS (defaults to `raw.githubusercontent.com`, env-overridable). Closes "admin holding `mitre.sync` pivots api container at 169.254.169.254 / internal mirrors" via a typo'd URL. New `MitreSourceForbidden` → 400 `source_forbidden`; checked at the top of `_download()` so it kicks in before any I/O. 2. `pg_advisory_xact_lock(hashtext('mitre.seed'))` at the top of the seed transaction. Two concurrent `/mitre/sync` requests now serialise across the DELETE+INSERT of `mitre_technique_tactics`; previously they could both wipe the M2M and one would fail the unique constraint on re-insert. 3. Typed SyncResult contract. Pydantic `SyncResultOut` on the Flask side `model_validate`s the dict before returning — single source of truth for the response shape, mirrored by a `MitreSyncResult` TS interface (next commit). The `as Record<string, unknown>` + `as { duration_ms }` cast in MitrePage is gone. 4. N+1 in dotted sub-technique fallback removed. Built `{external_id → technique_id}` once at function entry. Currently a no-op against MITRE official (0 orphans), but a latent footgun for partial / older bundles. 5. `SETTING_VERSION` cleared explicitly when `source != MITRE_DEFAULT_URL`. Previously it kept the stale pin label, so `/mitre/status` lied after a custom-URL re-sync. 6. `/mitre/sync` 500s no longer echo `str(e)` to the client — URLError / psycopg / Pydantic text now lives in the JSON log only. Public response stays `{"error": "internal_error"}`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>ascast 54adfee690Mirrors the backend Pydantic `SyncResultOut` in TS so the mutation result is properly typed end-to-end. `(res as { duration_ms: number })` cast removed from MitrePage.tsx; `apiPost<MitreSyncResult>` carries the contract. Also annotated the unused query-key factories in mitre.ts so the next reader knows they're parked for M5 template-form consumption (not dead). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>- 5 new pytest covering paths the code-reviewer flagged as un-asserted: * `test_seed_refuses_file_url` — `file://` scheme rejected before I/O (was the SSRF-to-local-FS vector). * `test_seed_refuses_disallowed_https_host` — non-allowlisted HTTPS host rejected with `MitreSourceForbidden`. * `test_seed_refuses_custom_url_without_sha` — end-to-end guard that `seed_mitre(source=<custom URL>, expected_sha256=None, allow_unverified=False)` raises `MitreSeedError`. * `test_dotted_id_fallback_resolves_orphan_subtechnique` — STIX bundle without `relationship[subtechnique-of]` still attaches T1059.001 to T1059 via the dotted-id convention. * `test_seed_clears_version_when_source_is_not_default` — seed from a local path leaves `settings.mitre_version` NULL (no stale pin). - Existing `test_checksum_mismatch_aborts` reworked to monkey-patch `_ensure_host_allowed` so `file://` can drive the test past the allowlist gate (was relying on file:// being accepted before CR1). - Removed unused `uuid` import. - e2e: assertions on `tactics_upserted`/`techniques_upserted`/ `subtechniques_upserted` switched from `>= 14/180/400` thresholds to `=== 15/222/475` exact counts pinned to MITRE Enterprise v19.0 + 0 orphans. Catches parser regressions that would silently include revoked rows. Bump alongside MITRE_VERSION when re-pinning. - e2e: `Math.random()` → `crypto.randomUUID().slice(0, 8)` for unique test-run emails (collision-safe across parallel CI workers). DoD: 58 pytest pass (was 53), 34 Playwright pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>