Skip to content

git hook実行下でのpytestが、nested git subprocessの環境漏洩で実リポジトリのconfig/indexを破壊しうる #46

Description

@senna-lang

概要

make hooksで入る .git/hooks/pre-commitmake check実行)や、同様に make check/pytest を呼ぶ pre-push フックの内部から pytest を実行すると、一部テストがtmpディレクトリ内で行う git init/git commit/git config のnested subprocess呼び出しが、実リポジトリのgit状態を書き換えてしまう。観測した実害:

  1. 実リポジトリの .git/configcore.bare = true と、テスト用の user.email = test@example.com / user.name = test が書き込まれる(git statusfatal: this operation must be run in a work tree で全面的に失敗するようになる)。
  2. 実リポジトリの index が、nested コミットの内容(tmpフィクスチャ側のファイル状態)で上書きされ、git status/git diff HEAD が実際には存在する全ファイルを "deleted" として表示する(ディスク上のファイル自体は無事、index のみ破壊)。
  3. 破壊された index に対して再度 git commit/git push すると、今度は実リポジトリの pre-commit フック自体が nested 側から誤発火し、tmp フィクスチャに Makefile が無いため make: *** No rule to make target \check'` で nested コミットが失敗 → 該当テストが失敗する。

再現手順

  1. make hooks で pre-commit フックを導入(または同等の make check を呼ぶ pre-push フックがある状態)。
  2. 何らかの変更を git commit(または git push)し、フック経由で make checkpytest が走る。
  3. pytest 内で以下のようなテストが実行される: tests/test_file_renames.py_make_git_repo_with_rename 等)、tests/test_core_ingest.pytest_ingest_resolves_symbols_against_touch_time_git_blob_not_live_disk 等)、tests/test_db.py::test_backfill_touch_time_symbol_edges_*tests/test_eval_cmd.py::test_eval_gen_writes_symbol_recall_dataset。これらは共通して tmp_path 配下で subprocess.run(["git", "init"/"config"/"commit", ...], cwd=tmp_path) を行うが、呼び出し元プロセスの環境をそのまま継承している。
  4. commit/push 後に実リポジトリで git status を実行すると壊れている。

根本原因(推定)

git はフック(pre-commit/pre-push)を起動する際、GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE / GIT_PREFIX 等を子プロセスの環境変数として export する。フック → make checkpytest → 上記テストの subprocess.run はこの環境をそのまま継承するため、テストが cwd=tmp_pathgit init した「つもり」でも、後続の git commit/git config が継承された GIT_DIR(実リポジトリの gitdir)を見てしまい、実リポジトリの config/index に対して操作してしまう。

影響

  • make hooks を実行した全コントリビューターが対象。フック経由で日常的な git commit/git push のたびに発火しうる。
  • 実リポジトリの git identity・core.bare・index が静かに破壊される。ディスク上のファイル自体は無事(git reset --mixed HEAD + git config 修正で復旧可能)だが、気づかずに git commit --amend/git reset --hard 等を打つと実データ損失になり得る。
  • 副次影響として、フック経由の make check 自体が誤って失敗し、CI/ローカル双方で原因不明のテスト失敗として現れる(本Issueの発見経緯: アーキテクチャ境界の歪み: adapters 多義語 / persistence→harness 層破り / cli __init__ docstring ドリフト #39 のPR作成中に git commitgit push で2回連続発生)。

修正案

上記テストの _git/_run ヘルパー(tests/test_file_renames.py:45tests/test_core_ingest.py:211tests/test_db.py:1860tests/test_eval_cmd.py:134 他)で subprocess.run に渡す env から GIT_DIRGIT_WORK_TREEGIT_INDEX_FILEGIT_PREFIXGIT_COMMON_DIR 等の GIT_* 継承変数を明示的に除去してから呼び出す(env={k: v for k, v in os.environ.items() if not k.startswith("GIT_") or k in ("GIT_AUTHOR_DATE", "GIT_COMMITTER_DATE")} のような allowlist/denylist)。ヘルパーが複数ファイルに重複しているため、共通の conftest.py フィクスチャ/ヘルパーに集約するのが望ましい。

発見経緯

issue #39 (PR #45) 作業中に git commitgit push で2回連続再現。実リポジトリの .git/config は手動で復旧済み(core.bare=false、テスト由来の user.* 削除、git reset --mixed HEAD で index復旧)。

Activity

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

    P1-highHigh: correctness bug

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions