Skip to content
frontmatter
キー値
specdojo.idprj-0001:pjr-76ag-test-git-env-leak-repo-corruption
specdojo.typeproject
specdojo.statusready
specdojo.rulebookspecdojo:pjr-rulebook
specdojo.part_ofprj-0001:pjr-index
specdojo.item_typeissue
specdojo.item_statusdone
specdojo.priorityhigh
specdojo.ownerARC
specdojo.registered_at2026-08-28T10:10:24Z
specdojo.due_on2026-09-30
specdojo.completed_at2026-08-28T10:37:27Z
specdojo.conclusionテスト実行の入口でrepositoryの位置を決める環境変数を除去する構造にした。GIT_LOCAL_ENV_VARSをsrc/git-environment.tsへ集約し、gitEnvironment()とVitest setupが同じ正本を参照する。3つのvitest設定すべてに共通setupを入れ、各workerがtest moduleを読み込む前にGIT_DIR・GIT_INDEX_FILE・GIT_WORK_TREE等を除去するため、個々のテストがenvを渡し忘れても汚染しない。PJR-EX5Eのidentity注入は維持している。検査は文字列走査からTypeScript AST解析へ置き換え、env: process.envを不合格とし、gitEnvironment()由来であることを追跡する。検証として、envを意図的に渡さないテストをGIT_DIRを設定して実行し使い捨てリポジトリが無傷であることと、setupFilesを外すと回帰テストが失敗することを確認した。3度の再発それぞれで見落とした境界も記録した。復旧手順はexec運用ガイドへ記載した。

PJR-76AG テストがgitフック配下でGIT_DIRを引き継ぎ実リポジトリを破壊する事故が3度目の再発をした ​

1. 課題内容 ​

PJR-TPY9 の実行結果をコミットした際、lefthook の pre-commit が npm test を起動し、その中で新規追加されたテストが一時ディレクトリのつもりで git init / add / commit を実行した。git フック配下では親の git が GIT_DIR / GIT_INDEX_FILE を環境変数へ設定するため、env を渡さずに git を起動すると実リポジトリへ適用される。共有 config へ core.bare=true と user.name=test が書き込まれ、worktree ブランチへ不正コミットが作られた。いずれも復旧済みで push 済み履歴には到達していない。PJR-X3E8、PJR-A99J に続く3度目の同型再発であり、規約の記述だけでは再発を止められないことが確認された。

2. 影響範囲 ​

観点影響
スコープテスト実行環境(vitest.config.ts、tests/helpers/git-environment.ts)、git を起動する全テスト、lefthook の pre-commit 構成
スケジュール直接の遅延はない。ただし復旧と調査で PJR-TPY9 の完了が中断した
コスト事故のたびに復旧作業が発生する。今回は config 復旧・不正コミット破棄・被害範囲確認を要した
品質共有リポジトリの config と履歴が壊れる。core.bare=true により全 worktree が使用不能になり、汚染された identity でのコミットは後から書き換えられない
関係者全開発者と全 agent。フックを通る操作すべてが経路になる

今回の被害と復旧(確定情報) ​

被害復旧
共有 config へ core.bare=true済
共有 config へ user.name=test / user.email=test@example.com済
worktree ブランチへ不正コミット1件(squashed register transitions)済
project/prj-0001/develop および push 済み履歴到達していない

3. 対応方針 ​

項目内容
原因テストプロセスが git フックから継承した GIT_DIR / GIT_INDEX_FILE / GIT_WORK_TREE を落としていない。個々のテストが env を渡し忘れると、一時ディレクトリでの git 操作が実リポジトリへ着弾する
対応策テスト実行の入口で git の位置を決める環境変数を除去する。呼び出し箇所ごとの規約ではなく、環境そのものを無害化する
依存事項なし。vitest.config.ts と src/exec-worktree.ts の GIT_LOCAL_ENV_VARS が既にある
完了条件下記のとおり

完了条件 ​

  • テストプロセスで git の位置を決める環境変数が除去されている。GIT_DIR を設定した状態で npm test を全件実行し、その GIT_DIR が指すリポジトリと実リポジトリのいずれも変化しないことを確認する。個々のテストの書き方に依存しないことが要件である。
  • 除去する変数の一覧を二重に持たない。src/exec-worktree.ts の GIT_LOCAL_ENV_VARS を参照するか、共通の定義へ寄せる。
  • 既存の identity 注入を壊さない。TEST_GIT_ENVIRONMENT は PJR-EX5E の対策であり、GIT_AUTHOR_* / GIT_COMMITTER_* / commit.gpgsign は引き続き適用される必要がある。
  • 現在の検査(tests/src/git-environment-isolation.test.ts)を、env が渡っているかではなく gitEnvironment() が使われているかまで見るよう強める。env: process.env を通さない。困難な場合は、なぜ強められないかを記録して代替を示す。
  • TypeScript 以外からの git 起動の扱いが決まっている。シェルスクリプトや npm scripts は現在の検査の対象外である。フックから呼ばれる経路に限定するか、全体を対象にするかを判断して記録する。
  • 3度再発した理由が個別に記録されている。PJR-X3E8、PJR-A99J とも原因を直したと判断した後に再発した。それぞれで何を見落としたか、今回の対処がその見落としをどう塞ぐかを書く。
  • 事故時の復旧手順が文書化されている。core.bare の復旧、汚染 config の除去、不正コミットの破棄、被害範囲(push 到達の有無)の確認方法を含める。
  • npm run typecheck、npm run lint:ts、npm run test:unit、npm run test:integration が成功する。

調査済みの事実 ​

  • 発生経路は agent の実行中ではなく、人がコミットしたときの lefthook pre-commit の中である。PJR-A99J で対処した agent 起動時の隔離とは別の経路である。
  • git を起動する既存の13ファイルはすべて gitEnvironment() を経由していた。tests/src/exec-agent-protected-config.test.ts には今回とまったく同じ失敗を説明するコメントがある。規約は存在していたが、新しいテストがそれを知らずに書かれた。
  • 暫定対処として tests/src/git-environment-isolation.test.ts を追加した。src / tests / tools / scripts の git 起動箇所で env が渡っていなければ失敗する。env を渡さないファイルを置くと実際に検出することを確認済みである。ただし env: process.env は通過する。
  • 対処後、使い捨てリポジトリを GIT_DIR に指定して全1,386件のテストを実行し、その使い捨てリポジトリと実リポジトリのいずれも変化しないことを確認した。
  • 対策の仕組みは既に存在し、片手落ちであった。vitest.config.ts は tests/helpers/git-environment.ts の TEST_GIT_ENVIRONMENT を test.env へ渡している。これは PJR-EX5E(identity 混入)の対策で入ったもので、GIT_AUTHOR_* / GIT_COMMITTER_* / commit.gpgsign を注入する。しかしリポジトリの位置を決める GIT_DIR / GIT_INDEX_FILE / GIT_WORK_TREE は落としていない。ここを除去していれば、個々のテストの書き方に関係なく今回の事故は起きなかった。
  • vitest の test.env は値の設定であり、既存の環境変数の削除に使えるかは未確認である。setupFiles で process.env から削除する方法とあわせて確認する。worker とその子プロセスに適用されればよい。

4. 対応結果 ​

解決内容:

  • repositoryの位置を決める環境変数一覧をsrc/git-environment.tsのGIT_LOCAL_ENV_VARSへ移し、gitEnvironment()とVitest setupが同じ正本を参照する構造にした。src/exec-worktree.tsからの既存exportは維持しているため、既存呼び出し元の互換性は変えていない。
  • vitest.config.ts、vitest.unit.config.ts、vitest.integration.config.tsの全入口へ共通setupを設定した。各workerはtest moduleを読み込む前にGIT_DIR、GIT_INDEX_FILE、GIT_WORK_TREEなどをprocess.envから除去し、個々のテストがenvを渡し忘れても子プロセスはcwdからrepositoryを解決する。
  • PJR-EX5Eで導入したTEST_GIT_ENVIRONMENTは変更せず、author / committer identityとcommit.gpgsign=falseの一時設定を引き続きworkerへ注入する。回帰テストでrepository固有変数が未設定であることとidentityが設定済みであることを同時に確認する。
  • tests/src/git-environment-isolation.test.tsの文字列window検査をTypeScript AST検査へ置き換えた。envプロパティの有無ではなく、直接呼び出し、短縮プロパティ、拡張objectのいずれもgitEnvironment()由来であることを追跡するため、env: process.envは不合格になる。

3度の再発で見落とした境界:

  • PJR-X3E8は事故を起こした1テストへenv: gitEnvironment()を追加し、記述規約を設けたが、テスト実行の入口は隔離しなかった。今回、Vitest workerのsetupを必須にして、個別テストの遵守に依存しない防御を追加した。
  • PJR-A99Jはagent起動環境の隔離とagent前後のGit状態検知を実装したが、人がcommitした際にagent外で起動するlefthook pre-commitのnpm testはその境界を通らなかった。今回、全Vitest設定を直接防御対象に追加した。
  • PJR-TPY9の暫定検査はGit起動optionにenvという語があれば通り、env: process.envも許可した。また既存のVitest共通環境はidentity注入だけでrepository固有変数を除去しなかった。今回、ASTでgitEnvironment()由来を検証し、共通setupでworker環境自体を無害化する二層にした。

TypeScript以外の扱い:

  • 現在の静的検査はsrc、tests、tools、scripts配下のTypeScriptからspawnSync、spawn、execFileSync、execFileでGitを起動する箇所を対象とする。shell scriptとnpm scriptはAST検査の対象外とした。
  • 調査時点でlefthookから直接Git fixtureを起動するshell / npm scriptはなく、事故経路はpre-commitからVitestへ入るため共通setupで遮断できる。将来hookから直接Gitを起動するテスト用scriptを追加する場合は、その入口で同じ変数一覧を除去する専用検査を追加する。この境界をSpecDojo システム設計横断ルールとexec設定ガイドへ記録した。

復旧と確認:

  • exec運用ガイドへ、共有Git directoryの特定、core.bare=falseへの復旧、事故で混入したlocal identityだけの除去、reflogによる変更前HEAD特定、remote branch / tagへの到達有無確認、未pushの不正commitを含むworktree / branchの破棄、push済みの場合のrevert方針を記録した。
  • npm run typecheckとnpm run lint:tsは成功した。危険なGIT_DIR / GIT_INDEX_FILEを設定して共通setupだけを読み込むスモーク確認でも、両変数が削除された。
  • npm run test:unit、npm run test:integration、npm run validate:schemaはexecutor内で二重実行せず、pipeline親runnerの検証結果を正本とする。

5. 関連ドキュメント ​