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(run-claude-cli): verify the installed CLI's npm signatures #792
run-claude-cli installs whatever @anthropic-ai/claude-code npm serves as latest (.github/actions/run-claude-cli/action.yml:107 at v0.52.0, default: latest) and hands it both credentials. The install runs with --no-audit (lane.cjs:766), without --ignore-scripts, and nothing checks the package's registry signatures or provenance. Both callers end up on latest (intake-triage.yml:373; pr-verify passes no cli-version).
Pinning the version is declined: the lanes always run the latest Claude Code CLI (owner decision). The remaining gap is checking that what was installed is what Anthropic published.
Fix
After install, run npm audit signatures on the installed tree and fail closed when it reports an invalid or missing signature or attestation for @anthropic-ai/claude-code.
Install with --ignore-scripts if the CLI needs no install script (check the package's scripts first).
Keep logging the resolved version (already done).
Severity: P4. Raised in the security pass on #781 (finding 5).
Decision: Verify the installed CLI two ways and fail closed on either. First, check the native binary's SHA256 against Anthropic's GPG-signed manifest.json. Second, run npm audit signatures on a saved install and fail unless its JSON output shows both packages audited. Keep the version pin from fix(run-claude-cli)!: run the subscription leg as its own job #803. Do not use --ignore-scripts. The issue body's "pinning is declined" premise is out of date: fix(run-claude-cli)!: run the subscription leg as its own job #803 pinned the CLI.
A stale comment still calls latest the default (lane.cjs:256).
install() (lane.cjs:857-890) runs npm install --registry=${NPM_REGISTRY} --no-save --no-audit --no-fund --prefix <work>/cli @anthropic-ai/claude-code@<version>; --no-save is at :874 and the registry is https://registry.npmjs.org/ (:107). No signature or checksum check runs.
cli-path points at node_modules/.bin/claude (lane.cjs:888); the package's postinstall copies the platform binary into place, so --ignore-scripts leaves a stub that exits 1.
Manifest check: for the resolved version, download the release's manifest.json and its GPG signature, verify the signature against Anthropic's published key (pinned by fingerprint in the action), and compare the SHA256 recorded for linux-x64 with the SHA256 of the binary the install placed. Fail before any leg runs on a signature failure, a missing entry or a mismatch. Use the download location and key the setup docs name.
npm signatures: drop --no-save and install with --save-exact so the lockfile records the pinned version. Then run npm audit signatures --json --prefix <work>/cli --registry=${NPM_REGISTRY} (the same registry as the install). Fail unless the exit code is 0 and the JSON shows both @anthropic-ai/claude-code and the platform package (@anthropic-ai/claude-code-linux-x64) audited with verified signatures. A registry that publishes no keys is skipped with exit 0, so the exit code alone is not enough.
Keep the 2.1.304 pin and its vars.CLAUDE_LANE_CLI_VERSION override. Fix the stale lane.cjs:256 comment.
Keep scripts enabled. Check how npm 11.20+ and 12.1+ treat the postinstall under allowScripts (npm 11.19.0 warns that it is "not yet covered by allowScripts" but runs it). If a newer npm on the runner skips it, allow that one script explicitly; do not switch to --ignore-scripts.
Keep logging the resolved version.
Out of scope: returning to latest; npm provenance attestations (the package publishes none); pinning the tarball integrity hash; invoking the platform binary directly.
Acceptance criteria:
On a real install of the pinned version, the binary's SHA256 matches the linux-x64 entry in the GPG-verified manifest.json, and the launcher logs both values.
A test with a tampered binary, a bad manifest signature, or a manifest with no linux-x64 entry fails the launcher before any leg runs.
The install no longer passes --no-save, passes --save-exact, and the audit call passes the same --registry as the install (asserted in lane.test.cjs).
On a real install, npm audit signatures --json reports both @anthropic-ai/claude-code and @anthropic-ai/claude-code-linux-x64 verified.
A test whose stubbed audit JSON lists fewer than both packages, with exit 0, fails the launcher.
A test whose stubbed audit exits non-zero fails the launcher.
The install is not run with --ignore-scripts, and the resulting cli-path runs --version successfully.
The PR states the npm version on the runner and whether its allowScripts handling runs the postinstall, with the log line that shows it.
lane.cjs no longer calls latest the default.
Sequencing / dependencies: independent of the intake-triage work. Builds on #803's pin.
Evidence:
HIGH (single source), the manifest: Anthropic's setup page states that each release from 2.1.89 onward publishes a GPG-signed manifest.json with SHA256 checksums for each platform binary, and that the npm package pulls the native binary through a per-platform optional dependency linked into place by a postinstall step. Not tested: whether the manifest checksum matches the binary inside the npm platform package. The first acceptance criterion settles it.
MEDIUM, reproduced on npm 11.19.0: with --no-save, npm audit signatures audited only the platform package ("audited 1 package"); a saved install audited both ("audited 2 packages"). --prefix is honoured. Packages from a registry with no keys are skipped and the run still exits 0 (npm/cli#10018). Behaviour sources: verifying registry signatures, npm-audit docs, audit.js, verify-signatures.js.
MEDIUM (single publisher, npm/cli source): invalid and missing signatures both exit 1, and the audit does not hash files on disk. That is why the manifest check is the one that covers the binary on disk.
MEDIUM: none of the 544 published @anthropic-ai/claude-code versions carries npm provenance attestations (packument), so the npm audit proves only the registry signature.
Gap: npm 11.20+ and 12.1+ allowScripts behaviour was not checked (see scope).
Activity
kyle-sexton commentedon Oct 20, 2026
Agent-ready brief (triaged 2026-10-20)
Decision: Verify the installed CLI two ways and fail closed on either. First, check the native binary's SHA256 against Anthropic's GPG-signed
manifest.json. Second, runnpm audit signatureson a saved install and fail unless its JSON output shows both packages audited. Keep the version pin from fix(run-claude-cli)!: run the subscription leg as its own job #803. Do not use--ignore-scripts. The issue body's "pinning is declined" premise is out of date: fix(run-claude-cli)!: run the subscription leg as its own job #803 pinned the CLI.Current state (origin/main
834f4e1):2.1.304(run-claude-cli/action.yml:127-130, from #803).latestthe default (lane.cjs:256).install()(lane.cjs:857-890) runsnpm install --registry=${NPM_REGISTRY} --no-save --no-audit --no-fund --prefix <work>/cli @anthropic-ai/claude-code@<version>;--no-saveis at :874 and the registry ishttps://registry.npmjs.org/(:107). No signature or checksum check runs.cli-pathpoints atnode_modules/.bin/claude(lane.cjs:888); the package'spostinstallcopies the platform binary into place, so--ignore-scriptsleaves a stub that exits 1.Scope:
manifest.jsonand its GPG signature, verify the signature against Anthropic's published key (pinned by fingerprint in the action), and compare the SHA256 recorded forlinux-x64with the SHA256 of the binary the install placed. Fail before any leg runs on a signature failure, a missing entry or a mismatch. Use the download location and key the setup docs name.--no-saveand install with--save-exactso the lockfile records the pinned version. Then runnpm audit signatures --json --prefix <work>/cli --registry=${NPM_REGISTRY}(the same registry as the install). Fail unless the exit code is 0 and the JSON shows both@anthropic-ai/claude-codeand the platform package (@anthropic-ai/claude-code-linux-x64) audited with verified signatures. A registry that publishes no keys is skipped with exit 0, so the exit code alone is not enough.2.1.304pin and itsvars.CLAUDE_LANE_CLI_VERSIONoverride. Fix the stalelane.cjs:256comment.postinstallunderallowScripts(npm 11.19.0 warns that it is "not yet covered by allowScripts" but runs it). If a newer npm on the runner skips it, allow that one script explicitly; do not switch to--ignore-scripts.Out of scope: returning to
latest; npm provenance attestations (the package publishes none); pinning the tarball integrity hash; invoking the platform binary directly.Acceptance criteria:
linux-x64entry in the GPG-verifiedmanifest.json, and the launcher logs both values.linux-x64entry fails the launcher before any leg runs.--no-save, passes--save-exact, and the audit call passes the same--registryas the install (asserted inlane.test.cjs).npm audit signatures --jsonreports both@anthropic-ai/claude-codeand@anthropic-ai/claude-code-linux-x64verified.--ignore-scripts, and the resultingcli-pathruns--versionsuccessfully.allowScriptshandling runs thepostinstall, with the log line that shows it.lane.cjsno longer callslatestthe default.Sequencing / dependencies: independent of the intake-triage work. Builds on #803's pin.
Evidence:
manifest.jsonwith SHA256 checksums for each platform binary, and that the npm package pulls the native binary through a per-platform optional dependency linked into place by a postinstall step. Not tested: whether the manifest checksum matches the binary inside the npm platform package. The first acceptance criterion settles it.--no-save,npm audit signaturesaudited only the platform package ("audited 1 package"); a saved install audited both ("audited 2 packages").--prefixis honoured. Packages from a registry with no keys are skipped and the run still exits 0 (npm/cli#10018). Behaviour sources: verifying registry signatures, npm-audit docs, audit.js, verify-signatures.js.@anthropic-ai/claude-codeversions carries npm provenance attestations (packument), so the npm audit proves only the registry signature.allowScriptsbehaviour was not checked (see scope).fix(run-claude-cli): verify the CLI's npm signatures and signed manif…