feat(secrets): sanitize run-token tracker writes against secret values #107
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab#107
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
Server-side harness layer: every tracker write that arrives through a run token — PR/change-request titles and bodies, issue comments, issue create — is scanned against that repo's secret values using the matcher from #104 (exact + base64/hex/urlencoded forms). A hit is rejected with a 400 naming the matching secret (name only, never the value) so the agent can rewrite and retry. Nothing is silently redacted — a body that says
[REDACTED]mid-sentence on the forge is worse than a clean retry.Scope:
Acceptance criteria
labctl pr create/issue comment/issue createwith a secret value (any matched form) in title or body is rejected 400, error names the secret onlyBlocked by
Agent Brief
Category: enhancement
Summary: Every tracker write arriving through a run token — PR/change-request create, issue create, issue comment — is scanned server-side against the repo's secret values; a hit is rejected with a 400 naming the matching secret (never the value) so the agent rewrites and retries.
Current behavior:
Run-token tracker writes flow through the agentapi into the Tracker seam, which fans out to the two bindings (forge REST client and builtin change requests). No content scanning happens: a title or body containing a secret value would land verbatim on the forge. The shared streaming matcher and decrypted-value access exist since #104.
Desired behavior:
A scan sits in front of the Tracker seam — one site covering both bindings identically — checking title and body of every run-token write against the repo's secret values in all four matched forms (exact, base64, hex, URL-encoded). A hit rejects the write with a 400 whose error names the secret only; nothing is silently redacted — a body reading
[REDACTED]mid-sentence on the forge is worse than a clean retry. Operator PAT write paths are not scanned (the operator is not the threat model). Zero-secret repos skip the scan with negligible overhead.Key interfaces:
secrets.NewMatcher(values)over the write's title + body; values decrypted via the store's repo-secrets access from #104Acceptance criteria:
labctl pr create/issue create/issue commentwith a secret value (any matched form) in title or body is rejected 400; the error names the secret onlyOut of scope:
Landing audit — PR #114 → verdict: PASS
Verification signal: repo CI (
ci / native) is red, but the only failing package isinternal/tmuxx, whose tmux integration tests fail identically on a cleanorigin/main(sessions exit immediately — a sandbox/infra limitation, not this PR). I reproduced locally: PR branch and clean main both failtmuxxand onlytmuxx. This forge does not gate merges on that signal (the prior 18 PRs merged against the same red).What I ran (PR branch
afk/107, CGO off):go build ./...— cleango test ./internal/tracker/secretscan/... ./internal/agentapi/... ./internal/httpapi/...— all okgofmt -lon changed files — clean;go vet— cleanReview findings:
secretscan.NewResolver); operator API and AFK engine keep the bare registry. Correct seam — above the forge/builtin fan-out, so one decorator covers both bindings.writeTrackerErrormaps*secretscan.BlockedError→ 400 ahead of the 502/500 folds, so a rejected leak is never miscoded as an upstream fault.BlockedError.Error()renders secret name/field/form only — no value in any encoding; package contains no logging.ForRunre-wraps the rescoped tracker, preserving builtin agent attribution through the decorator.mainhad two0008migrations (repo_secrets#112 +run_title#113) → goose duplicate-version boot failure.repo_secretskeeps0008;run_titlerenumbered to0009in both sqlite+postgres dialects; ADR-0040 updated. Confirmed no duplicate versions remain.Conventions: title is Conventional Commits (
feat(secrets): …); headafk/107carries a workingCloses #107.Clean to merge — awaiting free-text go-ahead from the human.