Harden CI: install the Claude Code CLI from a lockfile - #5571
Merged
Conversation
Three workflows installed the CLI with `npm install -g @anthropic-ai/claude-code` -- no version, no lockfile. Each run resolved the registry's moving `latest`, so the job took whatever the tag pointed at the moment it ran, and verified nothing about it. zizmor reports each as `adhoc-packages` (installs a package outside of a lockfile). None of the three is a bystander to what it installs: harness-observability-audit issues: write + CLAUDE_CODE_OAUTH_TOKEN i18n-autotranslate contents/PRs: write + CLAUDE_CODE_OAUTH_TOKEN i18n-docs-autotranslate contents/PRs: write + CLAUDE_CODE_OAUTH_TOKEN All three then run that binary over repository content, so an unverified tree executes with the job's token. Replaced with `npm ci` against a committed .github/claude-cli/package-lock.json, which installs exactly the recorded tree and checks every tarball against its integrity hash. A bare `@version` pin does neither -- it re-resolves transitive deps each run and verifies nothing. One shared manifest rather than three because all three consume the same CLI. Pinned 2.1.251 rather than latest (2.1.261): .github/dependabot.yml sets a 7-day cooldown on every ecosystem, on the reasoning that a compromised release is usually yanked within a day or two. 2.1.251 is the newest release that clears that window, so the initial pin honours the same rule the automated bumps do. The new npm entry for /.github/claude-cli keeps the lock moving rather than frozen -- the pin/updater pairing that file already documents for the SHA pins. The two i18n jobs set up Python but never Node, so they relied on whatever the runner image shipped. The CLI declares `engines: node >=22`, so both now pin setup-node 22, matching harness-observability-audit. Verified: - zizmor: 53 findings -> 50, and the three that went are exactly these `adhoc-packages`. No new finding appears. - `npm ci` from the committed lock in a clean directory exits 0 and `claude --version` reports 2.1.251. - All 36 workflow files and dependabot.yml parse under yaml.safe_load; both manifests parse as JSON. - scripts/check_action_refs.py: 17 references, all SHA-pinned. No-PRD: CI-only supply-chain hardening under .github/. No product behaviour, flag, default or endpoint changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SGHzpWqT9vqseYSeaebJvw
✅ Drift Bot (ClawMetry): no drift detectedDrift Bot analyzed the changed files against this project's blueprints and requirements and found no drift. |
Owner
Author
|
Non-mergeable PR sweeper — run summary (2026-09-05) Scanned all three repos (clawmetry, clawmetry-cloud, clawmetry-landing) for open, non-draft PRs authored by
Result: No merge conflicts to resolve across any repo. All Generated by Claude Code |
Closed
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Product record: No-PRD: CI-only supply-chain hardening under
.github/. No product behaviour, flag, default or endpoint changes — closes a zizmoradhoc-packagesfinding on three workflows.Risk: Low, and contained to CI. The three affected jobs are the daily harness audit and the two i18n translation bots; none is on the release or deploy path, and none ships anything to PyPI or the cloud. If the pinned CLI were wrong the symptom is those three jobs failing loudly at the install step, not a bad artifact — and the revert is this diff.
npm ciwas run from the committed lock in a clean directory before pushing, so the install path is exercised rather than assumed. Nothing inclawmetry/,routes/ordashboard.pyis touched.Summary
Three workflows installed the Claude Code CLI with
npm install -g @anthropic-ai/claude-code— no version, no lockfile — so each run resolved the registry's movinglatestand verified nothing about what it got. zizmor reports each asadhoc-packages(installs a package outside of a lockfile).None of the three is a bystander to what it installs, and each then runs that binary over repository content:
harness-observability-auditissues: write+CLAUDE_CODE_OAUTH_TOKENi18n-autotranslatecontents: write,pull-requests: write+CLAUDE_CODE_OAUTH_TOKENi18n-docs-autotranslatecontents: write,pull-requests: write+CLAUDE_CODE_OAUTH_TOKENReplaced with
npm ciagainst a committed.github/claude-cli/package-lock.json, which installs exactly the recorded tree and checks every tarball against its integrity hash. A bare@versionpin does neither — it re-resolves transitive deps on each run and verifies nothing. One shared manifest rather than three, because all three consume the same CLI.Why 2.1.251 and not latest
.github/dependabot.ymlputs a 7-day cooldown on every ecosystem, on the stated reasoning that a compromised release is usually yanked within a day or two. Latest is2.1.261(published yesterday);2.1.251is the newest release that clears that window, so the initial pin honours the same rule the automated bumps already follow. The new npm entry for/.github/claude-cliis what keeps the lock moving rather than frozen — the pin/updater pairing that file already documents for the SHA pins.One behaviour change worth flagging
The two i18n jobs set up Python but never Node, so they ran on whatever the runner image happened to ship. The CLI declares
engines: node >=22, so both now pinsetup-node22 (SHA-pinned, same ref already used elsewhere in the repo), matchingharness-observability-audit. That makesnpm cireproducible there rather than image-dependent.This is the same fix
clawmetry-promerged for its ownadhoc-packagesfinding, applied to this repo's three call sites.Test plan
adhoc-packageson the three workflows. No new finding appears.npm cifrom the committed lock in a clean directory exits 0, andclaude --versionreports2.1.251.dependabot.ymlparse underyaml.safe_load; both manifests parse as JSON.scripts/check_action_refs.py: 17 distinct references, all SHA-pinned (the addedsetup-nodeincluded).if:guards verified per job —setup-nodeprecedes the install step, and every new step in the i18n jobs carries the existingif: env.SKIP != '1'guard so the token-absent skip path is unchanged.node_modules/under the new directory is already covered by.gitignore(verified withgit check-ignore).🤖 Generated with Claude Code
https://claude.ai/code/session_01SGHzpWqT9vqseYSeaebJvw
Generated by Claude Code