Forgejoのpre-receive hookで
シークレットスキャナーを実装した話
APIキーやクレジットカード番号がGitにpushされるのを防ぐため、Forgejoのhookにシークレットスキャナーを実装しました。実装の詳細と、誤検知との格闘を紹介します。
目次
背景と目的
近年、GitHubへのpush時にクレジットカード情報やAPIキーが誤って含まれてしまう事案が増えています。自社のForgejoでも同様のリスクがあると考え、pushをブロックするシークレットスキャナーを実装しました。
APIキー漏洩リスク
AWS・GCP・GitHub等のAPIキーがcommitに混入するリスクを排除します。
クレジットカード情報
有効なカード番号をLuhnアルゴリズムで高精度に検知します。
セルフホスト環境
Forgejoのpre-receive hookで実現。外部サービス不要です。
構成環境:
- EC2(Amazon Linux 2023)上にDockerで構築したForgejo v15
- Docker Compose構成
- Mattermost(社内チャット)への通知連携
アーキテクチャ概要
Forgejoのgit hookを使い、pushを受け取ったタイミングでスクリプトを実行します。exit 1を返すとpushを拒否できます。
hookスクリプトはForgejoのWeb UI(Git Hooks設定)から各リポジトリに設定し、Forgejo APIを使って全リポジトリへ一括適用しています。
検知パターン
bashの正規表現(grep -E)で以下のパターンを検知しています。
APIキー・シークレット系
AKIA[0-9A-Z]{16}AIza[0-9A-Za-z_-]{35}ghp_[0-9a-zA-Z]{36}glpat-[0-9a-zA-Z_-]{20}sk-[0-9a-zA-Z]{48}xox[baprs]-[0-9a-zA-Z]{10,48}DB・認証情報系
# DB_PASSWORDの直接記載
DB_PASS(WORD)?[[:space:]]*=[[:space:]]*[^'"\$\{]{8,}
# 認証情報入りURL
https?://[^:@/]+:[^:@/]{8,}@[^/]+
# DATABASE_URL形式
DATABASE_URL=postgres://user:pass@host
password = ${DB_PASSWORD} のような環境変数参照は [^'\"\$\{] の部分で除外しています。ダミー値ではなく実際の値が埋め込まれた場合のみ検知します。
クレジットカード検知(Luhnアルゴリズム)
クレジットカード番号は正規表現だけでは誤検知が多いため、Luhnアルゴリズムで有効なカード番号かどうかを検証しています。
def luhn_check(number):
digits = [int(d) for d in number if d.isdigit()]
total = 0
for i, digit in enumerate(reversed(digits)):
if i % 2 == 1:
digit *= 2
if digit > 9:
digit -= 9
total += digit
return total % 10 == 0
ForgejoコンテナはAlpine Linuxベースのため、Dockerfileにpython3を追加してカスタムビルドしました。
FROM codeberg.org/forgejo/forgejo:15
RUN apk add --no-cache python3
Mattermost通知
シークレット検知でpushがブロックされた際、Mattermostに通知を送信します。検知件数が20件を超える場合は上位20件に省略して送信します(ペイロードが大きすぎると送信失敗するため)。
# 検知件数が多い場合は省略
if (( findings_count > 20 )); then
display_findings=$(echo "${findings}" | head -20)
truncated=1
fi
全リポジトリへの自動適用
Forgejo APIを使って全リポジトリにhookを一括適用するスクリプトを作成しました。また、cronで10分ごとに新規リポジトリを検出して自動適用する仕組みも整えました。
# hookが未設定のリポジトリにのみ適用
if [[ "${current_len}" == "0" ]]; then
curl -s -X PATCH \
-H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"content\": ${CONTENT}}" \
"${FORGEJO_URL}/api/v1/repos/${repo}/hooks/git/pre-receive"
fi
# 10分ごとに新規リポジトリへ自動適用
*/10 * * * * /opt/forgejo-scanner/apply_hook.sh >> /tmp/cron.log 2>&1
トークン管理(AWS Parameter Store)
APIトークンはEC2上のファイルに直接書かず、AWS Parameter Store(SecureString)に保存しています。
SecureString
KMSで暗号化して保存。平文でEC2上に残りません。
最小権限
EC2のIAMロールに ssm:GetParameter のみ付与します。
アクセスログ
CloudTrailで誰がいつ取得したか記録されます。
# スクリプト実行時にParameter Storeから取得
TOKEN=$(aws ssm get-parameter \
--name /forgejo/hook-token \
--with-decryption \
--region ap-northeast-1 \
--query Parameter.Value \
--output text)
誤検知との戦い
実装後に実際のリポジトリでpushしてみると、予想外の誤検知が多数発生しました。
発生した誤検知の例
docs/06-mock/**/*.htmlHTMLモックのメールアドレス入力フォームdocs/03-api/01-endpoint-list.mdAPIドキュメントのJSONサンプル値e2e/helpers/auth.tsE2EテストのダミーパスワードPasswordリテラルguides/e2e-mail-alias-setup.mdガイドドキュメントのサンプルメアド対策
また、テストデータなど意図的にスキャンをスキップしたい場合はコミットメッセージに [skip-secret-scan] を含めることで対応できます。
git commit -m "add test fixture [skip-secret-scan]"
git push origin main
ログの整備
検知内容をサーバーのログに記録するよう強化しました。値はマスク処理(先頭10文字のみ表示)しています。ログファイルは chmod 600 でgitユーザーのみアクセス可能に制限しています。
[2026-05-14 08:35:00] DETECTED: repo=test-backend.git branch=main commit=f3a9c12e file=.env
[2026-05-14 08:35:00] .env:1:AWS_SECRET***(masked)
[2026-05-14 08:35:00] BLOCKED: repo=test-backend.git branch=main findings found
[2026-05-14 08:35:00] Mattermost通知送信: repo=test-backend branch=main commit=f3a9c12e
まとめ
実装した機能の全体像です。
シークレットのcommit・pushを完全に防ぐことはできませんが、気づかず漏洩するリスクを大幅に下げることができました。誤検知との兼ね合いは難しく、今後もチューニングを続けていく予定です。

コメント