d542b08ced74361dc686d126e7f46a3650cafe68
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a1e5a4af8c |
fix(webui): validate externally influenced event_type at the timeline boundary (#637)
Review #526 found that event_type reached the serialized timeline payload without crossing the redaction/validation boundary every other free-text field on the same event crosses. A synthetic secret-shaped 40-hex canary was redacted through message but survived verbatim through event_type on the same control-plane record. CTH heading path: CTH_TYPES is now the single authority for what a CTH type may be. canonical_thread_handoff.is_known_cth_type() is that authority, used by format_cth_body (write), assess_cth_comment (assess), and now the read path too; parse_cth_comment reports membership as cth_type_known and stays total. adapt_cth_comments serializes handoff:<type> only for a declared type and otherwise emits the constant handoff:unrecognized, so arbitrary, malformed, secret-shaped, or whitespace-manipulated heading content never becomes an event_type. Control-plane path: a stored event_type is treated as source data. _safe_cp_event_type accepts only an ordinary identifier that is not a bare secret-shaped hex run and that a redaction pass leaves unchanged; anything else fails closed to the constant unsafe:redacted and marks the event sensitive. The value is never emitted verbatim, never partially sanitized, and never rewritten into a different valid-looking type. Remaining serialized-field audit: event_key ids must be plain numeric identifiers, the CTH adapter refuses a scope it cannot express, per-source failure reasons are redacted (they can quote an authenticated fetch error), and echoed scope/filter values are guarded so reflection is not a bypass. Legitimate values are preserved: every declared CTH type, the real producer types (assigned, lease_released, lease_adopted, dependency_edge_state_change, allocation, pr.opened, lease.renew), and the existing issue, PR, evidence, and full-SHA references. Tests seed the canary independently through both event_type paths with a benign message, so message redaction cannot be why they pass; each inspects event_type directly, asserts the canary is absent from the complete serialized payload, and asserts scan_for_secrets finds nothing. tests/test_webui_timeline.py 64 passed, 15 subtests pytest -k webui 362 passed, 285 subtests pytest -k redact 79 passed tests/test_canonical_thread_handoff.py tests/test_control_plane_db.py 29 passed Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> |
||
|
|
8ba1c5b87c |
fix(webui): make timeline session filter truthful and redact evidence refs (#637)
Remediates the two blocking findings in review #522 on PR #849. F1 — the session filter dimension was dead end to end. No source could produce an event carrying a session identifier, so filter_events dropped every event whenever session was supplied and the API answered with a green, empty page. An empty result reads to an operator as "no such session activity", which is a stronger and false claim. Each source now declares which filter dimensions its records can actually carry. The control-plane events table is (event_id, work_item_id, event_type, message, created_at) and records no session, so that source declares the session dimension unsupported rather than pretending to answer it; the dead read of a non-existent session_id column is removed. A CTH handoff comment declares its own Session field, so the handoff adapter populates session_id from that declared field — authoritative source data, never inferred from an actor, work item, or message text. When no source that ran can carry a requested dimension, load_timeline refuses with ok=false and a structured error naming the unsupported filters and the per-source reason, and the route answers 422. A source that can answer the dimension and simply matched nothing still returns 200 with an honest empty page. Ordering, pagination, and the issue/PR filters are unchanged. F2 — evidence_refs bypassed redaction and could emit a credential verbatim. proof and decision were passed to _extract_evidence_refs before redaction, and the SHA pattern matched any 7-40 character lowercase hex run, which is exactly the shape of a Gitea access token. Redaction now runs first and every derived value is taken from the redacted text. A commit reference is recognised only where the source text declares one (commit, head, base, sha, ...), so an undeclared hex run is never lifted out of prose into a structured field; this also drops the ordinary-word noise the reviewer noted. Every reference is then independently revalidated against an allowed shape and a second redaction pass immediately before serialization, failing closed by dropping anything unproven and flagging the event sensitive. Actor is redacted for the same reason, and a secret-shaped session value is dropped rather than emitted. Legitimate issue, PR, short-SHA and full 40-character SHA references stay usable. Tests: 16 added. Session filtering is now driven through the CTH adapter and the composed load_timeline/API path rather than a hand-built WorkflowEvent, covering a match, an honest empty result, pagination and ordering under the filter, the 422 refusal, and the per-source support declaration. Redaction coverage asserts a synthetic 40-character hex value (not a real credential) appears nowhere in the complete serialized payload including evidence_refs, that the independent revalidation drops unproven references, and that legitimate references still resolve. pytest tests/test_webui_timeline.py: 42 passed (was 26). pytest -k webui: 340 passed, 270 subtests passed (was 324). Full suite: 4607 passed, 12 failed, 6 skipped, 684 subtests passed — the same 12 failures as the master baseline, none under webui/. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> |
||
|
|
25bc2a3291 |
feat(webui): workflow-event and conversation timeline model (Closes #637)
Phase 1 child of the Web Console epic #631. Adds a durable, versioned WorkflowEvent schema with per-source adapters and a read-only query API so operators can browse a unified timeline of workflow events, decisions, tool calls, and handoffs instead of scattered evidence. - webui/timeline.py (new): versioned WorkflowEvent schema; control-plane event adapter and Gitea CTH handoff-comment adapter; read-only mode=ro control-plane reader; conjunctive filter by issue/PR/session; stable (timestamp, source_rank, event_key) ordering; bounded pagination; fail-soft per-source status; redaction at the boundary, fail closed. - webui/app.py: GET /api/v1/timeline read-only route with thread-scoped, fail-soft handoff comment source. - tests/test_webui_timeline.py (new): schema, adapters, redaction of secret-like payloads, filter/sort/pagination, scoped CP reader, fail-soft composition, and API integration. - docs/webui-local-dev.md: timeline route and field-authority notes. Read-only Phase 1; no mutation of historical events; no full chat replay; no unredacted tool-argument storage. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> |