Forgejoのpre-receive hookでシークレットスキャナーを実装した話

Security Forgejo Git AWS

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を拒否できます。

git push
pre-receive hook
スキャン実行
検知 → exit 1(ブロック)
git push
pre-receive hook
スキャン実行
問題なし → exit 0(通過)

hookスクリプトはForgejoのWeb UI(Git Hooks設定)から各リポジトリに設定し、Forgejo APIを使って全リポジトリへ一括適用しています。

検知パターン

bashの正規表現(grep -E)で以下のパターンを検知しています。

APIキー・シークレット系

サービスパターン
AWS アクセスキーAKIA[0-9A-Z]{16}
GCP APIキーAIza[0-9A-Za-z_-]{35}
GitHub PATghp_[0-9a-zA-Z]{36}
GitLab PATglpat-[0-9a-zA-Z_-]{20}
OpenAI APIキーsk-[0-9a-zA-Z]{48}
Slack Tokenxox[baprs]-[0-9a-zA-Z]{10,48}
他にも Stripe / SendGrid / Twilio / HuggingFace / GCP サービスアカウント など計26パターンを検知

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アルゴリズムで有効なカード番号かどうかを検証しています。

Luhnアルゴリズムとは、クレカ会社が番号発行時に必ず使う計算規則です。ランダムな16桁の数字がLuhnを通過する確率は約10%のため、誤検知を大幅に削減できます。
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件に省略して送信します(ペイロードが大きすぎると送信失敗するため)。

Mattermostへの通知スクリーンショット
Mattermostに届く通知の例
# 検知件数が多い場合は省略
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してみると、予想外の誤検知が多数発生しました。

あるブランチのpush時に120件以上の誤検知が発生。検知内容が多すぎてMattermostへの通知も失敗しました。

発生した誤検知の例

ファイル誤検知の原因
docs/06-mock/**/*.htmlHTMLモックのメールアドレス入力フォーム
docs/03-api/01-endpoint-list.mdAPIドキュメントのJSONサンプル値
e2e/helpers/auth.tsE2EテストのダミーパスワードPasswordリテラル
guides/e2e-mail-alias-setup.mdガイドドキュメントのサンプルメアド

対策

パターン対応理由
パスワード削除テストコードでの誤検知が多い
メールアドレス無効化HTMLやドキュメントに正当な理由で存在する
電話番号無効化数字列との区別が困難
クレジットカードLuhnに変更正規表現のみより誤検知を大幅に削減

また、テストデータなど意図的にスキャンをスキップしたい場合はコミットメッセージに [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

まとめ

実装した機能の全体像です。

pushブロックpre-receive hook
シークレット検知bash正規表現 / 26パターン
クレカ番号検知Luhnアルゴリズム(Python3)
Mattermost通知件数超過時は省略形式
トークン管理AWS Parameter Store
全リポジトリ自動適用Forgejo API + cron

シークレットのcommit・pushを完全に防ぐことはできませんが、気づかず漏洩するリスクを大幅に下げることができました。誤検知との兼ね合いは難しく、今後もチューニングを続けていく予定です。

今後の展望:メールアドレス・電話番号の検知については、ファイル種別によるフィルタリングや presidio などの専用ライブラリの導入を検討しています。またトークンの自動ローテーションにも取り組む予定です。

Recruit

ディーメイクでは各ポジションで一緒に働く仲間を募集中! エンジニア、デザイナー、ディレクターなど、多彩な職種があります。
一緒に成長していきましょう!

  • URLをコピーしました!

コメント

コメントする