はじめに
API キー・パスワード・秘密鍵を誤って Git にコミットしてしまう事故を防ぐ pre-commit フック keygate を作成し、PyPI に公開しました。
いまの開発は、人間が少しずつ手で書くよりも、Claude Code や Codex のような AI エージェントに一気に差分を作らせる場面が増えています。そこで keygate も、AI エージェント時代のローカル開発フローに合わせたシークレット検知として設計しています。
- PyPI: https://pypi.org/project/keygate/
- ライセンス: MIT
- GitHub: https://github.com/kanekoyuichi/keygate
pipx install keygate
cd your-project
keygate install-hook
これだけで、以降の git commit に混入したシークレットを自動で検知・ブロックしてくれます。
AI エージェントがコードを書く時代のガードレールとして
keygate は、単に「人間の打ち間違いを防ぐツール」としてだけでなく、AI エージェントがコードを書く時代のガードレールとして設計しました。
Claude Code や Codex のようなコーディングエージェントを使うと、実装・修正・テスト・コミット準備までの速度が大きく上がります。一方で、速度が上がるほど「差分を細部まで確認する前にコミットしてしまう」リスクも上がります。
特に AI エージェントは、以下のような状況を作りやすくなります。
-
.envや設定例を参考にしてコードを生成する - README やテスト fixture に含まれるサンプル値をそのまま展開する
- 既存コードの周辺文脈から
api_keyやpasswordらしき値を補完する - 人間が確認する前提で、一度に大きめの差分を作る
もちろん、AI エージェントが意図的に秘密情報を漏らすわけではありません。ただ、開発スピードが上がるほど、最後に機械的な安全確認を挟む価値が上がります。
keygate はこの前提で、AI が書いたコードでも、人間が書いたコードでも、コミット直前に同じ基準で止めることを狙っています。README を読んだ人だけが正しく使えるツールではなく、JSON 出力や AI エージェント向けの実行モードを用意し、エージェント自身が検知結果を読んで修正提案へつなげられる形にしました。
なぜ作ったのか
開発中、コードに API キーを直接書いてしまうことは誰にでもあります。問題は、それをそのまま git commit すると リポジトリの履歴に永久に残る ことです。
-
git rmや強制 push で消しても、過去の SHA を指せば取り出せる - GitHub に push してしまえば数秒で bot にスクレイピングされる
- AWS のキーなら高額請求、OpenAI のキーなら課金枠の消費まで一瞬
この「うっかり」を コミットの瞬間に止める ツールが必要でした。既存ツール(gitleaks、trufflehog 等)は優秀ですが、フルリポジトリスキャンや CI 向けの機能が中心で、ローカルの pre-commit 体験に最適化されたものが欲しかったのが出発点です。
さらに今後は、コードを書く主体が人間だけではなくなります。AI エージェントが実装を進め、人間がレビューして取り込むワークフローでは、commit 前の自動チェックはより重要になります。keygate はその変化を前提に、ローカルで高速に動き、判断理由を明示し、機械可読な JSON 出力でも同じ検知結果を扱えるようにしています。
もう少し具体的に言うと、keygate は git diff --cached の追加行だけ を見る設計です。フルリポジトリスキャンではなく、これからコミットされる差分だけを高速にチェックするので、pre-commit hook として日常運用しやすいバランスにしています。
keygate が検知するもの
以下を組み合わせて判定します:
ルールベース(既知のフォーマット)
- AWS アクセスキー(
AKIA*/ASIA*/AROA*) - OpenAI API キー(
sk-*) - GitHub トークン(
ghp_*、fine-grained PAT) - Slack トークン(
xoxb-*/xoxp-*) - Stripe キー(
sk_live_*/rk_live_*/pk_live_*) - SendGrid(
SG.*.*) - JWT、PEM 形式秘密鍵(RSA / OpenSSH)
- URL 内 credentials(
postgres://user:pass@host等)
pk_live_* のような公開前提の値や、postgres://user:***@host のようにマスク済みの URL credentials は、いきなり BLOCK ではなく WARN 相当 に落として扱います。危険なものは止めつつ、ドキュメント用途の文字列まで全部ブロックしないためです。
エントロピー検知
- Shannon entropy 4.0〜4.5 以上の 20 文字超文字列
コンテキスト検知
- 変数名(
api_key、password、secret_token等)を HIGH / MID の tier で区別 - パス(
.env、config.yaml、settings.py等)を tier 分け - 代入構文(
NAME = "..."/export NAME=...)
動作例
危険なものが含まれているとコミットが停止します:
[BLOCK] High confidence secret detected
File: config.py:12
Rule: aws-access-key
Score: 100
Reason:
AWS Access Key detected; sensitive context detected
Remediation:
- Remove the key from the code
- Rotate the AWS credentials immediately
- Use environment variables or AWS IAM roles instead
To ignore:
Add comment: # keygate: ignore reason="..."
読み方:
-
File— 問題のあるファイルと行番号 -
Rule— 検知ルール ID -
Score— 危険度(70以上で自動ブロック、40〜69 は WARN のみ) -
Remediation— 具体的な修正提案
text 出力の先頭には、AI やスクリプトが拾いやすいサマリ行も出ます。
[KEYGATE] status=block findings=1
人間にはそのまま読める文章を出しつつ、機械側はこのサマリや JSON 出力へ切り替えて扱えるようにしています。
AI エージェント向けの出力も用意している
AI エージェントに組み込むなら、text より JSON の方が扱いやすいです。keygate では以下を用意しています。
keygate scan --format json
keygate scan --json
keygate scan --profile agent
-
--format jsonは stdout に JSON だけを出力 -
--jsonはそのエイリアス -
--profile agentは AI エージェント用の固定モードで、JSON のみを返す
JSON は固定スキーマで、schema_version, status, summary, findings[] を返します。各 finding には rule_id, policy, score, verdict, file, line, message が入り、生成できる場合はマスク済み snippet も含まれます。
これは「人間向けの CLI に JSON をおまけで足した」というより、AI エージェントが再実行して機械的に解析できることを最初から前提にしたインターフェースです。コミットが止まったあと、エージェントが JSON を読んで修正候補を出すところまでつなげやすくしています。
設計のポイント
単一手法に依存しない多層スコアリング
regex 一致だけに頼ると誤検知が多く、エントロピーだけでは「意味のあるランダム文字列」を取り逃がします。keygate は以下を独立シグナルとして集約し、最終スコアで判定します:
| シグナル | 加点 |
|---|---|
| regex 一致 | +50〜100 |
| 高エントロピー | +20 |
キーワード(HIGH): secret, password, api_key 等 |
+25 |
キーワード(MID): token, credential, auth
|
+15 |
代入構文 NAME = "..."
|
+15 |
very_sensitive パス(.env 等) |
+20 |
sensitive パス(settings/、config/ 等) |
+15 |
| テストファイル | -10 |
example、dummy
|
-20 |
さらに コンボボーナス として、ルール未マッチでも複数シグナルが同時成立する場合は加点します:
-
keyword(HIGH/MID) + entropy→ +15 -
keyword(HIGH) + entropy + 代入構文→ さらに +15
これにより、未知フォーマットでも「変数名が怪しい + ランダム文字列 + 代入構文」で BLOCK に到達します。
逆に、既知ルールにマッチした場合はコンボ加点を重ねず、ルール自体の重みで判定します。スコアを無駄に盛らず、理由を説明しやすくするためです。
内部検証での検知精度
現在のラベル付きコーパス(既知シークレット50件 + 無害な文字列50件 = 100 サンプル)で計測した結果です。
| 指標 | 結果 |
|---|---|
| Recall(再現率: 本物のシークレットを見逃さず検知できた割合) | 100.0% |
| Precision(適合率: 検知したもののうち本当に危険だった割合) | 80.6% |
| F1(再現率と適合率のバランス指標) | 89.3% |
| True Positive(正しく検知できたシークレット) | 50 |
| False Negative(見逃したシークレット) | 0 |
| False Positive(本物のシークレットではないが検知したもの) | 12 |
| True Negative(正しく通過させた無害な文字列) | 38 |
特に重視したのは False Negative(見逃し)を 0 にすることです。シークレット検知では、少し確認が増えることよりも、本物のキーを通してしまうことの方がはるかに危険だからです。
False Positive の 12 件は、マスク済み URL credentials、プレースホルダー、Stripe publishable key、空の API_KEY= などです。これらは本物のシークレットではないケースもありますが、見た目がシークレットに近いため、コミット前に確認できるよう検知対象にしています。
pre-commit フックとしては、見逃しを避けることを優先しつつ、問題ないと判断できるものは inline ignore / allowlist / baseline で管理できるようにしています。
誤検知が出たときの対処
「安全に倒す」方針なので、稀に本物でない文字列が引っかかります。対処は 3 段階:
1. inline ignore(1 行だけ)
api_key = "dummy-key-for-testing" # keygate: ignore reason="テストデータ"
reason 必須。何のために無視したかを強制的に残します。
2. allowlist(プロジェクト全体)
keygate.toml に:
[allowlist]
paths = ["vendor/*", "third_party/*"]
patterns = ["dummy", "example"]
ただし tests/* を丸ごと allowlist に入れると、テストに混入した本物のシークレットを見逃すので非推奨です。
3. baseline(既存検知を凍結)
keygate baseline create
現時点の検知結果を .keygate.baseline.json に fingerprint(SHA256)として保存。以降は同じ場所を検知しても無視します。値そのものは保存しないので、baseline ファイルをコミットしても秘密は漏れません。
{
"version": 1,
"entries": [
{
"fingerprint": "e5282a7860678bc768d280eb3e77d2ca8a44286357c743dd024d74fe0605fe09",
"file_path": "src/app/config.py",
"line_number": 42,
"rule_id": "url-credentials",
"created_at": "2026-04-22T09:30:00+00:00"
}
]
}
すでに baseline を作ったあとで、新しい検知を追加で取り込みたい場合は keygate baseline update を使えます。
チームで共有すれば、新メンバーが pipx install keygate && keygate install-hook するだけで同じ baseline が使われます。
意図的にやらなかったこと
設計段階でいくつかの機能を 非目標 として明示しています:
- フルリポジトリスキャン(pre-commit の責務ではない)
- LLM による判定(オフライン / 高速 / 決定的を優先)
- 外部 API での検証(トークンの有効性確認等)
- IDE プラグイン、SaaS 連携、自動ローテーション
ローカル pre-commit の 200〜500ms 以内で完結することを最優先しました。オフラインでも動かせるように、LLM 判定や外部 API の有効性チェックも入れていません。サーバ側の守りは別ツール(pre-receive hook、CI スキャン)と併用する想定です。
免責事項
本ツールは「秘密情報を正しく管理する」ことの代わりではなく、「人間のうっかりミスを最後に拾う網」です。
- 完全な検知は保証しません(未知フォーマット、難読化された値は取り逃す可能性)
- 誤検知はゼロではありません(allowlist / baseline / inline ignore で対処)
-
git commit --no-verifyで簡単にバイパスされます(組織的統制が必要ならサーバ側と併用) - シークレットは本来、環境変数 / シークレットマネージャー / KMS で管理してリポジトリに入れないのが鉄則
おわりに
人間はいつかミスをします。最後の一歩でそれを止めるのが pre-commit フックの役割です。日々の開発で「うっかり」を減らしたい方はぜひ試してみてください。
pipx install keygate
フィードバック・Issue・PR 歓迎です!