2026年5月31日
2026年5月31日
Secretの漏洩防止スキャンをCIに組み込む方法:TruffleHogとGitLab CIで差分だけを検査する
秘密情報のスキャンを CI に入れるなら、普段のマージリクエストでは「今回追加されたコミットだけ」を検査し、全履歴の検査は定期実行に分けると、速さと見落としの少なさを両立できます。この記事では、TruffleHog を GitLab CI に組み込む例を中心に、差分スキャンの書き方、検出時の終了コードの扱い、誤検出の除外、ほかの CI への応用、見つかったときの対応までを説明します。GitHub Actions と Gitleaks の組み合わせは別記事で扱っているため、ここでは別のツールと CI を使う場合の考え方に絞ります。
差分スキャンと全履歴スキャンを分ける理由
WordPress のサイトやプラグインを Git で管理していると、データベースの接続情報、SMTP や決済サービスの API キー、.env、データベースのダンプ(.sql)などが誤ってコミットされることがあります。これを CI で止める方法は大きく2通りです。
- 差分スキャン: マージリクエストや push で追加されたコミットだけを検査します。数秒から数十秒で終わることが多く、毎回の必須チェックに向いています。
- 全履歴スキャン: リポジトリのすべてのコミットを検査します。時間はかかりますが、スキャナーのルール更新で過去のコミットから新しく見つかるものを拾えます。週1回などの定期実行に向いています。
毎回全履歴を検査すると、履歴の長いリポジトリでは CI が遅くなり、開発者がチェックを飛ばしたくなる原因になります。逆に差分だけでは、ルールが増えたときの過去分を拾えません。両方を用意しておくのが現実的です。
TruffleHog の特徴
TruffleHog は、Git の履歴やファイル、コンテナイメージなどから秘密情報を探すオープンソースのツールです。大きな特徴は、見つけた文字列が実際に使えるキーかどうかを、提供元の API に問い合わせて確かめる「検証」の機能です。結果は「検証済み(有効なキー)」「検証できなかった」「無効と判定された」に分かれます。
検証には利点と注意点があります。
- 利点: 有効なキーに絞れば誤検出が減り、対応の優先度を付けやすくなります。
- 注意点: 見つけた値を外部の API に送ることになります。社内の規程で外部通信が問題になる場合は、検証を無効にするオプション(
--no-verification)を使い、検出結果は人が確認する運用にします。
また、「検証済みだけで CI を失敗させる」設定にすると、検証の仕組みがない独自形式のキーや、社内システムのパスワードは通過してしまいます。結果の絞り込み方は、後の節で説明するように用途で使い分けます。
オプション名や結果の分類の表記は版によって変わることがあります。以下のコマンドは TruffleHog v3 系を前提にしているので、使う版の trufflehog --help と README で確認してください(2026年10月現在)。
手元で試す
まずはリポジトリの全履歴を一度スキャンし、現状を把握します。CI に入れる前にやっておくと、導入した日に大量の検出で CI が止まる事態を避けられます。
# 全履歴をスキャン(結果は JSON で保存して確認する)
trufflehog git file://. --json > trufflehog-full.json
# 作業ディレクトリのファイルだけをスキャン(Git 管理外の .env なども含む)
trufflehog filesystem . --json > trufflehog-fs.json
ここで見つかったものは、後述の「検出されたときの対応」に沿って処理します。結果のファイルには秘密情報そのものが含まれるため、確認が終わったら削除し、Git に入れないでください。
GitLab CI に組み込む
マージリクエストでの差分スキャン
GitLab CI では、マージリクエストのパイプラインで CI_MERGE_REQUEST_DIFF_BASE_SHA(比較元の起点となるコミット)が使えます。これを --since-commit に渡すと、そこから先のコミットだけが検査対象になります。
# .gitlab-ci.yml
stages:
- test
secret-scan-mr:
stage: test
image:
name: trufflesecurity/trufflehog:latest # 本番運用では版を固定する
entrypoint: [""]
variables:
GIT_DEPTH: 0 # 浅いクローンにしない(比較元のコミットが必要)
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
script:
- git config --global --add safe.directory "$CI_PROJECT_DIR"
- >
trufflehog git "file://$CI_PROJECT_DIR"
--since-commit "$CI_MERGE_REQUEST_DIFF_BASE_SHA"
--branch "$CI_COMMIT_SHA"
--results=verified,unknown
--fail --no-update
設定のポイントは次のとおりです。
- **
entrypoint: [""]**: TruffleHog の公式イメージは起動時に trufflehog 自体が実行される作りのため、GitLab のscriptを動かせるようにエントリーポイントを空にします。 - **
GIT_DEPTH: 0**: GitLab Runner は既定で浅いクローン(直近のコミットだけ)を取得することがあります。比較元のコミットが手元にないと差分を計算できないため、履歴を取得します。履歴が非常に長い場合は、十分な深さの数値を指定する方法もあります。 - **
safe.directory**: コンテナ内でリポジトリの所有者とユーザーが異なると、Git が操作を拒否することがあります。その対策です。 - **
--fail**: 検出があったときに終了コード 0 以外(README では 183)で終わり、ジョブが失敗します。これがないと、検出があってもジョブは成功扱いになります。 - **
--results=verified,unknown**: 検証済みと「検証できなかった」ものを報告し、無効と判定されたものは除きます。古い版では--only-verifiedという別の指定方法が使われているので、版に合わせてください。 - **
--no-update**: CI の実行中にツールが自分を更新しないようにし、結果を再現しやすくします。
最後に、プロジェクトの設定で「パイプラインが成功しないとマージできない」ようにしておくと、検出のあるマージリクエストを止められます。この設定項目の名前や場所は GitLab の版で異なるため、ドキュメントで確認してください。
全履歴の定期スキャン
GitLab の「パイプラインスケジュール」で週1回などの定期実行を作り、スケジュール実行のときだけ全履歴を検査するジョブを動かします。
secret-scan-full:
stage: test
image:
name: trufflesecurity/trufflehog:latest
entrypoint: [""]
variables:
GIT_DEPTH: 0
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
script:
- git config --global --add safe.directory "$CI_PROJECT_DIR"
- trufflehog git "file://$CI_PROJECT_DIR" --fail --no-update
定期スキャンでは結果の絞り込みをせず、検証できないものも含めて報告させています。毎回の差分スキャンより広く拾い、人が内容を確認する位置づけです。失敗したときに担当者へ通知が届くよう、GitLab の通知設定やチャット連携も確認しておきます。
GitLab 標準のシークレット検出との関係
GitLab には、テンプレートを読み込むだけで使えるシークレット検出の機能もあります(.gitlab-ci.yml に Jobs/Secret-Detection.gitlab-ci.yml を include する方式)。結果の表示画面や、push の時点で止める機能など、使える範囲は GitLab のプランや版によって異なります(2026年10月現在)。標準機能で足りる場合はそちらを使い、検証機能が欲しい場合や、ほかの CI と同じ設定にそろえたい場合に TruffleHog を併用する、という選び方ができます。
ほかの CI で使う場合
Jenkins や CircleCI などでも、考え方は同じです。「比較元のコミット」と「検査対象の先端」を CI の変数から取り出し、TruffleHog に渡します。
#!/usr/bin/env bash
# secret-scan.sh BASE_SHA と HEAD_SHA は CI の変数から渡す
set -euo pipefail
: "${BASE_SHA:?BASE_SHA is required}"
: "${HEAD_SHA:?HEAD_SHA is required}"
trufflehog git "file://$(pwd)" \
--since-commit "$BASE_SHA" \
--branch "$HEAD_SHA" \
--results=verified,unknown \
--fail --no-update
つまずきやすいのは、CI のチェックアウトが浅いクローンになっていて BASE_SHA が存在しないケースです。その場合はエラーになるか、意図した範囲を検査できません。チェックアウトの深さの設定を確認してください。新しいブランチの最初の push のように比較元がない場合は、既定ブランチとの分岐点(git merge-base)を比較元にする方法があります。
誤検出の除外
テスト用のダミーキーやサンプルファイルが検出された場合の扱いです。
- 行単位で除外する: 該当行に
trufflehog:ignoreというコメントを付けると、その行の検出は報告されません。ダミー値であることが明らかな行に限って使います。 - パスで除外する: 除外したいパスの正規表現を1行ずつ書いたファイルを用意し、
--exclude-pathsで指定します。
# .trufflehog-exclude(1行に1つの正規表現)
^tests/fixtures/
wp-config-sample\.php$
除外は網の目を粗くするので、理由をコメントに残し、追加するときはレビューで確認する運用にします。vendor/ や node_modules/ のような外部コードを除外したくなることもありますが、そこに秘密情報を置いてしまう事故もあるため、まずは除外せずに様子を見るのが安全です。
検出されたときの対応
本物の秘密情報が検出されたら、公開されたものとして扱います。履歴から消しても、クローンや CI のキャッシュ、フォークに残っている可能性があるためです。
- 無効化と再発行を先に行う: API キーは提供元で失効させて新しいキーを発行し、データベースのパスワードは変更します。WordPress の認証用キーとソルトを変更すると、ログイン中のユーザーはログアウトされます。
- 新しい値をリポジトリの外に置く: サーバー上の wp-config.php や、Web からアクセスできない場所の
.env、CI の保護された変数に置き、コードからはgetenv()や定数で参照します。 - CI のログと成果物を確認する: 検出結果の出力やジョブの成果物に秘密情報が残っていないか確認し、必要なら削除します。
- 履歴の書き換えは影響を見て判断する: git filter-repo などで履歴から消すと、全員がクローンし直す必要があります。無効化が済んでいれば、急ぐ必要はありません。
- 不正利用の有無を確認する: 漏れていた期間に不審なアクセスがなかったか、提供元の利用履歴やサーバーのログを確認します。
まとめ
- マージリクエストでは比較元から先の差分だけを検査し、全履歴の検査は定期実行に分けます。
- TruffleHog はキーの有効性を検証できますが、外部への問い合わせが発生します。検証済みだけに絞ると独自形式のキーを見逃す点にも注意します。
- GitLab CI では
CI_MERGE_REQUEST_DIFF_BASE_SHAを--since-commitに渡し、GIT_DEPTH: 0で履歴を取得し、--failでジョブを失敗させます。 - 誤検出は
trufflehog:ignoreや--exclude-pathsで個別に扱い、除外の理由を残します。 - 本物が見つかったら、削除より先にキーの無効化と再発行を行います。