feat(secrets): exposure detection — exposed flag, chat badge, mask-on-render, exposure push (#108) #118
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab!118
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/108"
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?
A secret value appearing in a run's transcript is a disclosure the operator learns about. The chat read paths now scan rendered content against the repo's secrets — exact plus base64/hex/urlencoded forms via the #104 matcher kernel — and contain the blast radius in lab's own UI. The provider-owned transcript on disk cannot be rewritten, so rotation stays the remediation; masking is defense-in-depth.
What ships
secrets.Redactor(derived from the #104 matcher's form derivation, concurrency-safe, idempotent) masks every operator-visible string as[REDACTED:NAME]— message text, tool chip title/input/output, every dialog string, the pending dialog — and reports which secrets were hit in the same pass, so flag and mask can never diverge. Runs at BOTH read paths:Service.Read(HTTP, including ended runs' history) and the tailer tick, ordered before the notify gate snapshots a needs-input body, so no push payload can carry a plaintext value.repo_secretsgainsexposed_run_id/exposed_at(migration 0010, both dialects), set by an atomic first-writer-winsMarkRepoSecretExposed— the unset→exposed edge — cleared by rotate, removed by delete.runID:exposed:NAMEso tag-coalescing never swallows an exposure; route to the run's chat. The DB edge guarantees exactly-once per episode; a fresh hit after rotation is a new edge and pushes again. No notifyGate debounce — a disclosure shouldn't wait out a flap window.run.changedpublish on the edge.secrets.Source(encrypted-blobs loader + decrypt closure, wired in cmd/lab) hands chat a per-repo redactor; chat never touches vault plumbing, and no log line, event payload, or API response carries a value — names and ids only. Zero-secret repos short-circuit to a nil redactor (no per-message overhead). Broker placeholders ([REDACTED:NAME]text) are inert by construction; contained hits (#105/#106) stay quiet.Also fixes #117 — main is broken
#112 (#104) and #113 (#111) both claimed goose migration version 0008, and goose refuses duplicates: every
Store.Openon merged main fails, taking lab startup and most test packages down. This PR renames0008_run_title→0009(run_title merged last; a deployment that applied 0008 as repo_secrets picks up 0009 cleanly) and takes 0010 for the exposure columns.Verification
go test ./...green (only exceptions:internal/tmuxxintegration tests, which fail identically on a clean checkout in this sandbox — tmux sessions cannot start here; CI installs tmux).golangci-lint run ./...: 0 issues;gofmt -lclean.npm run build(tsc + vite) green,npm test589/589 across 37 files,npm run lintclean.Closes #108
Closes #117
🤖 Generated with Claude Code
https://claude.ai/code/session_01SrMDj8ayE71AzA6M46pri2