feat: group, filter, and document the catalog in the web UI (spec Part 2) - #11
Merged
Conversation
Part 2 of tasks/webui-access-and-nav-spec.md, stacked on feat/webui-access. The
left rail was a flat list of 24 techniques.
- grouped by ATT&CK tactic, collapsible, count per group, a multi-tactic technique
under each of its tactics. Ordering logic lives in webui/src/lib/catalogView.ts
so it is unit-testable, and the kill-chain order is stated explicitly rather than
derived: it is neither alphabetical nor numeric, because Reconnaissance is the
first tactic and carries the highest number (TA0043), and Exfiltration (TA0010)
follows Command and Control (TA0011)
- a filter box matching technique id, name, use case id and ATT&CK id at once, so
whichever identifier the operator's ticket carried works. Empty groups disappear
and the header switches to "N of 24"
- log-type toggles for the five render paths the catalog uses
- a Docs tab: GET /api/docs and /api/docs/{id} over a fixed allowlist. The id is a
dictionary key and is never joined onto a path, so traversal resolves to nothing.
Rendered with marked (MIT), lazy-loaded into its own chunk the way xterm already
is, so the Emitter view's first paint is unchanged (main bundle +0.4 kB)
- an anchor control in the run form. POST /api/runs now accepts `anchor` and returns
the resolved anchor_epoch and an anchor_warning
The anchor is the substantive fix here. The web path had no way to set it, so every
live send from the UI carried the deterministic default: CEF eventtime sits at the
anchor while the syslog header is stamped at send time, and on a SIEM that keys on
parsed event time no recent-window rule fires. That is indistinguishable from a
broken detection, which is the ambiguity this project exists to remove. The form's
pre-flight notice states the consequence rather than re-implementing the server's
staleness threshold, so there is no duplicated rule to drift.
docs/ ships with the repository, not the wheel (pyproject packages `replicant*`
only, no MANIFEST.in), exactly like webui/dist. A non-editable install now gets an
explanation from the Docs tab instead of a failure.
Also fixes UAT case CHAIN-16, which could not pass: it asserted the web UI has no
scenario surface by grepping server.py for the literal string "scenario", but that
file imports replicant.scenario.engine, so the grep already returned a hit with no
scenario feature present. It was never a correct check. Now asserts the five
candidate routes 404 and that no scenario control exists in the UI, and App.test.tsx
pins the UI half automatically.
NOT built: the vendor half of spec item 9. All three vendor profiles implement all
six render paths, so all 24 techniques apply to all 3 vendors and the control could
never exclude an entry. Options recorded in tasks/todo.md for DJR to decide.
516 python tests (496 before), 64 frontend (23 before), black/ruff/mypy clean.
Driven in a real browser as well as jsdom.
- check the premise of a spec item before building it. The vendor-applicability filter would have split the catalog zero ways, because all 24 techniques apply to all 3 vendors. A control whose output cannot change is decoration, and it is the same failure as a test that cannot fail, moved into the UI. - pick-a-value decisions get measured, not reasoned about. 8787 was chosen by reasoning about likely collisions and turned out to be held on the author's own machine by an unrelated tool. One lsof would have found it.
404SecNotFound
added a commit
that referenced
this pull request
Jul 29, 2026
Both CLAUDE.md and AGENTS.md said 'Next up, not started: group the catalog by MITRE tactic in the web UI left rail, then a Docs tab'. Both shipped in PR #11. The file every session reads first was telling the next one to build what already exists. This is the third instance of the same failure mode in one day, after the README describing a UI that had moved on and the roadmap section in that same README. The pattern is that a feature PR lands and the orienting documents keep describing the world before it. Replaces it with a v0.3.0 entry, the three conventions the work established, and the actual next items (light theme, responsive layout, live-vendor pass). Also points at the settled vendor licensing position and carries the standing constraint about never claiming CEF certification. AGENTS.md is gitignored and was updated in the working tree to match.
404SecNotFound
added a commit
that referenced
this pull request
Sep 1, 2026
Execution of the 2026-09 five-persona roadmap: 13 buildable survivors across ten PRs (#89-#98). Two changes alter emitted output (marker default-on for non-loopback sends; per-flow packet counts), hence the minor bump. The three lab-gated items (#4 pilot, #10/#11 refining the unbuilt F2/F5) stay behind the launch gate; every timing and delivery claim remains loopback-only until the first observed rule fire. - version 0.9.0 -> 0.10.0 (pyproject + __init__) - CHANGELOG [0.10.0] entry (Added/Changed/Fixed) - README: roadmap-execution bullet + Status badge -> v0.10.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 2 of
tasks/webui-access-and-nav-spec.md. Stacked on #10 — review that first; this PR's base isfeat/webui-access, so the diff shown here is Part 2 only.The problem
The left rail was a flat list of 24 techniques with no filter, no grouping, and no way to reach the reference material without leaving the UI. The run form had no anchor control at all.
What changed
Grouped by ATT&CK tactic, collapsible, count per group, a multi-tactic technique under each of its tactics. The ordering lives in
webui/src/lib/catalogView.tsrather than the component so the rule is unit-testable, and the kill-chain order is written out explicitly instead of derived — it is neither alphabetical nor numeric. Reconnaissance is the first tactic and carries the highest number (TA0043), and Exfiltration (TA0010) follows Command and Control (TA0011). Both obvious sort keys get it wrong, so there is a test that fails under either.A filter box over technique id, name, use case id, and ATT&CK technique id simultaneously, because an engineer arriving from a detection backlog has whichever identifier their ticket happened to carry. Groups that empty out disappear; the header switches to
N of 24.Log-type toggles for the five render paths the catalog uses.
A Docs tab —
GET /api/docs,GET /api/docs/{id}over a fixed allowlist (three vendor CEF references, two catalog research notes). The id is a dictionary key and is never joined onto a path, so a traversal attempt resolves to nothing rather than to a file; there is a parametrised test pinning that. Rendered withmarked(MIT), lazy-loaded into its own chunk the way xterm already is, so the Emitter view's first paint is unchanged (main bundle +0.4 kB, docs chunk 44 kB / 13.8 kB gzip).An anchor control in the run form,
noworfixed, defaulting tonowfor a live send andfixedfor file output.The anchor is the substantive fix
The web path had no way to set the anchor. Every live send from the UI therefore carried the deterministic default: the CEF
eventtimesits at the anchor while the syslog header is stamped at send time. On a SIEM that keys on receipt time nothing looks wrong. On one that keys on parsed event time, no recent-window rule fires — and the operator cannot tell that from a broken detection. That ambiguity is the thing this project exists to remove.POST /api/runsnow acceptsanchor(now,fixed, an epoch, or ISO-8601, through the sameparse_anchorthe CLI uses) and returns the resolvedanchor_epochplus ananchor_warning. The form's pre-flight notice states the consequence rather than re-implementing the server's staleness threshold, so there is no duplicated rule to drift out of sync.docs/is not in the wheelpyproject.tomlpackagesreplicant*only and there is noMANIFEST.in, sodocs/— likewebui/distanddata/— ships with the repository, not the package. The Docs tab reports that plainly on a non-editable install instead of failing. This is the pre-existing packaging gap filed atdocs/end-to-end-debug-audit-2026-07-21.md:194-215; this PR inherits it deliberately rather than pretending otherwise.Fixes a UAT case that could not pass
CHAIN-16 asserted the web UI has no scenario surface by grepping
replicant/web/server.pyfor the literal stringscenario, expecting zero hits. That file importsreplicant.scenario.engineforimplemented_technique_ids, so the grep already returned a hit with no scenario feature present. It was never a correct check. It now asserts what it meant — five candidate routes 404, no scenario control in the UI — andApp.test.tsxpins the UI half automatically.Not built: the vendor half of spec item 9
Item 9 asks for toggles for "vendor applicability and log type". The log-type half is here. The vendor half is not, because it would filter nothing: all three vendor profiles implement all six render paths, the catalog uses five of them, so all 24 techniques apply to all 3 vendors and the control could never exclude an entry.
Shipping a toggle that looks like it does something and cannot is the same call as REP-016, which was left unbuildable rather than shipped dishonestly. Options are recorded in
tasks/todo.mdfor a decision. For what it is worth,actionis the only other axis that actually splits the catalog (6 values);benign_baselineandimplementedare uniform across all 24 and would be equally inert.Verification
516 Python tests (496 after #10), 64 frontend (23 after #10).
black,ruff,mypyclean.Driven in a real browser against a live server, not only in jsdom: the rail renders grouped with counts, filtering on
T1048narrows to3 of 24and correctly lists REP-004 under both Command and Control and Exfiltration, the Docs tab renders the FortiGate reference with working headings, code blocks and tables, the anchor control showsfixedfor file output, and the address bar readshttp://127.0.0.1:9787with the token stripped after load.