feat(secrets): exposure detection — exposed flag, chat badge, mask-on-render #108
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab#108
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
What to build
Disclosure is an event the operator learns about (grill decision). The chat tailer scans transcript content for the repo's secret values via the matcher from #104 (exact + base64/hex/urlencoded). A hit means the value is actually sitting in a provider-owned transcript file on disk — lab cannot rewrite that file, so lab makes it loud and contains the blast radius in its own UI:
[REDACTED:NAME]in rendered messages (defense-in-depth; the disk copy stays dirty).Contained hits elsewhere (broker redaction #105, blocked push #106) stay quiet — those are the harness working, not incidents. Only a transcript hit is a disclosure.
Out of scope: push notification for the exposure event — one-line follow-up once the notification seam (#98–#101) lands; this slice ships flag + badge + masking only.
Implementation note: scanning needs plaintext values at the tailer — keep the vault coupling behind a narrow interface (e.g. the store hands the tailer a per-repo matcher, not values), and never log the value on a hit.
Acceptance criteria
[REDACTED:NAME](all matched forms)[REDACTED:NAME]placeholders) does NOT trigger the exposed flagBlocked by
Agent Brief
Category: enhancement
Summary: A secret value appearing in a session transcript is a disclosure the operator learns about: the chat tailer detects it, marks the secret exposed (repo secrets page: which run, when, sticky), badges the run's chat header, and masks the value on render. Rotating the secret clears the flag.
Current behavior:
The chat tailer parses provider-owned transcript files and renders messages; it performs no secret scanning. Secrets, the streaming matcher, and rotation landed in #104. Lab cannot rewrite a provider's transcript file on disk — once a value is written there, the copy is permanently dirty; today nothing notices or contains it.
Desired behavior:
The tailer scans transcript content against the repo's secret values (exact + base64/hex/URL-encoded forms). On a hit:
[REDACTED:NAME]; the disk copy stays dirty (defense-in-depth, not remediation).Broker-redacted output (#105's
[REDACTED:NAME]placeholders) must NOT trigger the flag — the placeholder is proof of containment, not disclosure. Contained hits elsewhere (redaction, blocked push) stay quiet; only a transcript hit is a disclosure. Zero-secret repos: the scan is a no-op with no per-message overhead.Key interfaces:
secrets.Matcher(or equivalent), not plaintext values; the tailer never logs a value on a hit. Note the matcher is not concurrency-safe — one per stream/session or serialised accessAcceptance criteria:
[REDACTED:NAME](all matched forms)[REDACTED:NAME]placeholders) does NOT trigger the exposed flagAgent Brief addendum — out of scope:
Agent Brief amendment — exposure push notification is IN scope.
The earlier out-of-scope line (issue body and addendum above) deferred the exposure push until the notification seam landed. It has landed: the Web Push plumbing (#98) and both triggers (#99 needs-input, #100 done-signal) are merged; only #101 (terse lock-screen toggle) remains open, and it is orthogonal — it changes how notifications render, not whether they send. This amendment supersedes that deferral.
Additional desired behavior:
When a transcript hit sets a secret's exposed flag — the unset→exposed edge, not every subsequent match while the flag is already set — send a push notification through the existing notify seam. Follow the landed trigger pattern (ADR-0039): the tailer/exposure site receives an injected notify callback and never imports the push package; the payload is
{title, body, tag, route}with the route pointing at the affected run's chat. Title/body name the secret and the run — never the value. Since rotation clears the flag, a fresh transcript hit after rotation is a new edge and pushes again.Additional acceptance criteria:
Heads-up: #109 (open bug) reports a data race in
Sender.Broadcast's payload handling. Don't work around it in this slice — it's fixed separately; just call the seam normally.Still out of scope: terse/content-free rendering (#101), transcript rewriting, alerting on contained hits (#105/#106).