この記事が良かったら
「チップをリクエスト」で著者にチップの受け取り設定をお願いできます

― APIキー流出と誤push先ガード
この記事が良かったら
「チップをリクエスト」で著者にチップの受け取り設定をお願いできます
「Claude Code環境」シリーズです。前回はClaudeとCodexを1台で協業させる話を書きました。今回は、その出口 ―― AIにgitを叩かせるときに事故を機械で止めるフックの話です。
AIエージェントにcommitやpushまで任せると、便利な反面こわいのが2つあります。①うっかりAPIキーや.envを巻き込む と ②間違ったリポジトリにpushする。どちらも「気をつける」では防げないので、PreToolUse フックで機械的にブロックしています。
Claude Codeには「ファイル書き込み時に秘密をスキャンする」フック(Edit/Write用)はよくあります。でもそれだけだと穴が空きます。git commit も git push もBashツール経由だからです。ファイルを書いた瞬間ではなく、ステージしてpushする瞬間にもう一枚ゲートが要る。そこで Bash ツールに対する PreToolUse フックを足しました。
このフックはすべてのBashコールで発火します。だから「git以外のコマンドにコストを一切かけない」ことが最優先です。冒頭で文字列に git が無ければ、Pythonを一度も起動せず即終了します。
# 粗い早期return: payload に 'git' が無ければ python を1度も起動せず即終了。
case "$INPUT" in
*git*) ;;
*) exit 0 ;;
esacフックは「全コールに課税される」前提で書くのが大事です。旧実装はJSONパースに python3 を3回起動していて、毎コール数百msの課税になっていました。パースは1回に統合し、そもそもgitが絡まない大多数のコールは正規表現の早期returnで素通りさせます。
その後、ツールが Bash で、コマンドに git の単語境界があり、commit か push を含むときだけ本処理に入ります。
まず塞ぐのは「フックの無効化」そのものです。--no-verify のようなバイパスフラグを含むgitコマンドはブロックします。
if printf '%s' "$CMD" | grep -qE -- '(--no-verify|--no-gpg-sign|commit\.gpgsign=false)'; then
block "hook bypass フラグが含まれています。明示依頼が無い限り使用禁止。"
fiこれが無いと、エージェントが「フックに弾かれたから --no-verify を付けて再実行」という最悪の回避を学習しかねません。ゲートの前に、ゲートを外す行為自体を禁じておきます。
git commit のときは、ステージ済みdiff(git diff --cached)を走査します。ワーキングツリー全体ではなく「これからコミットされる差分」だけを見るのが肝です。
DIFF=$(git diff --cached 2>/dev/null || true)
# AWS Access Key の形
printf '%s' "$DIFF" | grep -qE 'AKIA[0-9A-Z]{16}' && block "AWS Access Key が含まれています"
# secret= "..." / api_key: "..." のような代入
printf '%s' "$DIFF" | grep -qE \
'(secret|api_key|apikey|access_token|private_key|client_secret)[[:space:]]*[:=][[:space:]]*["'"'"'][A-Za-z0-9/+_=-]{20,}' \
&& block "シークレットらしき値が含まれています"
# .env がステージされている(.env.example はOK)
git diff --cached --name-only | grep -E '(^|/)\.env' | grep -qvE '\.(example|sample|template)$' \
&& block ".env ファイルがステージされています"3つ目の .env 判定が地味に効きます。.env はブロックしつつ .env.example / .env.sample / .env.template は通す。テンプレは公開したいけど実体は絶対に出したくない、という現実に合わせています。
個人的にいちばん効いているのがこれです。push先リポジトリのownerが自分のものかを検証し、許可リストにないownerへのpushをブロックします。
ALLOWED_OWNERS="bokuwalily" # 自分のGitHub owner(スペース区切りで複数可)
# ...
URL=$(git remote get-url "$REMOTE")
OWNER=$(printf '%s' "$URL" | sed -nE 's#.*github\.com[:/]+([^/]+)/.*#\1#p')
# owner が ALLOWED_OWNERS になければ blockなぜこれが要るか。OSSをforkして触っていると、リモートに他人のリポジトリが紛れ込みます。そこへ誤pushすると、自分のコードが第三者リポに飛ぶ事故になる。私はアカウント名を改名した経緯もあって、「自分の現owner以外へのpushは全部止める」を機械で担保したかった。意図的に他ownerへ出すときだけ、許可リストに足します。
設計の原則は2つです。
decision: block をJSONで返して exit 2。この2枚(commit走査+push宛先検証)に、push直前の出力検証まで足したワンコマンド公開フロー(secret→remote→出力検証→push→デプロイ→疎通)を別途用意していますが、土台はこのフックです。
git diff --cached でステージ分だけ見る.env.example まで巻き込みブロック → テンプレ拡張子は除外--no-verify で回避 → バイパスフラグ自体をブロックPreToolUse ゲートが要る.envはブロックしテンプレは通す次回は、毎セッションの会話ログを長期記憶に流し込むパイプラインの話 ―― 会話ログをObsidianの長期記憶に変えるを書きます。
Lily(@bokuwalily)― 個人開発者。Claude Code で自動化基盤を組みながら、iOSアプリやWebサービスを量産しています
◼︎作ったアプリは **ポートフォリオ** にまとめています📱
◼︎新着・開発の裏側は X **@bokuwalily** で発信しています🐦
◼︎OSS: **github.com/bokuwalily**🐙
皆さんの ❤️ やシェアが励みになります!