おはようございます。まさきんです。
家の自動化スクリプトを、GitHub の公開リポジトリに置く準備をしています。秘密情報を洗い出したところ、スキャナが「漏れなし」と言ったのに2件残っていました。その手順をまとめておきます。
※記事公開時点の情報です。最新の条件は公式サイトでご確認ください。
- gitleaks は正規表現とエントロピーで、APIキーなど「形でわかる秘密」を見つけるツールです
- 形に特徴のない直書きの値は、スキャナをすり抜けることがあります
- 旧 .env の値そのものでリポジトリ全体を突き合わせると、すり抜けた値を拾えます
- 公開リポジトリでは GitHub の Secret Scanning が無料で自動実行されます
- 履歴に入ってしまった秘密は、履歴の書き換えより先にキーの無効化・再発行をします
「no leaks」でも残っていた2件
僕の家では、いろいろな自動化ツールが動いています。設定ファイル(.env)は14本に分かれ、鍵ファイルもあちこちにありました。これを1か所の設定ファイル(41キー)と credentials フォルダに1本化しました。コード側は約70ファイルの参照を切り替えています。
移動したものを git に入れた想定で、gitleaks をかけました。結果は「no leaks」です。
ただ、念のため旧 .env の値そのものでも突き合わせました。すると、gitleaks が拾えなかった直書きが2件出てきました。1件はLAN内機器のアクセスコードです。もう1件は、個人的なファイル名に関わる値でした。
どちらもAPIキーらしい見た目ではありません。スキャナが見逃したのは、仕組みを考えると自然なことかもしれません。
gitleaks はどうやって秘密を見つけるか
gitleaks は、パスワードやAPIキーなどの秘密を探すツールです。git の履歴やファイル、標準入力を調べられます。
検出の中心は「ルール」です。ルールごとに正規表現があり、サービスごとのトークンの形を見ています。公式リポジトリの標準設定には、200を超えるルールが入っていました。
もう一つの軸がエントロピーです。文字の並びがどれだけランダムかを表す数値です。汎用のAPIキーのルールは、key などの単語と値のエントロピーで判定しています。
整理すると次のとおりです。
| 種類 | gitleaks で拾えるか | 理由 |
|---|---|---|
| 主要サービスのAPIキー・トークン | 拾いやすい | 決まった接頭辞や形がある |
| password= に続く長いランダム文字列 | 拾いやすい | キーワードとエントロピーの両方に当たる |
| 短い数字だけのアクセスコード | 拾いにくい | 形にもランダムさにも特徴が少ない |
| 内部のURL・ホスト名・ファイル名 | 拾いにくい | 普通の文字列と見分けがつかない |
| キー名のない直書きの値 | 拾いにくい | 手がかりになる単語がない |
インストールと基本のコマンド
公式READMEでは、Homebrew・Docker・Go からのビルドでインストールできると案内されています。Mac なら次のとおりです。
brew install gitleaks
注意したいのはコマンド名です。古い記事に出てくる detect と protect は、v8.19.0 で非推奨になりました。いまは次の3つのモードが基本です。
| コマンド | 対象 | 使いどころ |
|---|---|---|
| gitleaks git | git の履歴(git log -p の差分) | コミット済みの内容を過去までさかのぼって調べる |
| gitleaks dir | ディレクトリやファイル | まだ git に入れていないファイルを調べる |
| gitleaks stdin | 標準入力 | 別のコマンドの出力をそのまま調べる |
よく使う形は次のとおりです。
# いまのリポジトリの履歴を調べる
gitleaks git -v
# 公開予定のフォルダを、git に入れる前に調べる
gitleaks dir -v ./my-scripts
漏れが見つかると終了コードは1、何もなければ0です。スクリプトで止める判定に使えます。
誤検知の扱い
テスト用のダミー値などで、誤検知が出ることもあります。公式READMEの方法は3つです。
- その行に
gitleaks:allowというコメントを付ける - レポートの Fingerprint を
.gitleaksignoreに書いて、その1件だけ無視する - 設定ファイル
.gitleaks.tomlの allowlist で、パスや正規表現ごと除外する
allowlist は v8.25.0 で [[allowlists]] という書き方に変わりました。古い設定例を写すときは注意したいところです。
個人的には、Fingerprint で1件ずつ無視するのが安全だと思います。パスごと除外すると、あとで本物が入っても気づけません。
pre-commit でコミット前に止める
普段から止める仕組みとして、gitleaks は pre-commit のフックにもなります。
- pre-commit をインストールする
- リポジトリ直下の
.pre-commit-config.yamlに次の内容を書く pre-commit autoupdateとpre-commit installを実行する
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.24.2
hooks:
- id: gitleaks
GitHub の Secret Scanning と Push Protection
GitHub 側にも、秘密を見つける仕組みがあります。公開リポジトリでの扱いは次のとおりです。
| 機能 | 公開リポジトリでの扱い | 何をするか |
|---|---|---|
| Secret Scanning | 無料で自動実行 | 全ブランチの git 履歴を調べ、見つかれば通知する |
| Push protection for users | アカウント単位で既定でオン | 公開リポジトリへの秘密入りの push を止める |
| Push protection for repositories | GitHub Secret Protection が必要 | リポジトリ単位で push を止める |
Secret Scanning は、Issue や Pull Request の本文も対象です。提携サービスのキーが見つかると、そのサービスに通知が行きます。
とはいえ、どちらも基本は「形でわかる秘密」が対象です。形のない値まで任せるのは、ちょっと危ないかもしれません。
.env の外出しと .gitignore
いちばん効くのは、秘密をコードに書かないことです。GitHub の公式ドキュメントも環境変数の利用を勧めています。
僕の1本化もこの考え方です。公開するときの形は次のようになります。
| ファイル | リポジトリに入れるか | 中身 |
|---|---|---|
| .env | 入れない | 本物の値 |
| .env.example | 入れる | キー名と、ダミーの値や説明 |
| .gitignore | 入れる | .env や credentials フォルダの除外設定 |
| credentials フォルダ | 入れない | 鍵ファイルなど |
.env.example を置いておくと、どのキーが必要かを公開しつつ値は隠せます。
ただ、.gitignore には落とし穴があります。git の公式ドキュメントにあるとおり、すでに追跡しているファイルには効きません。一度コミットしたファイルは、git rm --cached で追跡から外す必要があります。
ツールの棚卸しと同じく、通信費などの固定費も一度棚卸ししておくと見通しがよくなるかもしれません。
値そのもので突き合わせる自作チェック
ここが今回いちばん伝えたいところです。スキャナは「形」で探します。なので、自分が持っている「値」で探す方法を足します。旧 .env の値のリストが、リポジトリに出てこないかを調べる形です。
- 旧 .env から値の部分だけを取り出し、1行1値のファイルにする
- 空行と、短すぎて誤爆する値(true や 1 など)を外す
- 値リストはリポジトリの外に置く
grep -Fで、正規表現ではなく固定文字列として照合する- git の履歴にも同じ照合をかける
値リストを ~/private/values.txt に置いた場合の例です。
# 作業ツリー全体を照合する(.git は除外)
grep -rnF -f ~/private/values.txt --exclude-dir=.git .
# 履歴のすべてのコミットを照合する
git grep -nF -f ~/private/values.txt $(git rev-list --all)
-F を付けると、記号入りの値もそのままの文字列として探せます。値リストに空行があると、すべての行にヒットするので注意です。
値リストは「公開しない場所」に置きます。照合が終わったら消すか、鍵のかかる場所に戻すのが安心です。
置換スクリプトで二重置換した話
もう一つ、手を動かしてつまずいたことがあります。約70ファイルの参照を、置換スクリプトで書き換えたときのことです。
新しい文字列が古い文字列を含んでいるケースが3件ありました。例えば「config」を「config_shared」に置き換えるような形です。この場合、置換を2回通すと「config_shared_shared」になってしまいます。
僕は一度元に戻して、やり直しました。教訓は単純です。置換の前に、「新しい文字列の中に古い文字列が入っていないか」を確認します。入っている場合は、境界つきで1回だけ置き換えるのがよさそうです。
やり直したあと、読み込み確認をした18本はすべて正常でした。
履歴に入ってしまったときの順番
もし秘密をコミットしてしまったら、順番が大事です。GitHub の公式ドキュメントは、まずキーの無効化・再発行(ローテーション)を求めています。
| 順番 | やること | 理由 |
|---|---|---|
| 1 | キーやパスワードを無効化・再発行する | 古い値が使えなくなれば、被害はそこで止まる |
| 2 | コードを .env 読み込みに直す | 新しい値を直書きしないため |
| 3 | 必要なら git filter-repo で履歴を書き換える | 古い値を履歴から消すため |
| 4 | force push し、必要なら GitHub サポートに連絡する | キャッシュや Pull Request の参照を消すため |
公式ドキュメントでは、ローテーションだけで十分な場合もあるとされています。履歴を書き換えるとコミットのハッシュが変わり、フォークに残った分は消せないからです。
書き換えるときのツールは git filter-repo です。--sensitive-data-removal が使える版(2.47以上)が必要とされています。値を置き換える場合は、次の形です。
git-filter-repo --sensitive-data-removal --replace-text ../passwords.txt
公開リポジトリに一度でも出た秘密は、誰かに取得されている前提で考えます。履歴を消すより先に、キーを無効化して新しいものに替えるのが公式の順番です。
よくある落とし穴
- gitleaks の「no leaks」を、秘密が何もない証明だと思ってしまう
gitleaks gitだけを使い、まだコミットしていないファイルを調べ忘れる- .gitignore に書いたのに、すでに追跡中のファイルが残っている
- 値リストや置換リストを、リポジトリの中に置いてしまう
- 置換の新旧が部分一致していて、二重に置き換わる
- 履歴の書き換えだけして、キーのローテーションを忘れる
一つ目が、今回の僕の話です。スキャナは非常に頼れる道具です。ただ、見ているのは「形」だけです。値での照合と組み合わせるのが安心かなと思います。
決めるだけでなく数えて確かめる話は、164件のタスクを数えて分かったことでも書きました。秘密のチェックも同じ構図かもしれません。
始める前に確認すること
- 秘密がどのファイルに何本あるか、一覧になっているか
- .env と鍵ファイルを1か所に寄せたか
- .gitignore に .env と credentials フォルダが入っているか
- すでに追跡中の秘密ファイルを git rm --cached で外したか
- gitleaks の git と dir の両方をかけたか
- 旧 .env の値リストで作業ツリーと履歴を照合したか
- 値リストをリポジトリの外に置いたか
- GitHub の Push protection for users がオンになっているか
僕の公開リポジトリ化は、まだ途中の段階です。
よくある質問
Q. gitleaks で「no leaks」なら公開して大丈夫ですか?
形でわかる秘密は、かなり安心できると思います。ただ、短いコードや内部のファイル名は拾えないことがあります。値リストでの照合を併用するのがおすすめです。
Q. gitleaks detect と gitleaks git の違いは何ですか?
detect は v8.19.0 で非推奨になった古いコマンドです。いまは履歴を調べるなら git、フォルダを調べるなら dir を使います。古いコマンドも動きますが、ヘルプには表示されません。
Q. 個人の公開リポジトリでも Secret Scanning は使えますか?
使えます。公式ドキュメントでは、公開リポジトリでは無料で自動実行されると書かれています。全ブランチの履歴に加え、Issue や Pull Request の本文も対象です。
Q. Push Protection は自分で設定する必要がありますか?
アカウント単位の Push protection for users は、既定でオンです。公開リポジトリへの秘密入りの push を止めてくれます。設定の「Code security」から状態を確認できます。
Q. .gitignore に .env を書けば十分ですか?
まだコミットしていないなら、それで追跡されなくなります。ただ、すでに追跡しているファイルには効きません。git rm --cached で外したうえで、履歴に残っていないかも確認が必要です。
Q. 秘密をコミットしてしまったら、まず何をしますか?
キーやパスワードの無効化・再発行が先です。履歴の書き換えは、そのあとで要否を判断します。
参考にした公式情報
- gitleaks(GitHub リポジトリ・README)
- About secret scanning(GitHub Docs)
- About push protection(GitHub Docs)
- Push protection for users(GitHub Docs)
- リポジトリからの機密データの削除(GitHub Docs)
- git-filter-repo(GitHub リポジトリ)
- gitignore(Git 公式ドキュメント)
- pre-commit(公式サイト)
まとめ
gitleaks は、形でわかる秘密を見つけるのに非常に頼れるツールでした。一方で、形に特徴のない値は「no leaks」の裏に残ることがあります。
なので、スキャナと値での照合を組み合わせるのがよさそうです。サブスクの重複探しと同じで、視点を一つ足すと見え方が変わります。公開ボタンを押す前に、手元の .env を一度開いてみてください。
本記事にはアフィリエイト広告を含みます。当サイトのリンクを経由して商品・サービスへお申し込みいただいた場合、提携先企業から成果報酬を受け取ることがあります。 また、運営者は楽天グループに所属する社員であり、紹介プログラムを通じた報酬を受け取る場合があります。記事内容・評価は広告の有無にかかわらず、運営者の実体験と調査に基づき作成していますが、 上記の関係性をご理解のうえお読みください。詳しくは免責事項・アフィリエイト表記をご覧ください。
楽天モバイルの通信エリア・電波状況について気になることがある方は、公式の電波改善・調査依頼フォームからご相談いただけます。