この記事の内容は、実際に2週間運用した結果に基づいています。完成品はOSSキットとして公開しています(cc-quality-gates)。
はじめに
AIコーディングエージェントに開発を任せていると、コミットが人間の手では追いつかないペースで積み上がります。筆者の環境では、並行して動く複数のエージェントセッションが30日で340コミット以上を積み上げます。
この状況で一番怖いのは、コードの品質でもバグでもなく、**「うっかり秘密情報(APIキー・トークン)を公開リポジトリにpushしてしまうこと」**です。一度公開されると、ボットが数分で拾って悪用されるため、削除では手遅れになります。
人間の目でレビューするのは物理的に不可能なので、公開の直前に自動で止める門番(ゲート)を自作しました。この記事は、その設計と2週間の運用実績の記録です。
結論(先に数字を)
| 項目 | 実績 |
|---|---|
| 運用期間 | 2週間(14日・push継続中に判定) |
| ゲートによる停止(block) | 5件 |
| うち正しい停止(真陽性) | 5件(100%) |
| 誤警報(誤block) | 0件 |
| 全履歴スキャンでの検出 | 17件(うち実際の秘密は0件・ローテーション不要と判定) |
「止めるべきものは確実に止め、無駄に止めない」——これが数値で確認できたので、本運用に昇格させ、キットとして公開しました。
設計:3層の門番
「1つの強い検知」に頼るのではなく、3つの役割を分担させる構成にしました。
[層1] pre-push hook …… 公開直前の関所(止める係)
[層2] GitHub Actions CI … 裏口対策(層1を迂回したpushも毎週全履歴を走査)
[層3] 人間のレビュー …… 文脈判定の委譲先(「これはテスト用ダミーか?」は機械に判断させない)
層1: push直前のフック
git push の直前に作動し、pushしようとしている差分だけを走査します(過去の全履歴を見ないので、昔のダミーデータで誤爆しません)。
探知機は次の2種類です。
- gitleaks(有名なOSS)——数百種類の既知パターンで「本物っぽい鍵の形」を検知
-
自作の単純パターン——
token = "16文字以上..."のような代入形式を自前でも検知(gitleaksのルール更新ラグの対策)
検知したらpushをブロックし、どこで引っかかったかだけを表示します。値そのものは表示しない(ログにも残さない)ので、ゲート自身が情報漏洩源になることもありません。
層2: CIによる最終防衛線
hookは --no-verify で迂回できてしまう、という正直な限界があります。そこでGitHub Actionsでも全pushと毎週1回の全履歴走査をさせ、迂回pushがCIでfailすることをテストで実証しておきました。
層3: 文脈判定は機械に持たせない
ここが設計上の一番大事な判断です。
「この
token = ...は本物か、テスト用のダミーか?」という文脈判断をhookに実装しない。
文脈判定をhookに入れると誤検知が温床になり、ゲートが生活を止めてしまいます(並行セッション5本が日常の環境では致命的)。そこでhookは単純パターンに限定し、止められたら人間が確認して、ダミーなら書き方を変えて再pushする運用にしました。「形式を変えろ」というブロックメッセージ自体が、「意図的ダミーであることの自己申告」を促します。
2週間の運用で起きたこと
導入から14日間、pushを続けながら観察しました。5件のblockが発生しましたが、内訳は:
- テスト用のモック鍵(E2Eテストで意図的に書いたもの)——正しく停止。書き方を変えて再push
- テストフィクスチャ内のダミートークン——正しく停止。同じく形式変更で解決
つまり、全部が「鍵っぽい文字列が本当に混ざっていた」ケースで、無駄止まりはゼロでした。逆に言うと、自分でも気づかないうちに「鍵の形をした文字列」が日常的にコミットに入ってくる——だから自動ゲートが必要、という実測でもあります。
合わせて、警告レベルの記録(68件)も全件レビューし、ブロックに昇格させるべきものがないことも確認しました。
キット化して公開した
2週間の実績を受けて、これを誰でも導入できるキットとして公開しました。
👉 github.com/fukukei23/cc-quality-gates
git clone https://github.com/fukukei23/cc-quality-gates.git
cd cc-quality-gates
bash scripts/setup.sh # gitleaksのパス等を質問に答えて設定
bash scripts/selftest.sh # 導入が正しく効いているか5項目で確認(PASS=5期待)
selftest.sh は単なる構文チェックではなく、モック鍵を実際にコミットしてhookが発火するかまで確認します。加えて「.gitleaks.toml の設定で検出を無効化されたらどうなるか」というバイパス耐性テストも入っています(gitleaks層を設定で無効化しても、自作パターン層が止める二重構造を確認できます)。
公開までに起きた実際のトラブル(読者への共有)
Linux(WSL2)では完璧に動いたキットが、Windowsでは5件の問題で動きませんでした。同じ悩みを持つ人のために、対応を残しておきます:
| 問題 | 対応 |
|---|---|
| gitの改行コード自動変換(LF→CRLF)でbashスクリプトが構文エラー |
.gitattributes でスクリプトの改行をLF固定 |
Windowsは実行ファイルに .exe が付く |
セットアップ/テストで拡張子を自動解決 |
| 初めてのPCにはgitのユーザー設定が無くテストコミットが失敗 | 設定が無い場合のフォールバックを追加 |
| 対話式セットアップが一部のターミナル経由で動かない | 環境変数による非対話モードを追加 |
(開発中に実装ミスで「存在するgitleaksまで .exe に書き換える」バグも作り込みましたが、テストが実動作で捕捉してくれました。テストは書いておいて損になりません)
既知の限界(正直な申告)
-
token2 = ...のような接尾語付き変数名は、単純パターン層を素通りします(正規表現の単語境界の問題・CI層のgitleaksは拾う可能性があります)。文脈判定を入れない設計方針の代償として、正直にREADMEに書いています - hook単体は
--no-verifyで迂回できます(最終防衛線はCI側)
まとめ
- AIエージェントが大量にコミットする環境では、秘密情報のうっかり公開は「起こる前提」で対策すべき
- 検知は1つの強いツールでなく、hook(止める)+ CI(裏口)+ 人間(文脈)の3層分担が現実的
- 導入して終わりではなく、2週間の運用データ(真陽性5・誤警報0)で効いていることを数字で確認してから本運用に昇格した
キットはこちらからどうぞ。issue / PRも歓迎です。
※ この記事自体も、公開前に記事本文への機微スキャン(gitleaks)を通しています。ゲートを語る記事がゲートをすり抜けたら笑えないので。
本記事は個人Zennでも公開しています。