シークレットスキャナの定番だった Gitleaks の原作者が、同じ領域のツールをゼロから作り直した。それが Betterleaks だ。しかも gitleaks 公式リポジトリの README 自身が「新機能はもうマージしない。今後はセキュリティパッチのみ」と宣言し、開発の重心が明確に移っている。この記事では、実際に v1.7.1 をインストールして合成リポジトリをスキャンした結果をもとに、何が変わったのか・既存の gitleaks 運用をどう持ち込むのかを一次ソースで確認していく。
betterleaks 1.7.1 を実機実行した出力。検証用の合成リポジトリで、キーはすべてローカル生成の架空値。components: 行に注目——アクセスキーIDとシークレットのペアを1件として報告している- 誰が作ったか:Gitleaks 原作者 Zachary Rice(
zricethezav)。Betterleaks リポジトリでも最多コミット者。MIT ライセンス、Go 実装、GitHub ★1,545(2026-07-28 時点) - なぜ今か:gitleaks 側の README が「機能完成・新機能はマージしない・セキュリティパッチのみ」と明言。最新リリースは gitleaks が 2026-03-21、Betterleaks は 2026-07-27
- 移行コスト:ドロップイン置き換えを掲げ、
GITLEAKS_CONFIGや.gitleaks.tomlも設定探索の対象に含む - 注意すべき誤解:よく引用される「98.6% vs 70.4%」はツール同士の比較ではなく、フィルタ手法(Token Efficiency vs エントロピー)の recall 比較
- 2026-06-26 の破壊的変化:v1.6.0 で式エンジンが CEL → Expr に交代。日本語の既報記事の多くは CEL 前提のまま
・Gitleaks は公式に新機能を凍結し、原作者は後継の Betterleaks に注力している(gitleaks README に明記)
・広く引用される「98.6% vs 70.4%」はツール比較ではなくフィルタ手法の recall 比較。precision は 57.3% / 21.1%
・v1.6.0(2026-06-26)で式エンジンが CEL → Expr に交代。既存の日本語解説の多くは CEL 前提のまま
タイムライン——Gitleaks 凍結から Betterleaks v1.7.1 まで
| 日付 | 出来事 |
|---|---|
| 2026-02-03 | betterleaks/betterleaks リポジトリ作成 |
| 2026-02-20 | Rice が Token Efficiency のベンチマーク記事を公開(98.6% / 70.4% の原典)。同日に最初の公開リリース v1.0.1 |
| 2026-03 | 海外メディアが相次いで報道(Help Net Security・BleepingComputer 等) |
| 2026-03-21 | Gitleaks 最新リリース v8.30.1(以降、新規リリースなし) |
| 2026-06-12 | Betterleaks v1.5.0 |
| 2026-06-26 | v1.6.0:式エンジンが CEL → Expr へ。バイナリ 30MB→22.7MB、起動 160ms→20ms |
| 2026-06-30 | v1.6.1 |
| 2026-07-22 | Gitleaks の直近コミット(Dependabot による依存更新) |
| 2026-07-23 | Betterleaks v1.7.0 |
| 2026-07-27 | Betterleaks v1.7.1(検証回数の上限追加など) |
日付は GitHub API のリリース・コミット情報および各リリースノートの記載に基づく(2026-07-28 取得)。
シークレット漏洩は単体の事故ではなく、侵入経路の一部として連鎖する。攻撃の全体像と防御の位置づけは サプライチェーンセキュリティ2026|攻撃手法・防御ツール・実践チェックリスト に整理してある。本記事はそのうち「リポジトリに鍵を入れない/入ったら気づく」層を担うツールの話だ。
Betterleaksとは——Gitleaks原作者がシークレットスキャナを作り直した理由
Betterleaks は 2026-02-03 に作られた GitHub Organization 配下のプロジェクトで、Go 製・MIT ライセンス。README の説明は「設定可能で、速く、徹底的なシークレットスキャナ」であり、続けてこう書かれている——Gitleaks を作った面々(原作者を含む)がメンテナンスしている、と。
コミット統計はこの説明と一致する。コントリビュータ23人のうち最多は zricethezav の223コミットで、これは Gitleaks の原作者 Zachary Rice のハンドルだ。開発は Aikido Security がスポンサードしており、Rice 自身は同社で Head of Secrets Scanning を務めている。
なぜ既存の Gitleaks を育てなかったのか
ここは推測が混ざりやすい部分なので、公開されている記述だけを引く。Aikido の Betterleaks 紹介記事には、新プロジェクトを始めた理由としてこう書かれている。
背景の詳細は公開されていないため、これ以上の事情は本記事では断定しない。ただし開発の重心がどこにあるかは、リポジトリの実データで測れる。
gitleaks の README 冒頭には、原作者本人による次の宣言が置かれている。
・Gitleaks は機能的に完成している(feature complete)
・新機能はマージしない
・今後のリリースはセキュリティパッチのみ
・自分は Betterleaks に注力する
実際、gitleaks リポジトリの直近コミットは Dependabot による GitHub Actions の依存更新が中心で、最新リリースは 2026-03-21 の v8.30.1 のまま止まっている。一方 Betterleaks は v1.5.0(06-12)→ v1.6.0(06-26)→ v1.6.1(06-30)→ v1.7.0(07-23)→ v1.7.1(07-27)と、おおむね週〜隔週でリリースが続いている。
「gitleaks が動かなくなる」わけではない点は押さえておきたい。動作は続くし、セキュリティパッチも出る。変わるのは「新しい検出ルールや新機能はもう入らない」という一点だ。これは、鍵の種類が増え続ける領域では時間とともに効いてくる差になる。
Betterleaks のインストールと最初のスキャン——自分のリポジトリを今すぐ確認する
まずは手元のリポジトリに何が埋まっているかを見るのが早い。導入は各種パッケージマネージャに対応している。
# macOS / Linux (Homebrew)
brew install betterleaks
# Fedora
sudo dnf install betterleaks
# コンテナ
docker pull ghcr.io/betterleaks/betterleaks:latest
# Go
go install github.com/betterleaks/betterleaks@latest
# バージョン確認(本記事の検証は 1.7.1)
betterleaks version
インストールできたら、自分のリポジトリを対象に履歴ごとスキャンする。過去のコミットに残った鍵は、現在のファイルを消しても履歴に残り続けるため、git サブコマンドで履歴を見るのが要点だ。
# リポジトリ履歴を丸ごとスキャン(-v で該当行とルールIDを表示)
betterleaks git /path/to/repo -v
# 履歴が長いリポジトリは並列数を上げる
betterleaks git /path/to/repo -v --git-workers=16
# 作業ツリーだけ/ステージ済み差分だけを見る(pre-commit 相当)
betterleaks git /path/to/repo --staged --redact
# ディレクトリをファイルとしてスキャン(.git を持たない配布物やログにも使える)
betterleaks dir /path/to/dir -v
# JSON / SARIF で出力(CI やコードスキャン基盤に渡す)
betterleaks git /path/to/repo --report-path report.json
出力は冒頭の図のとおりで、ルールID・該当行・該当箇所のキャレット表示に加えて、attributes としてコミット作成者・コミットメッセージ・ファイルパス・SHA が並ぶ。この attributes が後述するフィルタ式の入力になっており、「誰がいつ入れたか」で除外条件を書けるようになっている。
既存リポジトリに入れるときの現実的な手順
既存プロジェクトにいきなり CI の必須ゲートとして入れると、過去分の検出で全ビルドが落ちて運用が止まる。順序としては次が扱いやすい。
・まず betterleaks git . -v でフルスキャンし、現状の件数と内訳を把握する
・本物の鍵が見つかったら、スキャナの設定より先に鍵のローテーションを行う(履歴から消しても、漏れた鍵は漏れたまま)
・誤検知や「対応済みだが履歴に残る」分は --baseline-path でベースライン化する
・そのうえで CI では新規の混入だけを落とす
過去のインシデントで実際に何が起きたかは CAMPFIREのGitHub侵害から学ぶ:エンジニアが今すぐ直すべきSecret管理と最小権限設計 と マネーフォワードGitHub不正アクセス事件|銀行連携停止と侵入経路推測・対策まとめ にまとめてある。どちらも「スキャナを入れる前にやるべきこと」の判断材料になる。
コミット前に止める——pre-commit フックの公式定義
リポジトリには .pre-commit-hooks.yaml が同梱されており、pre-commit フレームワークからそのまま使える。定義されているフックは3種類で、実行方法が違うだけで中身のコマンドは同一だ。
・betterleaks — Go ツールチェーンでビルドして実行
・betterleaks-docker — ghcr.io のコンテナイメージで実行(ローカルに Go を入れたくない場合)
・betterleaks-system — すでに PATH にある betterleaks を実行(Homebrew などで導入済みの場合はこれが速い)
# .pre-commit-config.yaml
repos:
- repo: https://github.com/betterleaks/betterleaks
rev: v1.7.1
hooks:
- id: betterleaks-system
いずれのフックも実体は betterleaks git --pre-commit --redact --staged --verbose で、ステージ済みの差分だけを対象にし、--redact で検出値をマスクして出力する。ログや CI 画面に生の鍵を残さないためのフラグなので、外していい理由は基本的に無い。
ここでも gitleaks からの互換性が効いている。同梱の scripts/pre-commit.py は、フックの有効・無効を判定する際に hooks.betterleaks を見たあと、後方互換として hooks.gitleaks も参照する。gitleaks 時代に一時的にフックを無効化する運用(git config --bool hooks.gitleaks false)をしていたチームは、その設定がそのまま引き継がれる。
CI 側では、履歴を含めてスキャンするため fetch-depth: 0 を指定する点に注意したい。既定の浅いクローンだと過去のコミットが取得されず、履歴に埋まった鍵を見逃す。
# .github/workflows/secrets.yml
name: secret-scan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 履歴を全部取る(浅いクローンだと過去分を見逃す)
- name: Install Betterleaks
run: |
brew install betterleaks
- name: Scan
run: |
betterleaks git . --redact \
--report-format sarif --report-path betterleaks.sarif
SARIF で出力しておけば、GitHub の Code scanning へそのまま取り込める。--report-format が受け付ける値は json / csv / junit / sarif / template の5種類だ(betterleaks --help で確認できる)。
コンテナで動かしたい場合は ghcr.io/betterleaks/betterleaks:latest イメージも公開されている。ただしホスト側のリポジトリをマウントして実行すると、コンテナ内のユーザーIDの違いから git が所有者を警告して停止することがある(detected dubious ownership)。CI に組み込む前に、手元で一度通しておくのが安全だ。
「98.6% vs 70.4%」の正体——それはツール比較の数字ではない
Betterleaks を紹介する記事で最も頻繁に引用されるのが「98.6% 対 70.4%」という数字だ。この2つはしばしば「Betterleaks 対 Gitleaks の検出精度」という形で並べられる。だが原典を読むと、比較しているものが違う。
原典は Zachary Rice 本人が 2026-02-20 に Substack「Looking at Computer」で公開したベンチマーク記事だ。そこで比較されているのは、ツール同士ではなく、正規表現でマッチした候補を絞り込む2つのフィルタ手法である。
原典の数字を整理するとこうなる。
| 比較対象 | precision(適合率) | recall(再現率) | F1 |
|---|---|---|---|
| Token Efficiency(BPEトークナイザ方式) | 57.3% | 98.6% | 0.725 |
| Shannon entropy(従来のエントロピー方式) | 21.1% | 70.4% | 0.325 |
| Token Efficiency + 単語フィルタ | 80.4% | 95.8% | 0.874 |
| Shannon entropy + 単語フィルタ | (記載なし) | (記載なし) | 0.715 |
つまり引用されている「98.6%」と「70.4%」は、どちらも recall 単独の値であり、比較しているのは Betterleaks と Gitleaks ではなく Token Efficiency とシャノンエントロピーという手法だ。しかも Token Efficiency の precision は素の状態では 57.3% で、「検出したもののうち4割強は誤検知」という水準になる。単語フィルタを重ねて初めて precision 80.4% に上がる。
なお Rice は同記事で、この手法を Betterleaks に統合した状態での総合 F1 として 0.8922 という値も示している。ツールとしての実力を語りたいならこちらの数字のほうが近い。
recall だけを取り出すとどんな検出器も良く見える(すべてを「シークレットだ」と判定すれば recall は100%になる)。precision とセットで読むこと、そして「手法の比較」か「ツールの比較」かを取り違えないこと。使用データセットは CredData で、実リポジトリ由来のラベル付きデータを用いたと説明されているが、再現用のハーネスは Betterleaks リポジトリには同梱されていない(
scripts/ にあるのは compare_reports.py などの補助スクリプトで、ベンチマーク実行環境ではない)。第三者が同じ条件で再現するのは現状むずかしい、というのが正確な理解だ。
Token Efficiency の仕組み——BPEトークナイザで「人間の言葉らしさ」を測る
では Token Efficiency とは何か。考え方はシンプルで、文字列を LLM 用のトークナイザにかけ、1トークンあたり何文字になるかを見るというものだ。
(正規表現でマッチ済み)"] --> B["BPEトークナイザ
cl100k_base"] B --> C["token_efficiency =
文字数 ÷ トークン数"] C --> D{"効率が高い?"} D -->|"高い=ありふれた語
例: Hello World → 3.7"| E["自然言語とみなし
除外"] D -->|"低い=語彙に無い並び
例: ランダムキー → 1.1"| F["シークレット候補として
報告"]
BPE(Byte-Pair Encoding)トークナイザは、学習データに頻出する並びを1トークンにまとめる。したがって——
・ありふれた英単語や自然文は少ないトークンに圧縮される=1トークンあたりの文字数が多い(効率が高い)
・ランダム生成されたAPIキーは語彙に無い並びなので細かく分割される=1トークンあたりの文字数が少ない(効率が低い)
原典の例では、Hello World は3トークンで効率3.7、対して ghp_ で始まるランダム風のキーは22トークンに分割され効率1.1になる。この差を閾値にすれば、「正規表現には引っかかったが、実際はただの英文・変数名・ドキュメントの文章」という誤検知を落とせる。
従来の定番はシャノンエントロピーだったが、エントロピーは「文字の出現分布のばらつき」しか見ないため、configuration_management_service のような長くて文字種の多い普通の英語も高エントロピーになりやすく、誤検知を生んでいた。「ランダムかどうか」ではなく「人間の語彙に無い並びかどうか」を測るのが Token Efficiency の勘所だ、というのが原典のタイトル “Rare Not Random”(ランダムではなく、レア)の意味である。
実装面では、v1.6.0 のリリースノートに cl100k_base トークナイザの遅延ロードが明記されている。トークナイザの読み込みは起動コストが大きいため、必要になるまで初期化しない設計に変わった。
composite rule——「IDだけ」では報告しない仕組みを実機で確認する
Betterleaks の既定設定は 412 ルールを持つが、単に正規表現を並べているわけではない。複数のルールの共起を検出条件にする仕組み(composite rule)があり、既定設定では30ルールがこれを使っている。
AWS のアクセスキーを例に、既定設定の該当部分を見てみる。
[[rules]]
id = "aws-access-token"
regex = '''\b((?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z2-7]{16})\b'''
keywords = ["a3t", "akia", "asia", "abia", "acca"]
filter = '''
entropy(finding["secret"]) <= 3.0
|| matchesAny(finding["secret"], [`.+EXAMPLE$`])
'''
[[rules.required]]
id = "aws-secret-access-key"
withinLines = 5
ポイントは3つある。
① 正規表現の文字クラスが base32 である。 サフィックスは [A-Z2-7]{16} で、数字の 0・1・8・9 が入らない。実際の AWS アクセスキーIDが base32 でエンコードされているためだ。検証中、/dev/urandom から A-Z0-9 で生成したダミーキーが一切検出されず調べたところ、生成値に 9 や 1 が混ざってルールにマッチしていなかった。手でダミーキーを書いて「検出されない」と判断する前に、まず正規表現に合致しているかを確認したほうがいい。
② filter が誤検知を落とす。 エントロピー3.0以下、または EXAMPLE で終わる文字列は除外される。AWS 公式ドキュメントに載っている AKIAIOSFODNN7EXAMPLE のような例示キーが弾かれるのはこの行の働きだ。
③ [[rules.required]] が共起を要求する。 アクセスキーIDを検出しても、シークレットアクセスキー側が一緒に見つからなければ報告しない。なお相方の aws-secret-access-key ルールには skipReport = true が付いており、単独では決して報告されない補助ルールとして定義されている。
この③を実際に確かめた。すべてローカル生成の架空値である。
# ① IDとシークレットを隣接行に置いたファイル
printf 'aws_access_key_id = "%s"\naws_secret_access_key = "%s"\n' "$AK" "$SK" > pair.txt
# ② アクセスキーIDだけを置いたファイル
printf 'aws_access_key_id = "%s"\n' "$AK" > lone.txt
betterleaks dir pair.txt # → leaks found: 1
betterleaks dir lone.txt # → no leaks found
意図どおり、IDだけでは報告されない。「アクセスキーIDらしき文字列」は設定ファイルやドキュメントに単独で登場しがちなので、共起を条件にすることで実害のある組み合わせだけを拾う設計になっている。冒頭の図で components: 行に aws-secret-access-key:2 と表示されていたのは、この複合ルールがどの行の何とペアになったかを示したものだ。
withinLines = 5 が実際に効いているかを確かめようと、IDとシークレットの行間隔を変えながらスキャンしたところ、どれだけ離しても「leaks found: 1」のままだった。一見すると距離指定が無視されているように見える。しかし
--report-format json でルールIDまで出すと、まったく違う絵が見えた。・間隔0〜4行 →
aws-access-token(複合ルールが成立)・間隔5行以上 →
generic-api-key(複合ルールは不成立。40文字のシークレット文字列を汎用ルールが単独で拾っている)つまり
withinLines = 5 は仕様どおり正確に効いている。件数が1のまま変わらなかったのは、複合ルールが外れた瞬間に別ルールが同じ文字列を拾い、合計が偶然一致していたからだった。念のため withinLines = 1 に書き換えた最小configでも確認したところ、隣接行なら検出・4行離すと不検出となり、値どおりに挙動が変わることを確認できた。教訓:検出ルールの挙動を確かめるときは件数を数えず、
--report-format json で RuleID まで見ること。件数は複数ルールの合計なので、片方が消えて片方が増えても変化しない。
CEL から Expr へ——v1.6.0 で式エンジンが入れ替わった
Betterleaks の設定は TOML だが、フィルタと検証のロジックは式言語で書く。ここが日本語の既報と現行版で食い違っている最大のポイントだ。
v1.6.0 のリリースノートは、この変更を明確に記している。Rice 自身のコメントも添えられている——CEL で実装した当時は expr-lang の存在を知らなかった、Expr は CEL が提供していたものをすべて備えつつコストが小さい、と。
移行に伴う実測値としてリリースノートに挙がっているのは次の通り。
・バイナリサイズ 30MB → 22.7MB(cel-go と protobuf/genproto 系の依存を削除)
・コールドスタート 160ms → 20ms(正規表現のコンパイル、各種フィルタ、検証式、cl100k_base トークナイザの読み込みをすべて遅延化)
・キーワードの前段フィルタを RRethy/ahocorasick に変更
・ルールに specificity(具体度)を導入し、より具体的なルールを先に走らせて汎用ルールの検出を抑制できるように
既存の CEL 形式の設定は互換として受理されるとも明記されているため、いきなり壊れるわけではない。ただし公式の方針は「新しい設定は Expr で書く」だ。2026年5月以前に書かれた日本語の解説記事は CEL 前提のものが多く、そのまま写すと非推奨の書き方を新規に増やすことになる。
自分が使っているバージョンで影響を受けるか
この変更で影響を受ける範囲はバージョンによって分かれる。まず手元のバージョンを確認する。
betterleaks version # 例: 1.7.1
betterleaks config --help # 設定の検証・確認用サブコマンド
| 使用バージョン | 式エンジン | 手元の CEL 設定 | 取るべき対応 |
|---|---|---|---|
| v1.5.x 以前 | CEL | そのまま動く | 影響なし。ただし新機能は入らない |
| v1.6.0 〜 v1.7.1 | Expr | 互換として受理される | 動くが、新規に書く式は Expr 構文へ |
| 今後のバージョン | Expr | 互換の維持は明言されていない | 早めに Expr へ書き換えておくのが安全 |
なお v1.7.1 では設定の最低バージョン要件が引き上げられている(betterleaksMinVersion / minVersion というフィールドが設定側にあり、バイナリと設定フォーマットの下限をそれぞれ指定できる)。組織で設定ファイルを共有配布している場合、バイナリのバージョンが混在すると設定が読めない組み合わせが生じうるので、配布側で下限を明示しておくと事故を避けられる。
現行の Expr で書いた設定はこうなる。prefilter は正規表現の実行前に走ってスキャン自体を打ち切る安価なフィルタ、filter はマッチ後に候補を捨てるフィルタだ。
# 正規表現より前に走る(安い)。attributes だけを見られる
prefilter = '''
filter.matchesAny(get(attributes, "path", ""), [
`(?i)\.(?:png|jpe?g|svg|pdf|exe)$`,
`(?:^|/)node_modules(?:/.*)?$`,
`(?:^|/)vendor(?:/.*)?$`
])
|| get(attributes, "git.author_name", "") == "renovate[bot]"
'''
# 正規表現でマッチした後に走る。finding を見られる
filter = '''
filter.containsAny(finding["secret"], [
"EXAMPLE", "CHANGEME", "YOUR_API_KEY_HERE", "0000000000000000"
])
'''
prefilter が attributes(パス、コミット作成者、コミットメッセージなど)だけを見るのは、正規表現を走らせる前に判断を終わらせて高速化するためだ。node_modules や vendor をここで落とすだけでスキャン量は大きく減る。
シークレットの「生死」を確かめる validate
もう一つ、Gitleaks 時代には無かった機能が validation——検出したシークレットが今も有効かを実際に問い合わせる仕組みだ。ルール定義の中に HTTP リクエストを書ける。
validate = '''
let r = http.get("https://api.github.com/user", {
"Accept": "application/vnd.github+json",
"Authorization": "token " + secret
});
r.status == 200 ? { "result": "valid", "username": r.json?.login ?? "" }
: r.status in [401, 403] ? { "result": "invalid", "reason": "Unauthorized" }
: validate.unknown(r)
'''
検出結果が数百件ある状況で「どれが今も生きているか」が分かるのは、対応の優先順位付けに直結する。ただし外部へリクエストが飛ぶ機能なので、既定では無効になっている点は理解しておきたい。有効化はオプトインで、送信回数の上限とレート制限も設定できる(v1.7.1 では検証回数の上限追加がコミットされている)。CI で無差別に有効化すると、検出した鍵を外部サービスへ送ることになる点は運用設計で意識すべきだ。
git の外をスキャンする——GitHub・GitLab・Hugging Face・S3
README のキャッチコピーは “Scan the world (for secrets)” だ。これは誇張ではなく、リポジトリの git 履歴以外が正面から対象になっている。実際、鍵が漏れる場所は git log の中だけではない。Issue のコメントに貼られたログ、CI のジョブ出力、公開バケットに置き忘れたダンプ——こうした面は従来のスキャナが手薄だった。
docs/scanning.md(v1.7.1)が定義するサブコマンドと対象リソース# GitHub: 組織配下すべて/履歴以外のリソースも
betterleaks github https://github.com/myorg
betterleaks github https://github.com/myorg --include issues,prs,actions,releases,gists
# 単一リソースだけを指定(PR・Issue・リリース・Actions実行・gist)
betterleaks github https://github.com/myorg/myrepo/pull/113
# GitLab: MR・CIジョブログ・スニペット
betterleaks gitlab https://gitlab.com/mygroup --include issues,mrs,releases,ci-jobs
# Hugging Face: モデル・データセット・Spaces・ディスカッション・バケット
betterleaks hf https://huggingface.co/myorg --include=discussions,prs
# S3 / Cloudflare R2: アカウント内の全バケットを列挙してスキャン
betterleaks s3 'https://<account-id>.r2.cloudflarestorage.com/*'
とくに実務で効きやすいのは GitHub Actions のワークフローログだ。デバッグ目的で環境変数をそのまま echo した結果がログに残る事故は珍しくなく、しかもリポジトリが public ならログも public になる。--include actions はこの面を直接見にいく。
Hugging Face 対応も、AI 開発の実態に沿った追加といえる。データセットや Space の設定ファイルに API キーが残るケースは、モデル配布が日常化した現在では十分に現実的な経路だ。
なお、これらの API リソースを広くスキャンするにはそれ相応の権限を持つトークンが必要になる。スキャン用トークン自体が強い権限を持つという構図になるため、権限は必要最小限に絞り、スキャン後は破棄する運用が望ましい。エージェントやツールに実キーを渡さない設計そのものについては OneCLI解説|AIエージェントに実APIキーを渡さない認証情報ゲートウェイの仕組みと自システム確認コマンド が隣接する話題を扱っている。
Gitleaks・TruffleHog との比較と、移行を判断する基準
主要3ツールを実測値で並べる。Star 数・ライセンス・最新リリースはいずれも 2026-07-28 に GitHub API で取得した。
| 項目 | Betterleaks | Gitleaks | TruffleHog |
|---|---|---|---|
| GitHub ★ | 1,545 | 28,338 | 27,229 |
| ライセンス | MIT | MIT | AGPL-3.0 |
| 最新リリース | v1.7.1(2026-07-27) | v8.30.1(2026-03-21) | 継続的に更新 |
| 新機能の受け入れ | 継続中 | 停止(公式表明) | 継続中 |
| 実装 | Go(CGO 不要) | Go | Go |
| 既定ルール数 | 412 | — | — |
| 式によるフィルタ | Expr(旧CELも互換受理) | allowlist(静的) | — |
| 検出物の有効性検証 | ルール内に定義(オプトイン) | なし | あり(主要機能) |
| git 以外の対象 | GitHub・GitLab・HF・S3 ほか | 限定的 | 多数のコネクタ |
| 複合ルール | あり(30ルールで使用) | なし | — |
判断の目安を整理する。
Betterleaks が向くケース:既に gitleaks を運用していて設定資産がある/誤検知の抑制に苦労している/GitHub Issue や CI ログまで対象を広げたい/ライセンスを MIT に保ちたい。
Gitleaks のままで良いケース:現状の検出で困っていない、CI が安定していて触りたくない。新機能は入らないが動作は続く。焦って移行する理由はない。
TruffleHog が向くケース:検出物の有効性検証を中心に据えたい、対応コネクタの多さを重視する。ただし AGPL-3.0 である点は、自社サービスへの組み込み方によってはライセンス上の検討が要る。MIT の2つとは条件が異なる。
ドロップイン置き換えを掲げているとおり、
betterleaks は設定ファイルの探索順に BETTERLEAKS_CONFIG と GITLEAKS_CONFIG の両方を含み、対象ディレクトリの .betterleaks.toml と .gitleaks.toml の両方を見にいく(betterleaks --help で確認できる)。まずはコマンド名を差し替えるだけで動くかを試し、そのうえで Expr 構文へ書き換えるのが現実的だ。ただし公式ドキュメントは、本番運用では上流の既定設定をそのまま extend せず、自前の設定を維持することを推奨している。上流にルールが追加されるたび検出結果が勝手に変わるのを避け、アップグレード時に新ルールを自分でレビューしてから採用するためだ。
導入するかどうかの判断
最後に、この記事で確認できた事実だけを踏まえて整理する。
・gitleaks は止まらないが、伸びもしない。 原作者本人が README で新機能凍結を明言している。既存運用を今すぐ壊す必要はない一方、新しい鍵の種類への追随は期待できない
・移行コストは低く設計されている。 環境変数・設定ファイル名ともに gitleaks 側を受け付ける。まずコマンドを差し替えて動作確認するところから始められる
・設定を書き換えるなら Expr で。 CEL は互換受理という位置づけで、公式の推奨は Expr。2026年5月以前の解説を写経しないこと
・数字は原典で確認する。 「98.6%」は手法比較の recall であって、ツールの総合精度ではない
・カバー範囲の広さが実質的な差。 git 履歴だけでなく Issue・PR・CI ログ・S3・Hugging Face を同じツールで見られる点は、gitleaks には無い
そして、スキャナは最後の砦ではなく検知層でしかない。検出された時点で鍵は既にリポジトリに入っている。鍵をローテーションする手順と、そもそも鍵をコミットさせない仕組み(環境変数・シークレットマネージャ・pre-commit)の両方があって初めて機能する。
参照ソース
・betterleaks/betterleaks — GitHub リポジトリ(README、docs/config.md、docs/scanning.md、config/betterleaks.toml、および v1.6.0/v1.7.1 のリリースノート。2026-07-28 参照)
・Rare Not Random — Zachary Rice, Looking at Computer(2026-02-20)(Token Efficiency の仕組みと CredData ベンチマークの原典)
・Regex is almost all you need — Zachary Rice, Looking at Computer(検出エンジンの設計思想)
・gitleaks/gitleaks — GitHub リポジトリ(README の機能凍結表明、リリース履歴。2026-07-28 参照)
・Betterleaks: The Gitleaks Successor Built for Faster Secrets Scanning — Aikido Security(プロジェクト発足の経緯)