You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(lanes): secret-scan lane artifacts before upload and shorten their retention #8295
Artifacts on this public repo can be downloaded by any GitHub account; GitHub's docs require only read access. Lane and eval artifacts are kept for the repo default of 90 days. A full sweep on 2026-10-18 scanned all 4,210 unexpired artifacts and found no credential, but nothing stops a future one at upload time.
What an attacker needs: any GitHub account.
Impact: anything that leaks into an artifact can be downloaded for up to 90 days.
Fix
Scan lane and eval artifacts for secrets before upload, failing the upload on a hit, and set retention-days to a configurable short value (starting at 3) on those uploads. Leave the repo-wide retention at 90 days, because it covers logs too. Note that gitleaks is feature complete (security patches only); check whether its successor fits before choosing a scanner.
Priority
Low. Deferred by the owner until the lanes run steadily.
Proposed work class: work-class: scoped
Basis: automated intake triage; the lanes App applied this class.
Size: Large
Complexity: Medium
Uncertainty: Known
Flags: none
Blocked by: none
Decision recorded by kyle-sexton:
Decision recorded.
This answers the four open questions in intake's refinement (comment 6395463816), read against origin/main 60b4096a4a. Since the refinement, #10478 added .github/artifact-retention.json: one row per artifact family with its retention-days, readers and reason, checked against every upload step by scripts/check-artifact-retention.sh (check-artifact-retention.mjs:9-14). That table is now the configurable retention value this issue asks for.
[quote truncated; the full decision is at the link]
The decision settles the four open questions (scanner, which uploads count, retention values, ci-workflows scope), so nothing further is asked. I understood the work as: scan lane and eval artifacts for secrets before upload, and cut retention on the rows the decision names. The decision also lists acceptance criteria. This is a bounded change across several workflow files, the retention table and a ci-workflows pin bump.
Released for re-triage under the owner's 2026-10-24 rule: an issue stops for a person only for an intent or vision question, a security call, a host or attended run, or spend outside prepaid credit. The hold label came off and intake triage runs again now; it re-applies a hold, with a numbered question, when one of those applies.
To hold this issue again, add needs-human (or status: needs-decision) and say why in a comment.
Not admitted to the issue lane: its title or stated work names a charge (retention), which needs an owner decision; it carries host-run; Size large: the split into sub-issues at intake is pending (owner Q93). Admission takes the issue once each of those is met, when intake triages it again (.github/actions/resolve-config/admission-rules.json).
1 remaining item
melodic-automation-lanes commented on Oct 25, 2026
Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.
Intended outcome: Every lane and eval artifact upload is secret-scanned first and fails on a hit, and sets a configurable retention-days defaulting to 3, with repo-wide retention left at 90 days.
Which scanner: gitleaks (maintenance mode, already used in the repo) or its successor?
RECOMMENDED: Reuse the existing gitleaks setup unless the successor is a drop-in that reads the same rule file; record the choice in the PR. Basis: .base/.github/workflows/pr-check-managed-files-hosted.yml:61 references .gitleaks.toml
Which uploads count as lane and eval artifacts? Many upload-artifact steps already set retention-days 1, 7 or 30, and the first lines of pr-verify.yml:354 and pr-verify.yml:655 show none.
RECOMMENDED: Every upload-artifact step in lane and eval workflows under .github/workflows, except the 90-day backup in maintenance-delete-stale-branches.yml:110 unless it holds lane data. Basis: .base/.github/workflows/pr-verify.yml:354
Should retention be 3 days everywhere, even where it is already 1 day (shorter)?
RECOMMENDED: Use one configurable variable defaulting to 3 and keep any existing value that is shorter, so the 1-day uploads stay at 1. Basis: .base/.github/workflows/intake-triage.yml:557 sets retention-days 1
Do lane uploads that live in the pinned external ci-workflows repo fall within this issue?
RECOMMENDED: No: limit this issue to workflows in this repository and file a follow-up for ci-workflows. Basis: judgment; candidates 9074 and 9556 show lanes pinned to ci-workflows releases
Sources read
.base/.github/workflows/pr-check-managed-files-hosted.yml:61: lists .gitleaks.toml, so a gitleaks config already exists
.base/.github/workflows/intake-triage.yml:557: upload-artifact step with retention-days 1
.base/.github/workflows/pr-run-activity-read.yml:2641: lane artifact upload with retention-days 7
.base/.github/workflows/eval-browser-dispatch.yml:316: eval artifact upload with retention-days 30
.base/.github/workflows/maintenance-delete-stale-branches.yml:110: upload with retention-days 90
Automated refinement by the lanes App. Answer with a comment whose first line is **Decision recorded.**.
Builder: the Automation Lanes session builds this from the owner's machine. The fix edits .github/workflows, which the lanes App cannot push (patch-rules.json:3); that boundary stays. Its PR closes this issue. needs-human came off under the owner's 2026-10-26 rule (a person only for an intent or vision change, an implementation finding that alters intent or the primary design, or a HIGH/CRITICAL security issue); none applies. agent-ready stays off so the issue lane does not race the build.
This answers the four open questions in intake's refinement (comment 6395463816), read against origin/main 60b4096a4a. Since the refinement, #10478 added .github/artifact-retention.json: one row per artifact family with its retention-days, readers and reason, checked against every upload step by scripts/check-artifact-retention.sh (check-artifact-retention.mjs:9-14). That table is now the configurable retention value this issue asks for. Only the read runner's lane outputs are scanned before upload today: its leak-scan step (pr-run-activity-read.yml:2701-2702, scanLeak in lib/publish-gate.mjs) gates the skill-reply-*, patch-*, feedback-replies-*, dependency-verdict-* and work-outcome-* uploads (:2925-2986). Every other lane and eval upload is unscanned. Agents decided this on evidence under standing direction §1.
Which scanner? Decided: the lanes' existing leak scan, scanLeak in lib/publish-gate.mjs, with its CREDENTIAL_SHAPES and SECRET_PATTERNS (:34, :38). It already gates the read runner's uploads and a reused branch in the issue lane (lib/publish-gate.mjs:145-146), so extending it keeps every lane upload on one rule set. The PR gate's gitleaks call (pr-require-checks.yml:1602-1608) scans git history (scan-mode: git), not files about to be uploaded, and stays as it is. The body asks for a scanner comparison: the build records in the PR how scanLeak compares with gitleaks against .gitleaks.toml, and with Betterleaks.
Which uploads count as lane and eval artifacts? Decided: every upload whose family covers a Claude lane's inputs or outputs, a lane run audit, or an eval run.
Not lane or eval artifacts: mutation-range, selection-audit-*, selection-red-replay-* and branch-cleanup-ledger-*. These are test-selection, mutation and branch-cleanup records.
lane-transcript-* counts too. Its upload holds only ciphertext sealed to the transcript public key (.github/actions/upload-lane-transcript/action.yml:2-8), so a scan of the upload itself would find nothing. Instead, the action scans the plaintext execution file it already takes as input, before the seal step.
Should retention be 3 days everywhere, even where it is already 1? Decided: no. A row at 3 or less keeps its value. A lane or eval row over 3 days drops to 3 unless its reason names a reader or a recorded decision that needs longer.
lane-sweep-units records "no reason is recorded for 7 days" (artifact-retention.json:227). eval-browser-* records "Manual inspection only" (:381). Both drop to 3.
skill-reply-* keeps 7 days. Its reason calls 7 days "a recorded owner decision" (:149), and its pending cut says changing it needs the owner (:150). Keeping it costs nothing (below), so these criteria do not change it.
lane-transcript-* keeps 7 days. It is ciphertext (item 2), and the daily puller needs the week (:141).
The four gate-read rows keep their values: pr-verify-*, pr-review-main-*, pr-merge-dispatched-* and pr-merge-rerun-* (:4-54). Each names a merge or post-merge gate reader that needs longer than 3 days (:16, :30, :45, :54), and pr-review-main-* records in its pending-cut field what must land before any cut (:31). Keeping them costs nothing today: the billing usage API shows this repository's October Actions storage at a net amount of 0. The pre-upload scan applies to them.
Do lane uploads in ci-workflows fall within this issue? Decided: yes. The issue covers lane artifacts on this repository's runs, wherever the step is defined. The reusable review workflow's handoff uploads keep 1 day but are not scanned (ci-workflows .github/workflows/pr-review-cli.yml:1400-1410 at 2476d5d, with further upload steps at :2503, :3604 and :3889). Their scan is a ci-workflows change, followed by a pin bump here.
Repo-wide retention stays at 90 days, as the body says. With every retention question decided here, the build's PR may close this issue.
Acceptance criteria:
Each family row in .github/artifact-retention.json records whether its uploads are lane or eval artifacts that need a scan. scripts/check-artifact-retention.sh fails when an upload step of a scanned family has no scanLeak step over the same path before it; the existing leak-scan step counts. A fixture test covers a scanned family with and without the scan step and passes in CI.
Every lane and eval upload in this repository not already gated by the leak-scan step runs scanLeak over the files it is about to upload, and a hit fails the step before the upload, with no matched value printed. For lane-transcript-*, the scan runs on the plaintext execution file before it is sealed.
A fixture test plants a credential-shaped string in a file and shows the scan step fails on it without printing the value, and passes on a clean file. It passes in CI.
lane-sweep-units and eval-browser-* are 3 days in .github/artifact-retention.json and on their upload steps. Their reasons say why 3 days is enough, and scripts/check-artifact-retention.sh passes in CI.
Each ci-workflows review handoff upload runs a secret scan over its files before it uploads, and this repository's pins move to a ci-workflows release that carries it.
The PR records the scanner comparison the body asks for: scanLeak against gitleaks with .gitleaks.toml, and Betterleaks.
On the "until the lanes run steadily" deferral (body; ledger row C7, .work/backlog-shaper-course-correction/interview-checklist.md:22): this session (Issue Triage) reads it as not covering the scan. Standing direction §10 defines the high/critical boundary as "a token or secret leaking", and defers only holds against our own actors. Workflow artifacts need only read access to download (docs), and "Public repositories are accessible to everyone on the internet" (docs), so an unscanned lane artifact is an outside-reader token-leak path, the case #10854 kept open for the same reason. The retention cuts ride along because they are in the same table.
Route: builder session from the owner's machine. The scan and retention steps edit workflow files, which the lanes App cannot push, and item 4 is a ci-workflows change. Labels: add host-run. status: confirmed stays. No agent-ready.
Issue Triage session: status of this issue. The status: needs-decision hold set at 00:42Z comes from admission's charge-word check, not an open question. It stays on: the owner's Q1 (Issue Triage numbering, 2026-10-31) chose to fix that check rather than clear its holds by a recorded decision, so this note does not lift it.
Why it was set. The **Decision recorded.** comment at 00:38Z re-entered intake. Admission then held the issue again because its title and Fix name retention, a word on the charge list (admit.mjs:1235-1238 sets the hold; :2266 maps it to status: needs-decision). host-run and the Size Large split park at status: confirmed, not here.
Criterion 1: each family row in .github/artifact-retention.json now has a scan field (upload, plaintext or none).
Criteria 2 and 3: the scan-upload action and its tests.
Criterion 4: both rows are at 3 days on main.
Criterion 5.fix(pr-review-cli): scan review handoff and shadow report uploads for credentials ci-workflows#945 merged 03:40Z. This repository calls pr-review-cli.yml@main, so there is no pin to move. On review run 41325464261 (16:16Z), step 26 "Scan the review handoff for credentials" ran before step 27 "Upload the review handoff", and both succeeded. The shadow-report pair is wired in the same order but was skipped in that run.
Activity
github-actions commentedon Oct 18, 2026
Proposed work class: work-class: scoped
Basis: automated intake triage; the lanes App applied this class.
Size: Large
Complexity: Medium
Uncertainty: Known
Flags: none
Blocked by: none
Decision recorded by kyle-sexton:
#8295 (comment)
The decision settles the four open questions (scanner, which uploads count, retention values, ci-workflows scope), so nothing further is asked. I understood the work as: scan lane and eval artifacts for secrets before upload, and cut retention on the rows the decision names. The decision also lists acceptance criteria. This is a bounded change across several workflow files, the retention table and a ci-workflows pin bump.
Automated triage, applied by the lanes App.
kyle-sexton commentedon Oct 25, 2026
Released for re-triage under the owner's 2026-10-24 rule: an issue stops for a person only for an intent or vision question, a security call, a host or attended run, or spend outside prepaid credit. The hold label came off and intake triage runs again now; it re-applies a hold, with a numbered question, when one of those applies.
To hold this issue again, add
needs-human(orstatus: needs-decision) and say why in a comment.melodic-automation-lanes commentedon Oct 25, 2026
Not admitted to the issue lane: its title or stated work names a charge (retention), which needs an owner decision; it carries host-run; Size large: the split into sub-issues at intake is pending (owner Q93). Admission takes the issue once each of those is met, when intake triages it again (
.github/actions/resolve-config/admission-rules.json).1 remaining item
melodic-automation-lanes commentedon Oct 25, 2026
Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.
Intended outcome: Every lane and eval artifact upload is secret-scanned first and fails on a hit, and sets a configurable retention-days defaulting to 3, with repo-wide retention left at 90 days.
Related work
Open questions
RECOMMENDED: Reuse the existing gitleaks setup unless the successor is a drop-in that reads the same rule file; record the choice in the PR. Basis: .base/.github/workflows/pr-check-managed-files-hosted.yml:61 references .gitleaks.toml
RECOMMENDED: Every upload-artifact step in lane and eval workflows under .github/workflows, except the 90-day backup in maintenance-delete-stale-branches.yml:110 unless it holds lane data. Basis: .base/.github/workflows/pr-verify.yml:354
RECOMMENDED: Use one configurable variable defaulting to 3 and keep any existing value that is shorter, so the 1-day uploads stay at 1. Basis: .base/.github/workflows/intake-triage.yml:557 sets retention-days 1
RECOMMENDED: No: limit this issue to workflows in this repository and file a follow-up for ci-workflows. Basis: judgment; candidates 9074 and 9556 show lanes pinned to ci-workflows releases
Sources read
.base/.github/workflows/intake-triage.yml:557: upload-artifact step with retention-days 1.base/.github/workflows/pr-run-activity-read.yml:2641: lane artifact upload with retention-days 7.base/.github/workflows/eval-browser-dispatch.yml:316: eval artifact upload with retention-days 30.base/.github/workflows/maintenance-delete-stale-branches.yml:110: upload with retention-days 90Automated refinement by the lanes App. Answer with a comment whose first line is
**Decision recorded.**.kyle-sexton commentedon Oct 26, 2026
Builder: the Automation Lanes session builds this from the owner's machine. The fix edits
.github/workflows, which the lanes App cannot push (patch-rules.json:3); that boundary stays. Its PR closes this issue.needs-humancame off under the owner's 2026-10-26 rule (a person only for an intent or vision change, an implementation finding that alters intent or the primary design, or a HIGH/CRITICAL security issue); none applies.agent-readystays off so the issue lane does not race the build.kyle-sexton commentedon Nov 1, 2026
Decision recorded.
This answers the four open questions in intake's refinement (comment 6395463816), read against origin/main
60b4096a4a. Since the refinement, #10478 added.github/artifact-retention.json: one row per artifact family with itsretention-days, readers and reason, checked against every upload step byscripts/check-artifact-retention.sh(check-artifact-retention.mjs:9-14). That table is now the configurable retention value this issue asks for. Only the read runner's lane outputs are scanned before upload today: itsleak-scanstep (pr-run-activity-read.yml:2701-2702,scanLeakinlib/publish-gate.mjs) gates theskill-reply-*,patch-*,feedback-replies-*,dependency-verdict-*andwork-outcome-*uploads (:2925-2986). Every other lane and eval upload is unscanned. Agents decided this on evidence under standing direction §1.scanLeakinlib/publish-gate.mjs, with itsCREDENTIAL_SHAPESandSECRET_PATTERNS(:34,:38). It already gates the read runner's uploads and a reused branch in the issue lane (lib/publish-gate.mjs:145-146), so extending it keeps every lane upload on one rule set. The PR gate's gitleaks call (pr-require-checks.yml:1602-1608) scans git history (scan-mode: git), not files about to be uploaded, and stays as it is. The body asks for a scanner comparison: the build records in the PR howscanLeakcompares with gitleaks against.gitleaks.toml, and with Betterleaks.mutation-range,selection-audit-*,selection-red-replay-*andbranch-cleanup-ledger-*. These are test-selection, mutation and branch-cleanup records.lane-transcript-*counts too. Its upload holds only ciphertext sealed to the transcript public key (.github/actions/upload-lane-transcript/action.yml:2-8), so a scan of the upload itself would find nothing. Instead, the action scans the plaintext execution file it already takes as input, before the seal step.lane-sweep-unitsrecords "no reason is recorded for 7 days" (artifact-retention.json:227).eval-browser-*records "Manual inspection only" (:381). Both drop to 3.skill-reply-*keeps 7 days. Its reason calls 7 days "a recorded owner decision" (:149), and its pending cut says changing it needs the owner (:150). Keeping it costs nothing (below), so these criteria do not change it.lane-transcript-*keeps 7 days. It is ciphertext (item 2), and the daily puller needs the week (:141).pr-verify-*,pr-review-main-*,pr-merge-dispatched-*andpr-merge-rerun-*(:4-54). Each names a merge or post-merge gate reader that needs longer than 3 days (:16,:30,:45,:54), andpr-review-main-*records in itspending-cutfield what must land before any cut (:31). Keeping them costs nothing today: the billing usage API shows this repository's October Actions storage at a net amount of 0. The pre-upload scan applies to them..github/workflows/pr-review-cli.yml:1400-1410at2476d5d, with further upload steps at:2503,:3604and:3889). Their scan is a ci-workflows change, followed by a pin bump here.Repo-wide retention stays at 90 days, as the body says. With every retention question decided here, the build's PR may close this issue.
Acceptance criteria:
.github/artifact-retention.jsonrecords whether its uploads are lane or eval artifacts that need a scan.scripts/check-artifact-retention.shfails when an upload step of a scanned family has noscanLeakstep over the same path before it; the existingleak-scanstep counts. A fixture test covers a scanned family with and without the scan step and passes in CI.leak-scanstep runsscanLeakover the files it is about to upload, and a hit fails the step before the upload, with no matched value printed. Forlane-transcript-*, the scan runs on the plaintext execution file before it is sealed.lane-sweep-unitsandeval-browser-*are 3 days in.github/artifact-retention.jsonand on their upload steps. Their reasons say why 3 days is enough, andscripts/check-artifact-retention.shpasses in CI.scanLeakagainst gitleaks with.gitleaks.toml, and Betterleaks.On the "until the lanes run steadily" deferral (body; ledger row C7,
.work/backlog-shaper-course-correction/interview-checklist.md:22): this session (Issue Triage) reads it as not covering the scan. Standing direction §10 defines the high/critical boundary as "a token or secret leaking", and defers only holds against our own actors. Workflow artifacts need only read access to download (docs), and "Public repositories are accessible to everyone on the internet" (docs), so an unscanned lane artifact is an outside-reader token-leak path, the case #10854 kept open for the same reason. The retention cuts ride along because they are in the same table.Route: builder session from the owner's machine. The scan and retention steps edit workflow files, which the lanes App cannot push, and item 4 is a ci-workflows change. Labels: add
host-run.status: confirmedstays. Noagent-ready.fix(lanes): scan the pr-merge run records again and fit the tests to …
fix(lanes): let a failed pr-merge record scan fail the job
kyle-sexton commentedon Nov 1, 2026
Issue Triage session: status of this issue. The
status: needs-decisionhold set at 00:42Z comes from admission's charge-word check, not an open question. It stays on: the owner's Q1 (Issue Triage numbering, 2026-10-31) chose to fix that check rather than clear its holds by a recorded decision, so this note does not lift it.**Decision recorded.**comment at 00:38Z re-entered intake. Admission then held the issue again because its title and Fix nameretention, a word on the charge list (admit.mjs:1235-1238sets the hold;:2266maps it tostatus: needs-decision).host-runand the Size Large split park atstatus: confirmed, not here.lane-sweep-unitsandeval-browser-*went from 7 to 3 days, which lowers storage. It merged in fix(lanes): scan every lane and eval upload for credentials before it leaves #11806.11da9482, whichRefsthis issue:.github/artifact-retention.jsonnow has ascanfield (upload,plaintextornone).pr-review-cli.yml@main, so there is no pin to move. On review run 41325464261 (16:16Z), step 26 "Scan the review handoff for credentials" ran before step 27 "Upload the review handoff", and both succeeded. The shadow-report pair is wired in the same order but was skipped in that run.scanLeakagainst gitleaks with.gitleaks.toml, and against Betterleaks. fix(lanes): scan every lane and eval upload for credentials before it leaves #11806 records only why it chosescanLeakover gitleaks for upload bytes, and lists criterion 6 as remaining; Betterleaks has not been evaluated. Since fix(lanes): scan every lane and eval upload for credentials before it leaves #11806 has merged, the comparison goes on this issue, and once it is posted here the issue can close as done.Labels unchanged. The host builder that ran #11806 records criterion 6. No workflow edit remains.