Dev.to Security 🔐 Cybersecurity 👁 0 📖 5 min read

Claude Code checked out a plugin's pinned 40-character SHA but never compared HEAD to it, so a branch named like the SHA won

Pinning a dependency to a full commit SHA is supposed to mean this exact code, as reviewed. An October advisory for Claude Code (CVE-2026-86063, fixed in 2.1.179) showed a plugin install that ran git checkout on the pinn

Claude Code checked out a plugin's pinned 40-character SHA but never compared HEAD to it, so a branch named like the SHA won

Pinning a dependency to a full commit SHA is supposed to mean this exact code, as reviewed. An October advisory for Claude Code (CVE-2026-86063, fixed in 2.1.179) showed a plugin install that ran git checkout on the pinned SHA and never checked where HEAD ended up, so a branch whose name was that same 40-character string could take its place. The fix is one comparison, and any script of yours that pins and checks out commits needs the same one.

What the advisory says

The advisory is GHSA-pq7j-f95f-qfcv, Plugin Commit SHA Pin Bypass Allows Installation of Attacker-Controlled Code, rated High (CVSS 7.7). Affected versions are Claude Code before 2.1.179, and 2.1.179 is the patched version.

The description is two sentences long and worth reading in full. "The plugin install flow in Claude Code and Claude Desktop checked out a marketplace's pinned commit SHA using git checkout without verifying that the resulting HEAD matched the pinned SHA. An attacker controlling a plugin's upstream repository could create a branch named identically to the pinned 40-character SHA, causing the checkout to silently resolve to the attacker's branch tip instead of the reviewed commit, resulting in attacker-controlled code being installed bypassing the pinned hash."

Quote from the Claude Code advisory: the install flow checked out a marketplace's pinned commit SHA using git checkout without verifying that the resulting HEAD matched the pinned SHA

The conditions are narrow, and the advisory lists all three: "Exploitation required a user to install a malicious plugin, pin to a safe hash, and for the plugin source to be hosted on a git host that permitted SHA-shaped branch names." It also says "Users on standard Claude Code auto-update have received this fix already." The reporter was Or Nevo Michrowski of Air Security.

Where the pin lives

The pin is written by whoever publishes a plugin marketplace, not by the person installing. Claude Code's marketplace reference describes plugin sources of type github, url and git-subdir, which share two fields: ref and sha. The sha field is "a full 40-character lowercase commit SHA", and when you set both ref and sha, the reference says Claude Code checks out the sha.

The reason to set sha is review. A marketplace maintainer reads a plugin at one commit and pins that commit, so that what users install is what was read, even if the plugin's author later pushes something else. The advisory's precondition, "pin to a safe hash", is exactly that workflow, and the attack is aimed at the moment the pin is supposed to pay off: the upstream repository is controlled by someone who wants to ship different code.

Why a commit name can lose to a branch name

Git's own documentation for git checkout explains how one string can mean two things. For the <branch> argument it says: "if it refers to a branch (i.e., a name that, when prepended with "refs/heads/", is a valid ref), then that branch is checked out. Otherwise, if it refers to a valid commit, your HEAD becomes "detached"".

A 40-character hexadecimal string is normally a commit name. On a host that permits it, as the advisory puts it, a branch can carry that same string as its name. When both exist, git checkout <string> has a rule for which one it means, and the rule puts the branch first. The advisory does not walk through which step of the install flow hit this, and we will not guess. What it does say is the important part: the checkout "silently" resolved to the branch tip, and nothing afterwards noticed.

One tempting fix does not help. The same page describes git checkout --detach [<branch>] as "The same as git checkout , except that instead of pointing HEAD at the branch, it points HEAD at the commit ID." With --detach, a name that refers to a branch still lands on that branch's tip; HEAD is just detached there. Changing the flags on the checkout is not the fix. Checking the result is.

The check that closes it

The patch is described as a verification that "the resulting HEAD matched the pinned SHA". The same check works anywhere you pin and check out commits yourself, in an install script, a CI step that vendors a tool, or a script that a coding agent wrote to fetch a dependency at a known commit:

pin="a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"
git checkout --quiet "$pin"
actual="$(git rev-parse HEAD)"
if [ "$actual" != "$pin" ]; then
  echo "checkout landed on $actual, expected $pin" >&2
  exit 1
fi

git rev-parse HEAD prints the full commit name of whatever is checked out, so the comparison does not depend on how the checkout interpreted its argument. If any rule, default or remote branch led somewhere else, the script stops before the code is used.

Two more habits make the comparison worth having:

  • Compare full SHAs. A pin of seven or twelve characters is a prefix. The marketplace reference requires the full 40 characters for sha, and your own scripts should do the same, so the comparison is exact.
  • Run the check before anything from the checkout executes. For plugins and tools, that means before install scripts, hooks or build steps, since those are the first thing an attacker-controlled commit would use.

What to do as a Claude Code user

  1. Check the version. claude --version should report 2.1.179 or later. The advisory says standard auto-update delivered the fix; installations that do not auto-update, such as Homebrew and WinGet according to the setup documentation, need a manual upgrade.
  2. Know which plugins you installed while on an older version. If you installed a plugin from a third-party marketplace before updating, the install itself is the moment that mattered. Consider reinstalling it after the update, so the pinned checkout runs again with the verification in place.
  3. Keep pinning. The bug was in how the pin was enforced, not in the idea. A marketplace entry with sha gives the client something to verify against; an entry with only ref gives it nothing.

What to do as a marketplace or tool author

  1. Pin with sha, not only ref. A branch or tag can move. A full commit SHA, checked after checkout, cannot.
  2. Treat the plugin's upstream as untrusted after review. The whole attack assumes the upstream repository changes hands or changes intent after you read it. That is the situation pins exist for.
  3. Add the HEAD comparison to your own fetch scripts. If your tooling clones repositories at pinned commits, it has the same shape as the install flow in the advisory.

It is easy to think of a 40-character SHA as unforgeable, and the commit it names is. What the advisory shows is that the string has to be interpreted by a command before it becomes a commit, and the command had more than one way to read it. The protection comes from asking Git what you actually got, after the fact.

Do you pin plugins, actions or tools to full SHAs, and does anything check that the checkout landed where the pin says? If you have found another place where a pinned name was interpreted as something else, share it in the comments below.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.