Skip to content

fix(supply-chain): census every version pin and run stale-repos by digest - #873

Merged
devantler merged 2 commits into
mainfrom
claude/supply-chain-version-census-627
Nov 7, 2026
Merged

devantler merged 2 commits into
mainfrom
claude/supply-chain-version-census-627

Conversation

@devantler

@devantler devantler commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer (interactive Claude Code session)

Why

#627 asks that every version the catalogue runs be both immutable and kept current. Only the gomod tracking part (#840, delivered in #841) has landed. Two gaps remain:

  • Nothing notices a pin that no updater reads.
  • github/stale-repos is SHA-pinned, yet it runs a floating image while it holds the organization App token. It is a Docker action whose metadata says image: docker://ghcr.io/github-community-projects/stale_repos:v9, so the SHA pin froze nothing.

What

  • Census, kept as a test. .github/scripts/version-pin-census.sh lists every version pin in workflow files, action metadata and Go modules, one row each: tracked, manifest or violation. A pin is tracked in two cases. It can be a full-SHA action with a # v<version> comment, in a directory that Dependabot's github-actions entry covers. Or it can be a module with external requirements, in a gomod entry that does not ignore *. Requirements replaced by a local path are not versions. Any other pin must be listed in the new .github/version-pins.json with its refresh path. That means images, tool versions in run:/with:/env: (including block-scalar lists such as dotnet-version: |), branch-tracked actions and frozen fixtures. The census fails in four cases: a mutable pin (an action ref that is not a full SHA, or an image without a digest), a pin that is neither tracked nor listed, a manifest entry that no longer matches anything, and unreadable input (exit 2). It does not use the network.
  • Manifest. 19 entries listing 42 pins. It holds the hard-coded tool versions the issue lists (semantic-release, golangci-lint, deadcode, gocover-cobertura, govulncheck, Node, Go and the .NET SDKs), the PostgreSQL fixture image, setup-homebrew's branch commit, and the two deliberately frozen govulncheck fixtures, each with a reason. The census found two pins the issue did not list: the .NET SDK list in run-dotnet-tests and ci.yaml's dotnet-version blocks.
  • stale-repos by digest. The step now runs docker://ghcr.io/github-community-projects/stale_repos:v9.1.1@sha256:b259f6c3…. That is the image the v9 and v9.1.1 tags both resolve to today. The action declares no inputs, arguments or entrypoint, and the step passes everything through env:, so the container gets the same environment and workspace.
  • test-version-pin-census.sh is wired into the lint-ci-coverage-parity job. actions/CONTRIBUTING.md documents the rule.

Acceptance criteria of #627

Criterion State
A census, kept as a test, lists every pin and fails on one that is neither tracked nor listed Done here
Every module with dependencies receives grouped weekly updates Holds today; the census now enforces it
No step runs a container image by a mutable tag while holding a token that can write stale-repos fixed here. One remaining: the production validate-go-project.yaml lint job runs oxsecurity/megalinter/flavors/go@9949bad0, whose metadata uses the tag megalinter-go:v10.1.0, while GITHUB_TOKEN has issues/pull-requests: write. Fixing that trades Dependabot tracking of MegaLinter for a digest, or takes the token off the step, so it is left for a separate change.
Weekly update-PR count measured before and after, growth of at most one per ecosystem This PR adds no update configuration. Dependabot PRs on this repository: 12 (2026-10-21 to 27), then 24 (2026-10-28 to 11-03). #841 merged on 11-03, so a week after it is not yet observable.

So this PR is Part of #627, not a fix for it.

Validation

  • bash .github/tests/test-version-pin-census.sh passes. It runs the census on this repository (431 tracked, 42 listed, 0 violations) and 22 controls on an isolated fixture tree. They cover: tag ref, SHA without version comment, uncovered directory, nested directory under a one-level *, open-pull-requests-limit: 0, workflow template, docker:// tag, service tag, expression image, mutable expression branch, *_IMAGE env tag, unlisted env digest, docker run literal, continued docker run, unlisted go install …@v, *_VERSION env, block-scalar version, untracked module, unlisted frozen module, stale entry, malformed manifest and missing dependabot.yml. Each control asserts its specific row.
  • The census fails on main's tree with an empty manifest (32 violations, RED) and passes here.
  • shellcheck is clean for both scripts. test-test-script-wiring.sh passes. zizmor has no findings on the stale-repos workflow.
  • The census takes under one second.

🤖 Generated with Claude Code

Review follow-up (second commit): an independent review pass found two fail-opens. Images outside image:/container:/docker:// were never examined, which hid the unlisted todo-to-issue-action image. And a mutable branch inside an image expression went unseen. Both are fixed and each has controls. The scan also now covers workflow-templates/ and Dependabot's one-segment *. The manifest lists the two pins the wider scan found.

…gest

Add a census, kept as a CI test, that lists every version pin in workflows,
action metadata and Go modules. It fails when a pin is mutable, neither
tracked by Dependabot nor listed with a refresh path in
.github/version-pins.json, or when a listed entry no longer matches.

github/stale-repos is a Docker action whose metadata runs the floating
stale_repos:v9 tag while the step holds the App token. Call the v9.1.1
image by digest instead.

Part of #627

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@devantler

devantler commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer (interactive Claude Code session)

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-11-04T19:22:56.181602Z
We couldn’t complete this review. Comment @codex review to try again.
d3fd902 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Every digest on any line, every *_IMAGE assignment and docker
run/pull/create arguments (joined across continued lines) are now
examined, and each result branch of an image expression must be
digest-pinned or empty. A Dependabot "*" matches one path segment, and
entries that ignore everything or open no pull requests count as
untracked. Workflow templates are scanned and go.mod discovery fails
closed. Lists the todo-to-issue image and the template checkout pin the
wider scan found.

Part of #627

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@devantler devantler left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Generated by the Agentic Engineer (interactive Claude Code session)

Self-review (fallback — CodeRabbit, Codex and Cursor Bugbot unavailable)

Reviewed commit: e48e610

  • CodeRabbit: rate-limited across the organization since 2026-11-03T09:04Z (coordinator lane-health reading). No review requested.
  • Codex: requested at the first head. It answered "You have reached your Codex usage limits for code reviews" at 2026-11-04T19:23:04Z.
  • Cursor Bugbot: hit its usage limit at 2026-11-04T00:29Z, with no delivery evidence since. Not requested.

An independent reviewer agent reviewed this PR at d3fd9026ccc2c4aa254ff312b97eebaae372a7a1: correctness and security first, then this repository's review guidelines. It ran the PR's tests and mutation fixtures in a detached worktree. I fixed or documented every finding, then re-reviewed the delta to e48e6107708a5358933f3a2b4a4024c430181f0a myself:

  • P1, fixed: the census now examines every image digest on any line, every *_IMAGE assignment, and docker run/pull/create arguments, joined across continued lines. The wider scan found the unlisted todo-to-issue-action image, which is now listed with its refresh path. New controls: env image, docker run, continued docker run, unlisted env digest.
  • P1, fixed: in an image expression, each result branch (a literal after ${{, && or ||) must be digest-pinned or empty. Comparison operands do not count. New control: a mutable || branch.
  • nit, fixed: a Dependabot * matches one path segment (new nested-action control). Entries that ignore * or set open-pull-requests-limit: 0 count as untracked (new control). workflow-templates/ is scanned (new control; its checkout pin is listed). go.mod discovery fails closed.
  • nit, documented in actions/CONTRIBUTING.md: shell variables are resolved only through *_IMAGE, a tool version needs a dotted number, and a quoted # ends the scanned text.
  • Confirmed: the docker:// stale-repos step behaves the same as the action. Both tags resolve to the pinned index digest.

Verdict: no P0/P1 findings

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer (interactive Claude Code session)

Tried as a user at e48e610770:

  1. bash .github/scripts/version-pin-census.sh on this branch's tree exits 0 in under a second:
Status Kind Pins
manifest action 2
manifest image 7
manifest module 2
manifest tool 31
tracked action 427
tracked module 4
  1. I removed the semantic-release@25.0.9 entry from the manifest in a working copy and ran the census again. It exited 1 with 4 violation rows, one for each semantic-release@25.0.9 occurrence in create-release.yaml and ci.yaml, plus the error a pin is mutable, or neither tracked by Dependabot nor listed. I restored the manifest afterwards; the working tree is clean.

🤖 Generated with Claude Code

@github-code-quality

github-code-quality Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Code coverage overview

Languages: Go, C#

Go / code-coverage/test

main 17ee709 claude/supply-chain-... e48e610 +/-
50% 50% 0%

C# / code-coverage/dotnet

main 17ee709 claude/supply-chain-... e48e610 +/-
100% 100% 0%

Go / code-coverage/go

main 17ee709 claude/supply-chain-... e48e610 +/-
50% 50% 0%

ℹ️ Calculations represent line coverage. Results may change as calculations are updated.


Updated November 04, 2026 21:59 UTC

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer (interactive Claude Code session)

Status: held as a draft until CI finishes at e48e610770. The local review round at this head passes local-review-verdict.sh (GREEN self@e48e6107708a5358933f3a2b4a4024c430181f0a). The review has no open findings, and the PR has no unresolved threads and no conflict. The first CI run at this head failed nothing: its only non-green jobs were cancelled when a PR-body edit started a newer run, which is still running. Promote once every required check is green at this head.

🤖 Generated with Claude Code

@devantler
devantler marked this pull request as ready for review November 7, 2026 11:10
@devantler
devantler merged commit 15372ea into main Nov 7, 2026
451 of 454 checks passed
@devantler
devantler deleted the claude/supply-chain-version-census-627 branch November 7, 2026 11:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant