Development record
autonomous-cloud-runs-in-sibling-repos-leaked-harness-attrib
Summary
Adds a shared privacy-pattern class for two harness-leak shapes — a live session
URL and a harness-appended "Generated with …" attribution footer — and enforces
it on committed and stored text via abcd audit and docs-lint, and exposes it
for outbound artefacts (PR bodies, issue comments) as a primitive the posting
routine calls: abcd owns no forge client, so the outbound half gains no front
door of its own here. It also carries the operational half into the routine-prompt policy:
every autonomous routine prompt bans session URLs and harness footers in public
text and mandates a post-create re-read-and-strip of every PR, issue and comment
the loop creates, because the append happens outside the model's own text.
Scope
In:
internal/adapter/scanner/patterns.go— two new patterns in the canonicalDefaultPatterns()set (session-URL, harness-footer), so they flow into everyScanText/ScanBundleconsumer automatically.internal/core/repolint/rule_privacy.go— widenabcd audit's privacy rule to iterate the new class over tracked files (today it reads onlyNetworkPatterns()).- A new outbound-artefact scan seam:
scanner.New(repoRoot).ScanText(text, label)+scanner.Redact+ ablockingResidual-style fail-closed re-scan, callable before a PR/issue/comment is posted. - The docs-lint path: thread the new class into
internal/core/lintso posted text under the lint roots is flagged. - The routine-prompt assembly used by the bughunt/autonomous-run scaffolding — the policy text and the mandated post-create strip step.
Out:
- The full secret set (
DefaultPatterns()API keys, PATs) is not newly added to the audit privacy rule beyond the two footer/URL patterns — widening audit to every secret pattern is a separate behavioural change and is not in scope. - No new forge client: abcd does not itself open PRs. The outbound scan is a primitive the routine calls, not a posting path abcd owns.
Approach
The canonical set is the scanner's DefaultPatterns() (patterns.go:41),
already the source for ScanText/ScanBundle used by capture
(capture/redact.go), memory (memory/redact.go), history
(history/history.go) and launch (launch/dryrun.go). Two Pattern entries
(struct at patterns.go:12) are added:
harness_session_url— matches the live-session URL shape the harness appends (a claude.ai session path),Severityhigh, with aSkip/SkipAtguard so a bare unrelated URL does not false-positive.harness_attribution_footer— matches the "Generated with …"/"Co-authored-by" harness footer block the append adds outside the model's own text.
Because these live in the canonical set, all four store-before-commit consumers
gain them for free; the fail-closed residual re-scan those paths already run
(history.go:160, memory/redact.go:75) treats a surviving match as blocking.
Audit (repolint/rule_privacy.go). privacyHygiene.Eval (rule_privacy.go:85)
today builds its pattern list from sc.NetworkPatterns() (rule_privacy.go:134)
and scans gitutil.TrackedFiles. The change appends the two new patterns to
that list (a small SecretSubset() / explicit selection so only these two, not
the whole secret set, join the network patterns), so privacyLeak
(rule_privacy.go:203) flags a footer or session URL in any committed file.
docs-lint (internal/core/lint). The two patterns are registered as banned
tokens via textlint.go's NewTokenChecker (textlint.go:28) so committed/posted
prose under the lint roots is flagged with a clear message. This is the seam that
makes the class apply "in any committed or posted text, not only in freshly
created PR bodies".
Outbound scan. A new helper (e.g. scanner-backed ScrubOutbound(repoRoot, text)) runs ScanText + Redact and returns the scrubbed text plus a
fail-closed error if any target pattern survives — the same
ScanText → Redact → blockingResidual shape capture/memory/history use. The
routine that holds the forge credentials calls it on every PR body and comment
before posting; abcd itself exposes no verb onto it, by the scope decision above,
so the primitive ships without a front door and the posting-time control stays
the re-read-and-strip policy below.
Routine-prompt policy. The autonomous-run prompt assembly gains a fixed policy block: (a) never emit session URLs or harness footers in public text; (b) after creating any PR, issue or comment, re-read it and strip the harness-appended footer/URL, because the append lands outside the model's own output. This mirrors the pattern already recorded in the user's operating notes (strip the auto-appended footer post-write).
How it satisfies each acceptance criterion
- Outbound PR body with a harness footer is caught and stripped —
ScrubOutboundruns the footer pattern andRedactbefore posting. Test: feed a PR body with the footer, assert the returned text carries only the repo'sAssisted-by:attribution and the footer is gone. - Outbound issue comment with a live session URL is caught, model- or
harness-authored — the
harness_session_urlpattern matches regardless of origin. Test: a comment containing a session URL fails the scan / is redacted. - Already-clean artefact passes unchanged — no target pattern matches, so
ScrubOutboundreturns the input verbatim with no finding. Test asserts byte equality and empty findings. - The shared set is applied by
abcd auditand docs-lint over committed/posted text — the audit widening (rule_privacy.go) and the docs-lint token registration (textlint.go). Tests: a tracked file carrying a footer failsaudit; a doc under the lint root carrying a session URL failsdocs-lint. - An assembled routine prompt carries the policy — the prompt-assembly change. Test: assemble a routine prompt for a managed repo and assert the policy block (ban + post-create strip) is present.
Decisions
Only the two new shapes join the audit privacy rule, not the whole secret set: the leak this closes is specifically the harness footer/URL, and widening audit to every API-key pattern would change unrelated behaviour and risk noise on legitimately redacted fixtures. The class is defined once, in the scanner's canonical set, so the four store-before-commit paths, audit, docs-lint and the outbound scan all read one definition rather than drifting copies.
Frontmatter
| id | spc-45 |
|---|---|
| type | spec |
| lifecycle | closed |
.abcd/development/specs/closed/spc-45-autonomous-cloud-runs-in-sibling-repos.md
open on GitHub ↗ · commit history ↗
References1
- itd-152Autonomous cloud runs in sibling repos leaked harness attribution footers and a live session URL into public GitHub artifacts: the harness auto-appends a 'Generated with' footer plus a session link when a PR or issue is created, overriding the repos' Assisted-by-only attribution policy (commit messages stayed clean; the leak surface was PR bodies and issue comments, plus GitHub's public edit history retaining the pre-scrub revision). Leak shape only - no session ids reproduced here. Two remedies needed: (a) every autonomous routine prompt must ban session URLs and harness footers in public text AND mandate a post-create re-read-and-strip of every PR/issue/comment the loop creates, because the append happens outside the model's own text; (b) abcd should detect the class - session-URL and harness-footer patterns belong with the shared privacy pattern set (iss-154 family / itd-74 banlist territory) so audit and docs-lint flag them in any committed or posted text.implements
mentions2
- iss-2608282124231752scanner.ScrubOutbound has no front door: it is defined, tested and documented as the outbound-artefact scrub, but no CLI command, plugin verb or routine prompt calls it, and internal/ cannot be imported from outside the module, so the posting-time half of the harness-leak class is unmitigated in practice. This is not an oversight in spc-45, which deliberately scopes a forge client out; the gap is that nothing hands the primitive to the party that does post. Two candidate shapes for a future change: a small stdin-reading verb (abcd scrub --label pr-body) that the autonomous routine prompts pipe every PR body, issue and comment through before posting; or wiring the primitive into whatever posting path lands with itd-107, so an assembled routine scrubs by construction. Until one lands, the operative control is the re-read-and-strip policy in scanner.OutboundPolicy, as AGENTS.md, iss-178 and spc-45 state.
- spc-2609230612443069Routine prompts carry the outbound policy