
Verifying Claude Code's CVE-2026-86063 locally: git checkout prioritizes a branch of the same name over a commit SHA
This page has been translated by machine translation. View original
Hello! I'm Yuji Nishimura from the Operations Department!
There was an issue (CVE-2026-86063) in the installation process of Claude Code plugins where code different from the commit SHA pinned by the marketplace could be installed. The direct cause was that the installation process did not verify HEAD against the pinned SHA after checking it out. Behind this lies git's name resolution behavior, in which git checkout gives priority to a branch with the same name as a commit SHA. If you pin a SHA and run git checkout in your own scripts or CI, you may have the same pitfall.
Conclusion first: what to check and how to protect yourself
| What to check | Countermeasure |
|---|---|
Is claude --version 2.1.179 or later? |
Update to 2.1.179 or later. If you use auto-update, it has already been applied |
Do your own scripts or CI run git checkout with a SHA specified? |
Check out with <SHA>^{commit} or FETCH_HEAD, and finally verify with git rev-parse HEAD |
What happened
According to the GitHub security advisory (GHSA-pq7j-f95f-qfcv), the problem was in the plugin installation process of Claude Code and Claude Desktop. After checking out the commit SHA pinned by the marketplace with git checkout, the installation process did not verify whether HEAD matched that SHA. As a result, if a branch with the same name as the SHA existed, unreviewed code could be installed.
- Affected versions: npm
@anthropic-ai/claude-codeearlier than 2.1.179. The advisory does not state a fixed version for Claude Desktop - Severity: High. The CVSS v4 score is 7.7 in the repository advisory and 7.5 in the GitHub Advisory Database
- Publication date: The repository advisory was published on October 9, 2026. According to the GitHub Advisory Database record, the publication on NVD was on October 13
- Reporter: Or Nevo Michrowski of Air Security
- Response: Fixed in 2.1.179. The fix has already reached users with auto-update, and for those who update manually, applying the latest version is recommended
Flow of the attack
Based on the advisory's description, the flow of the attack can be divided into three stages as follows.
- Prerequisites: The user installs a malicious plugin and pins it to a safe commit SHA. The plugin's hosting service allows branch names shaped like a SHA
- Intrusion and exploitation: An attacker who can operate the plugin's upstream repository creates a branch with the same name as the pinned SHA and points it at a different commit
- Impact: The installation process's
git checkoutresolves to the branch, and unreviewed code is installed
This is not an issue that anyone can exploit. Even so, it means that pinning by SHA alone cannot guarantee that reviewed code is installed.
Why this works
git checkout <name> switches to a branch if the argument matches the branch name. On the other hand, revision parsing such as git rev-parse treats a 40-character hexadecimal string as a commit ID even if a ref with the same name exists. Git's warning message also states that a ref ending with a 40-character hexadecimal string is ignored when you specify it with 40 characters.
If a ref with the same name exists when you pass a 40-character hexadecimal string, git emits the warning refname '...' is ambiguous. However, the warning goes only to standard error, the process does not stop, and the exit code is 0. Tools judge success or failure by exit code, and standard error tends to get buried in logs, so when a tool calls git checkout, this warning is easy to miss.
Verifying it locally
I verified it with git 2.55.0 on macOS 26.6.1. In an empty repository, I created commit X, which represents reviewed code, and commit Y, which represents unreviewed code, then pointed a branch with the same name as X's SHA at Y. I put the whole procedure into a short script to run in a disposable directory.
cd "$(mktemp -d)" && git init -q -b main demo && cd demo
git commit -q --allow-empty -m "X (reviewed)"; X=$(git rev-parse HEAD)
git commit -q --allow-empty -m "Y (not reviewed)"; Y=$(git rev-parse HEAD)
git reset -q --hard "$X"
git branch "$X" "$Y"
In this state, I compared what the same $X resolves to. In the image, the pinned SHA is shown as $PIN.

| Operation | Resolves to |
|---|---|
git rev-parse $X |
X (reviewed) |
git checkout $X |
Y (unreviewed) |
git checkout --detach $X |
Y (unreviewed) |
git switch --detach $X |
Y (unreviewed) |
git checkout --detach $X^{commit} |
X (reviewed) |
git checkout $X emitted a warning and then switched to branch Y, with an exit code of 0. Even with --detach, it resolves to the branch. With ^{commit} appended, the string is parsed as a revision, so the pinned commit X is checked out.
On the other hand, I also tried the case where a branch with the same name exists only on the remote. I placed a branch with the same name in a local bare repository, cloned it via file://, and ran git checkout $X as is; in my environment, X was checked out as a detached HEAD. It resolved to the branch when a local branch with the same name existed. The advisory does not describe how a state with a local branch came about in the actual installation process.
Checking whether your environment is affected
First, check the Claude Code version.
claude --version
If it is 2.1.179 or later, it is fixed. In my environment it was 2.1.300.
Next, look for places in your own scripts or CI where git checkout is run with a SHA specified. The commands below only pick up lines where the SHA is written directly. If the SHA is passed via a variable, you need to check each place that calls git checkout or git switch individually.
git grep -nE 'checkout .*[0-9a-f]{40}'
git grep -nE 'git (checkout|switch)'
You can also check whether a repository you have already fetched contains a ref with the same name as a SHA.
# Check inside a repository that has already been fetched
git for-each-ref --format='%(refname)' | grep -E '/[0-9a-f]{40}$'
# Check the remote side before cloning
git ls-remote <リポジトリのURL> | grep -E 'refs/heads/[0-9a-f]{40}$'
As the git warning message says, a ref ending with a 40-character hexadecimal string is something git normally does not create. If you find one, suspect that a commit may have been swapped by a branch with the same name as the SHA. If refname '...' is ambiguous appears in your CI logs, that is also a clue.
How to protect yourself
When writing your own process that pins a SHA and checks it out, settle on how to check out and pair it with verification after the checkout. There are two ways to check out. I confirmed in the verification above that both check out X.
PINNED="<固定したい40文字のSHA>"
# Checkout method 1: have it parsed as a revision and check it out
git checkout --detach "${PINNED}^{commit}"
# Checkout method 2: fetch by specifying the commit, then check out FETCH_HEAD
# git fetch origin "$PINNED"
# git checkout --detach FETCH_HEAD
# Verification: whichever method you used, check at the end and stop on mismatch
test "$(git rev-parse HEAD)" = "$PINNED" || { echo "pinned commit mismatch" >&2; exit 1; }
I verified the SHA-specified git fetch in checkout method 2 on a local file:// repository. Depending on the host or server configuration, fetching by specifying a SHA may not be permitted.
The final verification was able to detect the mismatch between X and Y in my local test as well. It can be used regardless of how you check out, so you will notice a mismatch even if the procedure changes in the future.
Summary
CVE-2026-86063 was an issue where SHA pinning could be bypassed in Claude Code's plugin installation. It arose from the combination of the installation process not verifying HEAD after checkout and git's behavior in which git checkout gives priority to a branch with the same name as a commit SHA. Claude Code was fixed in 2.1.179, and if you use auto-update, you are already covered.
If you pin a SHA in your own scripts or CI, checking out with ^{commit} or FETCH_HEAD and finally verifying with git rev-parse HEAD lets you avoid the same pitfall.
I hope this helps someone.
References:
