TL;DR
- AIエージェント経由のcommitはpre-commitを回避しやすい。MCP層とpush protectionで多層化する。
- GitHub MCP Serverを使うなら、tool呼び出し側にもsecret scanningを仕込む必要がある。
- rotateまで自動化しないと、検知だけが空転する。
はじめに
この記事はシリーズ「AIコーディング導入のセキュリティ設計」の L2 流出検知層 の深掘りです。 👉 AIコーディング導入のセキュリティ設計 4層モデルで攻撃面を整理する
AIコーディング時代の流出経路は変わりました。エージェントがコメントにAPIキーを書く、プロンプト値を生成コードに混入、MCP経由で直接commit/push。pre-commitだけでは追いつきません。
1. GitHub MCP Server の連携設計
pre-tool-use hook でペイロード検査
書き込み系tool(create_or_update_file、push_files など)が呼ばれる直前に、ペイロードをsecret scanningに通す pre-tool-use hook を仕込みます。
hooks:
pre-tool-use:
matchers:
- tool: github.create_or_update_file
- tool: github.push_files
handler: scan-payload-for-secrets.sh
on_match: block
加えて GitHub 組織設定で push protection を必須化。クライアント hook が壊れても最後の砦になります。なお push protection は Public リポは無料、Private リポは GitHub Advanced Security(GHAS)ライセンスが必要です(2026年4月時点)。ライセンス有無で適用範囲が変わるため、組織導入時は先にライセンスとコストを確認してください。関連するMCP権限の分離設計は MCPで"運転席と作業者"を分離して事故率を下げる が詳しい。
2. 多層検知の3点
| 層 | 実装 | 役割 |
|---|---|---|
| エディタ/hook層 | gitleaks detect --staged --redact --exit-code 1 を post-edit で | 一次検知 |
| commit層 | pre-commit フレームワーク経由で同じ gitleaks | 二次検知 |
| リモート層 | GitHub secret scanning + push protection(組織強制、GHAS 前提) | 最終砦 |
3層のうち1つ欠けると迂回路が残ります。AIエージェントは最短経路を取るので、迂回路を作らない設計が必須です。
3. 検知後の運用フロー
検知だけで運用が無いとAlertが積み上がります。ガバナンスの枠組みは AIガバナンス リリース判断のリスクマトリクス を合わせて参照。最低限の3ステップ:
- rotate: 検知した credential を即座に無効化(プロバイダAPIで自動化)
- 通知: 所有者・セキュリティチームへSlack自動投稿
- 履歴削除: git filter-repo / BFG で除去(半自動)
rotate自動化が無いと「検知したがcredentialはまだ生きている」状態が続きます。AWS / GCP / Stripe など主要プロバイダはAPIでrotateできるので、検知 → rotate → 通知の一連をbot化します。
実行時の異常検知や監査ログの集約は、L3(実行時制御層)のhooksに任せるのが整理しやすいです。
👉 Claude Code hooks の実践パターン集 品質担保・監査・安全制御の3用途
4. secret rotate 自動化の実装
検知後のrotateが手動では、アラートが積み上がるだけで機能しません。以下ではGitHub Actionsを使ったrotate自動化の具体的な実装パターンを解説します。
4-1. 全体フロー
GitHub secret scanning アラート発火
↓
GitHub Actions ワークフロー起動(repository_dispatch / webhook)
↓
① Slack / GitHub Issue で所有者・セキュリティチームへ通知
↓
② 旧 credential を無効化(プロバイダ API)
↓
③ 新 credential 生成 → GitHub Secrets / Vault に再登録
↓
④ 監査ログへ記録(append-only)
4-2. アラート受信ワークフロー
GitHub の secret scanning alert イベント を起点にワークフローを起動します。
# .github/workflows/secret-rotate.yml
name: Secret Rotate on Leak Detection
on:
secret_scanning_alert:
types: [created]
permissions:
issues: write
contents: read
jobs:
notify-and-rotate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Notify Slack
uses: slackapi/slack-github-action@v2
with:
payload: |
{
"text": ":rotating_light: *Secret leak detected*\nRepo: ${{ github.repository }}\nSecret type: ${{ github.event.alert.secret_type }}\nAlert URL: ${{ github.event.alert.html_url }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_SECURITY_WEBHOOK }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: Open GitHub Issue
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `[SECURITY] Secret leak: ${{ github.event.alert.secret_type }}`,
body: [
'## Secret Leak Detected',
'',
`- **Type**: \`${{ github.event.alert.secret_type }}\``,
`- **Alert**: ${{ github.event.alert.html_url }}`,
`- **Detected at**: ${new Date().toISOString()}`,
'',
'## Action Required',
'- [ ] Confirm credential has been rotated',
'- [ ] Remove secret from git history (git filter-repo / BFG)',
'- [ ] Audit access logs for unauthorized use',
].join('\n'),
labels: ['security', 'secret-leak'],
});
4-3. Personal Access Token (PAT) の無効化と再登録
GitHub PATが漏洩した場合のrotateスクリプト例です。PAT自体を直接無効化するAPIはないため、GitHubの/installation/tokenや Organization Secrets の更新で対応します。
#!/usr/bin/env bash
# scripts/rotate-github-pat.sh
# Usage: GITHUB_TOKEN=<admin_pat> ./rotate-github-pat.sh <org> <secret_name>
set -euo pipefail
ORG="${1:?Usage: $0 <org> <secret_name>}"
SECRET_NAME="${2:?}"
echo "[1/3] Generating new PAT via GitHub App token exchange..."
# 実プロジェクトでは GitHub App の installation token を使うのが推奨
NEW_TOKEN=$(gh auth token) # ← 実装時はアプリ認証に差し替える
echo "[2/3] Updating Organization Secret: ${SECRET_NAME}"
gh secret set "${SECRET_NAME}" \
--org "${ORG}" \
--body "${NEW_TOKEN}" \
--visibility all
echo "[3/3] Logging rotation event..."
jq -n \
--arg secret "${SECRET_NAME}" \
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{"event":"secret_rotated","secret":$secret,"rotated_at":$ts}' \
>> audit/secret-rotation.log
echo "Done. Notify the team to update any local references to ${SECRET_NAME}."
4-4. 汎用 API Key の rotate ワークフロー
Stripe・OpenAI など複数プロバイダのAPIキーを一元的に扱う場合、プロバイダごとのrotate関数をラップした共通ワークフローを用意します。
# .github/workflows/rotate-api-key.yml
name: Rotate API Key
on:
workflow_dispatch:
inputs:
provider:
description: "Provider (stripe / openai / sendgrid)"
required: true
type: choice
options: [stripe, openai, sendgrid]
secret_name:
description: "GitHub Secret name to update"
required: true
jobs:
rotate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Rotate Stripe key
if: inputs.provider == 'stripe'
run: |
# Stripe: 旧キー失効 → 新キー生成
OLD_KEY="${{ secrets.STRIPE_SECRET_KEY }}"
NEW_KEY=$(curl -s -X POST https://api.stripe.com/v1/restricted_keys \
-u "${OLD_KEY}:" \
-d "name=rotated-$(date +%Y%m%d)" \
| jq -r '.secret')
gh secret set "${{ inputs.secret_name }}" --body "${NEW_KEY}"
# 旧キー失効(Stripe Dashboard または API)
echo "Rotated Stripe key. Revoke old key manually in Dashboard."
- name: Rotate OpenAI key
if: inputs.provider == 'openai'
run: |
# OpenAI: Admin API でキー削除 → 再生成
curl -s -X DELETE "https://api.openai.com/v1/organization/api_keys/${{ secrets.OPENAI_KEY_ID }}" \
-H "Authorization: Bearer ${{ secrets.OPENAI_ADMIN_KEY }}"
NEW_KEY=$(curl -s -X POST "https://api.openai.com/v1/organization/api_keys" \
-H "Authorization: Bearer ${{ secrets.OPENAI_ADMIN_KEY }}" \
-H "Content-Type: application/json" \
-d '{"name":"rotated-auto"}' \
| jq -r '.value')
gh secret set "${{ inputs.secret_name }}" --body "${NEW_KEY}"
4-5. rotate 後の git 履歴削除
rotateだけでは不十分です。履歴に残ったsecretを削除しないと、過去のコミットから取得できてしまいます。
# BFG Repo Cleaner を使う場合(推奨:高速)
java -jar bfg.jar --replace-text secrets-to-remove.txt .git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force-with-lease
# git filter-repo を使う場合(Python製、よりきめ細かい制御が可能)
pip install git-filter-repo
git filter-repo --replace-text <(echo "OLD_SECRET_VALUE==>REMOVED")
注意: 履歴書き換えは破壊的操作です。チーム全員がリベースまたはfresh cloneが必要になります。必ずIssueで周知してから実行してください。
まとめ
pre-commitだけではAIエージェントの流出は止まりません。MCP tool前検査 + push protection + rotate自動化。3層+運用が揃って初めてL2が機能します。
| フェーズ | 実装 | 優先度 |
|---|---|---|
| 検知 | gitleaks (エディタ/commit/push) + MCP hook | 最高 |
| 通知 | Slack webhook + GitHub Issue 自動起票 | 高 |
| rotate | プロバイダ API + GitHub Secrets 更新 | 高 |
| 履歴削除 | BFG / git filter-repo | 中(rotate後すみやかに) |
まずrotate自動化から始め、「検知したが credential がまだ生きている」状態をゼロにすることを目標にしてください。
全体像に戻る: 親記事はこちら
