Claude CodeのCVE-2026-86063を手元で確かめる。git checkoutはコミットSHAより同名ブランチを優先する

Claude CodeのCVE-2026-86063を手元で確かめる。git checkoutはコミットSHAより同名ブランチを優先する

Claude Codeのプラグイン導入でコミットSHAの固定をすり抜けられた問題を、空コミットだけのローカルリポジトリで確かめました。git checkoutが同名ブランチを優先する仕組みと、SHA固定の取得を安全に書く方法を整理します。
2026.10.17

どうも!オペ部の西村祐二です!

Claude Codeのプラグインの導入処理で、マーケットプレイスが固定したコミットSHAとは別のコードが入ってしまう問題(CVE-2026-86063)がありました。直接の原因は、導入処理が取り出したあとのHEADを固定したSHAと照合していなかったことです。その背景には、git checkoutがコミットSHAと同じ名前のブランチを優先するという、gitの名前解決の仕組みがあります。自前のスクリプトやCIでSHAを固定してgit checkoutしている場合も、同じ落とし穴を抱えている可能性があります。

先に結論:確認と対策

確認すること 対策
claude --versionが2.1.179以上か 2.1.179以降に更新する。自動更新を使っていれば適用済み
自前のスクリプトやCIで、SHAを指定してgit checkoutを実行していないか <SHA>^{commit}かFETCH_HEADで取り出し、最後にgit rev-parse HEADで照合する

何が起きたのか

GitHubのセキュリティアドバイザリ(GHSA-pq7j-f95f-qfcv)によると、問題はClaude CodeとClaude Desktopのプラグイン導入処理にありました。導入処理は、マーケットプレイスが固定したコミットSHAをgit checkoutで取り出したあと、HEADがそのSHAと一致するかを確かめていませんでした。そのため、SHAと同じ名前のブランチがあると、未レビューのコードが入るおそれがありました。

  • 影響を受けるバージョン: npmの@anthropic-ai/claude-code 2.1.179未満。Claude Desktopの修正バージョンはアドバイザリに記載がありません
  • 深刻度: High。CVSS v4のスコアは、リポジトリのアドバイザリでは7.7、GitHub Advisory Databaseでは7.5です
  • 公開日: リポジトリのアドバイザリが2026年10月9日。GitHub Advisory Databaseの記録では、NVDでの公開は10月13日です
  • 報告者: Air SecurityのOr Nevo Michrowski氏
  • 対応: 2.1.179で修正。自動更新の利用者には修正が届いており、手動で更新している場合は最新版の適用が推奨されています

攻撃の流れ

アドバイザリの記述をもとに、攻撃の流れを3段に分けると次のとおりです。

  1. 前提条件: 利用者が悪意のあるプラグインを導入し、安全なコミットSHAに固定している。プラグインのホスト先が、SHAの形をしたブランチ名を許可している
  2. 侵入・悪用: プラグインの上流リポジトリを操作できる攻撃者が、固定されたSHAと同じ名前のブランチを作り、別のコミットを指させる
  3. 影響: 導入処理のgit checkoutがブランチ側に解決され、未レビューのコードが入る

誰でも悪用できる問題ではありません。それでも、SHAで固定するだけでは、レビュー済みのコードが入ることを保証できないということになります。

なぜ成立するのか

git checkout <名前>は、引数がブランチ名と一致するとそのブランチへ切り替えます。一方でgit rev-parseなどのリビジョン解析は、40文字の16進文字列を受け取ると、同じ名前のrefがあってもコミットIDとして扱います。gitの警告文にも、40文字の16進文字列で終わるrefは、40文字で指定したときには無視されると書かれています。

40文字の16進文字列を渡したときに同じ名前のrefがあると、gitはrefname '...' is ambiguousという警告を出します。ただし警告は標準エラーに出るだけで、処理は止まらず、終了コードも0です。ツールは終了コードで成否を判定し、標準エラーはログに埋もれがちなので、ツールからgit checkoutを呼ぶとこの警告に気付きにくくなります。

手元で確かめる

macOS 26.6.1上のgit 2.55.0で確かめました。レビュー済みを想定したコミットXと、未レビューを想定したコミットYを空のリポジトリに作り、XのSHAと同じ名前のブランチをYに向けます。全体の手順は、使い捨てのディレクトリで実行する短いスクリプトにまとめました。

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"

この状態で、同じ$Xを渡したときの解決先を比べました。画像では、固定したSHAを$PINと表示しています。

SHAと同名のブランチがあるとき、rev-parseはXを、checkoutとswitch --detachはYを、^{commit}を付けたcheckoutはXを返す

操作 解決先
git rev-parse $X X(レビュー済み)
git checkout $X Y(未レビュー)
git checkout --detach $X Y(未レビュー)
git switch --detach $X Y(未レビュー)
git checkout --detach $X^{commit} X(レビュー済み)

git checkout $Xは警告を出したうえでブランチYに切り替わり、終了コードは0でした。--detachを付けてもブランチ側に解決されます。^{commit}を付けると、文字列がリビジョンとして解析されるため、固定したコミットXが取り出されます。

一方で、同名のブランチがリモートにだけある状態も試しました。ローカルのbareリポジトリに同名のブランチを置いてfile://でcloneし、そのままgit checkout $Xを実行すると、手元ではXがdetached HEADで取り出されました。ブランチ側に解決されたのは、同じ名前のローカルブランチがある場合です。実際の導入処理で、どのような経緯でローカルブランチができる状態になっていたかは、アドバイザリに書かれていません。

自分の環境が影響を受けるか確かめる

まず、Claude Codeのバージョンを確認します。

claude --version

2.1.179以上なら修正済みです。手元では2.1.300でした。

次に、自前のスクリプトやCIでSHAを指定してgit checkoutしている箇所を探します。下のコマンドで拾えるのは、SHAを直接書いている行だけです。変数でSHAを渡している場合は、git checkoutやgit switchを呼んでいる箇所を個別に確認する必要があります。

git grep -nE 'checkout .*[0-9a-f]{40}'
git grep -nE 'git (checkout|switch)'

すでに取得済みのリポジトリに、SHAと同名のrefがないかも確認できます。

# 取得済みのリポジトリの中を確認する
git for-each-ref --format='%(refname)' | grep -E '/[0-9a-f]{40}$'

# clone する前にリモート側を確認する
git ls-remote <リポジトリのURL> | grep -E 'refs/heads/[0-9a-f]{40}$'

40文字の16進文字列で終わるrefは、gitの警告文にあるとおり、gitが通常は作らないものです。見つかった場合は、SHAと同名のブランチでコミットがすり替えられていないかを疑います。CIのログにrefname '...' is ambiguousが出ていれば、それも手がかりになります。

守り方

SHAを固定して取り出す処理を自分で書く場合は、取り出し方を固めたうえで、取り出したあとの照合までをセットにします。取り出し方は次の2通りです。どちらも、上の検証でXが取り出されることを確かめました。

pinned-checkout.sh
PINNED="<固定したい40文字のSHA>"

# 取り出し方1: リビジョンとして解析させて取り出す
git checkout --detach "${PINNED}^{commit}"

# 取り出し方2: コミットを指定して取得し、FETCH_HEAD を取り出す
# git fetch origin "$PINNED"
# git checkout --detach FETCH_HEAD

# 照合: どちらで取り出した場合も、最後に確かめて不一致なら止める
test "$(git rev-parse HEAD)" = "$PINNED" || { echo "pinned commit mismatch" >&2; exit 1; }

取り出し方2のSHAを指定したgit fetchは、手元のfile://のリポジトリで確かめました。ホストやサーバーの設定によっては、SHAを指定した取得が許可されていない場合があります。

最後の照合は、手元の検証でもXとYのずれを検出できました。取り出し方に関係なく使えるので、将来、手順が変わってもずれに気付けます。

まとめ

CVE-2026-86063は、Claude Codeのプラグイン導入でSHAの固定をすり抜けられた問題でした。導入処理が取り出したあとのHEADを照合していなかったことと、git checkoutがコミットSHAと同じ名前のブランチを優先するgitの仕組みが重なって起きています。Claude Codeは2.1.179で修正されていて、自動更新を使っていれば対応済みです。

自前のスクリプトやCIでSHAを固定している場合は、^{commit}かFETCH_HEADで取り出し、最後にgit rev-parse HEADで照合しておくと、同じ落とし穴を避けられます。

誰かの参考になれば幸いです。


参考リンク:


Claudeならクラスメソッドにお任せください

クラスメソッドは、Anthropic社とリセラー契約を締結しています。各種製品ガイドから、業種別の活用法、フェーズごとのお悩み解決などサービス支援ページにまとめております。まずはご覧いただき、お気軽にご相談ください。

サービス詳細を見る

この記事をシェアする

AI白書

関連記事