Skip to content

fix(run-claude-cli): verify the installed CLI's npm signatures #792

Closed

Description

@kyle-sexton

Problem

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).

Activity

  1. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 19, 2026
  2. kyle-sexton commented on Oct 20, 2026

    @kyle-sexton
    MemberAuthor

    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, 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.

    • Current state (origin/main 834f4e1):

    • Scope:

      • 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).
  3. added a commit that references this issue on Oct 20, 2026
    0af563e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageNot yet classified. Floor until a type and one priority tier are set.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions