最近、AI エージェントにコードを書いてもらう機会がかなり増えてきました。
自分で全部手で書くというより、「この修正お願い」「このテスト直して」「この設定足して」みたいに、作業単位で AI に任せることが増えています。めっちゃ便利です。便利なのですが、一方で少し怖さもあります。
人間が手で書いているときは、ファイルを開いて、値を見て、差分を眺めながら「あ、これは commit しちゃダメだな」と気づく余地があります。ところが AI に作業させると、変更速度が速いです。
神 > 光速 > AI のコード生成速度 >>>>>> 人間のレビュー速度
くらいの気持ちになります(誇大広告)。
そこで、AI 時代のガードレールとして pre-commit hook がけっこう有効なのでは?と思いました。人間がレビューする前に、機械的に危ないものを止める仕組みです。
この記事では、実際に pre-commit、gitleaks、detect-secrets を使って、機密情報の commit を防ぐ仕組みを試した内容をまとめます。
Git には hook という仕組みがあります。
ざっくり言うと、git commit や git push の前後に、任意のスクリプトを実行できる仕組みです。
今回使うのは pre-commit hook です。名前の通り、commit が作られる直前に実行されます。
流れはこんな感じです。
git commit
↓
.git/hooks/pre-commit が実行される
↓
設定されたチェックを実行する
↓
成功したら commit 作成
失敗したら commit 中止
機密情報チェックをここに差し込んでおくと、API key や token が含まれたファイルを commit しようとした瞬間に止められます。
今回は以下の2つを使いました。
| ツール | 役割 |
|---|---|
| gitleaks | Git リポジトリやファイルから secret を検出するツール。Slack token など既知サービスの token 形式やカスタムルールに強い |
| detect-secrets | Yelp 製の secret 検出ツール。API_KEY=... のような文脈や高エントロピー文字列、AWS key、private key などを検出する |
片方だけでも効果はありますが、実際に試すと「gitleaks は通るが detect-secrets は止める」「detect-secrets は通るが gitleaks は止める」というケースがありました。
なので、個人的には併用が良さそうです。
.pre-commit-config.yaml はこんな感じです。
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.0
hooks:
- id: gitleaks-docker
args: ["--config", "gitleaks.toml"]
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: ["--baseline", ".secrets.baseline"]
gitleaks.toml は以下のようにしました。
[extend]
useDefault = true
[[rules]]
id = "strict-uuid-check"
description = "Standalone UUID detected (including comments)"
regex = '''(?i)[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}'''
keywords = ["id", "datasource", "client", "key", "token", "api", "auth", "secret"]
[[rules]]
id = "notion-token"
description = "Notion integration token"
regex = '''(?i)\b(?:ntn|secret)_[A-Za-z0-9]{40,}\b'''
keywords = ["ntn_", "secret_", "notion"]
[[rules]]
id = "anthropic-api-key"
description = "Anthropic API key"
regex = '''sk-ant-api03-[A-Za-z0-9_-]{40,}'''
entropy = 0
[[rules]]
id = "linear-api-key"
description = "Linear API key"
regex = '''lin_api_[A-Za-z0-9]{32,}'''
entropy = 0
[[rules]]
id = "postman-api-key"
description = "Postman API key"
regex = '''PMAK-[A-Za-z0-9]{24,}-[A-Za-z0-9]{24,}'''
entropy = 0
[allowlist]
paths = [
"package-lock.json",
"node_modules",
"yarn.lock",
".secrets.baseline"
]
ポイントはここです。
[extend]
useDefault = true
これを書かないと、gitleaks.toml を指定したときに gitleaks のデフォルトルールが自動では足されません。
自分は最初、「設定ファイルを書く = デフォルトルールを拡張する」だと思っていました。ところが、実際には独自ルールだけの設定になってしまい、Slack token 形式のダミーが通ってしまいました。これが思わぬ落とし穴でした。
macOS なら Homebrew で入れられます。
brew install pre-commit
Python 環境で入れるなら以下でも大丈夫です。
pipx install pre-commit
リポジトリ直下に .pre-commit-config.yaml を置きます。
touch .pre-commit-config.yaml
中身は先ほどのように、gitleaks と detect-secrets を設定します。
ここが重要です。
.pre-commit-config.yaml があるだけでは、commit 時のチェックは動きません。
ローカルの Git hook をインストールします。
pre-commit install
これで .git/hooks/pre-commit が作られます。
以後、git commit のたびに pre-commit が実行されます。
まずは既存ファイルに反応しないか確認します。
pre-commit run --all-files
既存ファイルに secret っぽい値がある場合はここで検出されます。
ただし、初回はけっこう false positive が出ることがあります。lock file、サンプル設定、ドキュメント中のダミー値などです。
試しに GitHub token 形式のダミーを置いてみます。
echo 'ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcd123456' > hoge.txt
git add hoge.txt
git commit -m 'test'
すると detect-secrets が止めてくれました。
Detect hardcoded secrets.................................................Passed
Detect secrets...........................................................Failed
- hook id: detect-secrets
- exit code: 1
ERROR: Potential secrets about to be committed to git repo!
Secret Type: GitHub Token
Location: hoge.txt:1
commit は作られません。
AWS Access Key ID のよくあるダミーも試しました。
echo 'AKIAIOSFODNN7EXAMPLE' > hoge.txt
git add hoge.txt
git commit -m 'test'
これも止まります。
Secret Type: AWS Access Key
Location: hoge.txt:1
秘密鍵のヘッダーも検出されます。
-----BEGIN OPENSSH PRIVATE KEY-----
結果はこんな感じです。
Secret Type: Private Key
Location: hoge.txt:1
64桁の hex 文字列だけだと、hash や digest と区別しにくいので通ることがあります。
442d21a0a434f34eb709020a6263c3ef7a495657b9a85d8a91ecd8c245c1c498
一方で、API_KEY= という文脈が付くと止まりました。
echo 'API_KEY=442d21a0a434f34eb709020a6263c3ef7a495657b9a85d8a91ecd8c245c1c498' > hoge.txt
git add hoge.txt
git commit -m 'test'
Detect hardcoded secrets.................................................Failed
- hook id: gitleaks-docker
RuleID: strict-uuid-check
File: hoge.txt
Line: 1
Detect secrets...........................................................Failed
- hook id: detect-secrets
Secret Type: Hex High Entropy String
Location: hoge.txt:1
Secret Type: Secret Keyword
Location: hoge.txt:1
この挙動はかなり妥当だと思います。
裸の hex を全部止めると、commit hash、checksum、content digest、snapshot ID などで誤検知が増えます。一方で API_KEY= のような文脈があれば、secret として扱って止める。実用上のバランスとして悪くないです。
今回一番ハマったのがここです。
最初は gitleaks.toml に独自ルールだけを書いていました。
[[rules]]
id = "strict-uuid-check"
regex = '''...'''
そして .pre-commit-config.yaml ではこう指定していました。
args: ["--config", "gitleaks.toml"]
この状態で Slack bot token 形式のダミーを commit してみると、なんと通ってしまいました。
echo 'xoxb-123456789012-123456789012-abcdefghijklmnopqrstuvwx' > hoge.txt
git add hoge.txt
git commit -m 'test'
gitleaks.toml を外して gitleaks のデフォルトルールで試すと、ちゃんと slack-bot-token として検出されます。
つまり、独自 config を渡しただけでは「デフォルト + 追加」にはならないということです。
解決策はこれ。
[extend]
useDefault = true
これで gitleaks のデフォルトルールを維持しつつ、自分のプロジェクト固有のルールを足せます。
これも大事です。
.pre-commit-config.yaml や gitleaks.toml をリポジトリに置くと、設定は共有できます。
ただし、それだけでは各開発者のローカル hook は有効になりません。
各自が以下を実行する必要があります。
pre-commit install
なので README やセットアップ手順に書いておくのが良いです。
pre-commit hook は便利ですが、万能ではありません。
git commit --no-verify
を使うと hook はスキップできます。
なので、本当に守りたい場合は CI 側でも secret scan を実行するのが安全です。
ローカル hook は「手元で早く気づくためのガードレール」、CI は「最終的に混入を止めるゲート」と考えるのが良さそうです。
AI にコードを書かせると、作業速度は上がります。
でも、速度が上がるということは、危ない差分が混ざる速度も上がるということです。
例えば、AI が以下のようなことをする可能性があります。
人間が全部レビューできれば良いのですが、現実には差分が大きいと見落とします。自分は見落とします。
だからこそ、機械で判定できるものは機械に任せたいです。
pre-commit hook は、AI に作業を任せる前提の開発フローにおいて、かなり相性が良いガードレールだと思います。
個人的には、以下くらいから始めるのが良さそうです。
brew install pre-commit
pre-commit install
pre-commit run --all-files
設定はまずこれ。
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.0
hooks:
- id: gitleaks-docker
args: ["--config", "gitleaks.toml"]
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: ["--baseline", ".secrets.baseline"]
そして gitleaks.toml では必ずデフォルトルールを継承します。
[extend]
useDefault = true
その上で、自分のプロジェクトで使っているサービスの token 形式を追加していきます。
例えば Notion を使っているなら ntn_... や secret_...、Anthropic を使っているなら sk-ant-api03-... のようなルールです。
検証中に git commit -m 'test' が通ってしまうことがあります。
このとき、慌てて以下を使うのは危険です。
git reset --hard HEAD~
これは直前 commit だけでなく、作業ツリー上の未コミット変更も消します。
直前の test commit だけ取り消したいなら、基本はこれで十分です。
git reset --mixed HEAD~1
rm hoge.txt
commit だけ取り消して、変更内容を staged のまま残したいなら --soft です。
git reset --soft HEAD~1
--hard は「本当に作業ツリーごと捨ててよい」と確認できているときだけ使うのが良いです。自分もここで少しヒヤッとしました 😭
pre-commit hook を入れておくと、commit 前に危ない差分を止められます。
今回のポイントは以下です。
AI に作業を任せるのは便利です。
ただし、AI に任せるなら、AI がやらかしても人間が気づける仕組み、あるいは人間が見る前に止める仕組みもセットで用意したいです。
pre-commit hook は、その第一歩としてかなり導入しやすいと思います。
機密情報の誤 commit が怖い方の参考になれば幸いです。
