3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

APIキー・パスワードをGitHubに漏らさない7つの設定(.gitignore・gitleaks・コピペOK)

3
Posted at
  • 対象: Git / GitHub でサイトのソースを管理している Web 担当者・制作会社・サーバー管理者
  • 解決すること: .env や wp-config.php、API キー・DB パスワードをうっかりコミット / push する事故を、手元・GitHub・CI の 3 か所で止める
  • 前提: Git 2.x / Linux or macOS / GitHub(GitLab 等でも 1〜4・7 はそのまま使えます)

この記事はご自身が管理するリポジトリの点検・予防を目的としています。他人のリポジトリを調べる用途には使わないでください。

なぜ「消せばOK」にならないのか

Git は すべての版を履歴として保存する 仕組みです。.env を 1 回コミットしたあとで削除コミットを作っても、過去のコミットを開けば中身はそのまま読めます。GitHub に push 済みなら、clone した人・fork・CI のキャッシュなど 自分では消せない複製 も増えていきます。

GitHub 公式の「リポジトリから機密データを削除する」手順も、最初のステップは 履歴の書き換えではなく、秘密情報の失効・ローテーション です。漏れた鍵が使えなくなれば、それで問題が片付く場合もあるからです。

そこで考え方は次の 3 段構えにします。

段階 目的 本記事の設定
入れない そもそもリポジトリに秘密を置かない ①②
止める コミット・push・PR の時点で機械的に検出して止める ③④⑤⑥
漏れたら 失効 → 影響確認 → 履歴削除の順で対処する ⑦

① .gitignore を「リポジトリ」と「PC 全体」の二重にする

リポジトリの .gitignore だけだと、新しく作ったリポジトリで書き忘れることがあります。PC 全体に効くグローバル除外 も設定しておきます。

# PC 全体の除外ファイルを作成して Git に登録
mkdir -p ~/.config/git
cat >> ~/.config/git/ignore <<'EOF'
# 秘密情報になりやすいファイル
.env
.env.*
!.env.example
*.pem
*.key
*.p12
id_rsa
id_ed25519
*.sql
*.sql.gz
EOF
git config --global core.excludesFile ~/.config/git/ignore

リポジトリ側の .gitignore にも同じものを書きます(チームの全員に効かせるため)。WordPress なら wp-config.php も対象です。

.env
.env.*
!.env.example
wp-config.php
*.pem
*.key
*.sql
*.sql.gz

.env の代わりに 値の入っていない見本 .env.example をコミットして、必要な変数名だけ共有するのが定番です。

# .env.example(値は空にする)
DB_HOST=
DB_NAME=
DB_USER=
DB_PASSWORD=
STRIPE_SECRET_KEY=

すでに追跡されているファイルは .gitignore が効かない

.gitignore は まだ追跡していないファイル にしか効きません。すでにコミット済みのファイルは追跡を外す必要があります。

# 除外されているか・どのルールで除外されたかを確認
git check-ignore -v .env wp-config.php

# 追跡されている秘密ファイルを一覧
git ls-files | grep -E '(^|/)(\.env(\..*)?|wp-config\.php|.*\.(pem|key|sql))$' | grep -v '\.env\.example$'

# 追跡だけ外す(手元のファイルは残る)
git rm --cached .env
git commit -m "Stop tracking .env"

git rm --cached は これから先 の追跡を止めるだけで、過去の履歴からは消えません。中身が本物の秘密なら ⑦ の手順(失効)に進んでください。

Docker を使っているなら .dockerignore も

docker build はビルドコンテキストのファイルをまとめて送るため、COPY . . で .env や .git がイメージに入り、イメージ配布で漏れることがあります。

# .dockerignore
.env
.env.*
!.env.example
.git
*.pem
*.key
*.sql

② 秘密はコードに書かず、外から読み込む

ソースに直接書いた値は、どれだけ除外ルールを整えても漏れます。値はコードの外(環境変数やドキュメントルート外のファイル)に置きます。

PHP の例です。.env はドキュメントルート(公開ディレクトリ)の 外 に置きます。

<?php
// /var/www/app/public/index.php から 1 つ上の /var/www/app/.env を読む例
// 実運用では vlucas/phpdotenv 等のライブラリ利用を推奨
$envFile = dirname(__DIR__) . '/.env';
if (is_readable($envFile)) {
    foreach (file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
        if ($line === '' || $line[0] === '#' || !str_contains($line, '=')) {
            continue;
        }
        [$key, $value] = explode('=', $line, 2);
        $_ENV[trim($key)] = trim($value);
    }
}

$dbPassword = $_ENV['DB_PASSWORD'] ?? '';
if ($dbPassword === '') {
    http_response_code(500);
    exit('Configuration error');
}

WordPress の wp-config.php も、値を環境変数から読む形にすればファイル自体を共有しやすくなります。

<?php
// wp-config.php(抜粋)
define( 'DB_NAME',     getenv( 'WP_DB_NAME' ) );
define( 'DB_USER',     getenv( 'WP_DB_USER' ) );
define( 'DB_PASSWORD', getenv( 'WP_DB_PASSWORD' ) );
define( 'DB_HOST',     getenv( 'WP_DB_HOST' ) ?: 'localhost' );

ファイルの権限も絞っておきます。

chmod 600 /var/www/app/.env
chown www-data:www-data /var/www/app/.env   # 実行ユーザーに合わせる

③ まず今のリポジトリを棚卸しする(gitleaks)

設定を入れる前に、すでに履歴に入っていないか を確認します。定番の検出ツール gitleaks を使います(公式 README によると現在は機能追加を終えてセキュリティ修正のみの「feature complete」扱いですが、検出用途には十分使えます)。

# 例: Linux x64 にバイナリを入れる(最新版は Releases ページで確認)
VER=8.30.1
curl -sSLo /tmp/gitleaks.tar.gz \
  "https://github.com/gitleaks/gitleaks/releases/download/v${VER}/gitleaks_${VER}_linux_x64.tar.gz"
tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks
sudo install /tmp/gitleaks /usr/local/bin/gitleaks
gitleaks version
# リポジトリの全履歴を検査(git log -p を走査する)
cd /path/to/your-repo
gitleaks git -v

# 全ブランチを対象にしてレポートを JSON で残す
gitleaks git -v --log-opts="--all" --report-path gitleaks-report.json

# Git 管理外も含めて作業ディレクトリのファイルを検査
gitleaks dir -v .

終了コードは 0 = 検出なし / 1 = 検出あり(またはエラー) です。誤検知を除外したいときは、レポートに出る Fingerprint をリポジトリ直下の .gitleaksignore に 1 行ずつ書きます。

# .gitleaksignore(本物の秘密を除外しないこと)
a1b2c3d4e5f6...:tests/fixtures/dummy.env:generic-api-key:12

ツールが無くても、Git だけで「特定の文字列が過去に入ったか」は探せます。

# 文字列が追加・削除されたコミットを全ブランチから探す
git log --all --oneline -S 'DB_PASSWORD'

# 一度でも存在したことのあるファイル名を探す
git log --all --oneline --name-only -- '*.env' '.env' 'wp-config.php'

④ コミット前に止める(pre-commit + gitleaks)

棚卸しが終わったら、コミットした瞬間に止める 仕組みを入れます。pre-commit フレームワークに gitleaks のフックを登録します。

pipx install pre-commit   # または pip install --user pre-commit
# .pre-commit-config.yaml(リポジトリ直下に置いてコミットする)
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.30.1
    hooks:
      - id: gitleaks
pre-commit install          # .git/hooks/pre-commit に登録(clone した各自が 1 回実行)
pre-commit autoupdate       # rev を最新リリースに更新
pre-commit run --all-files  # 導入直後に全ファイルで一度確認

これで秘密らしき文字列を含むコミットは、手元で失敗します。

フックは 各自の手元で pre-commit install しないと動きません。README の環境構築手順に 1 行入れておくのと、⑤⑥ のサーバー側チェックを併用するのがポイントです。また git add . / git commit -a で丸ごと入れる癖をやめ、git add -p や git status で中身を確認してからコミットするのも効果があります(GitHub 公式も推奨)。

⑤ GitHub の push protection を有効にする

手元のフックはスキップできてしまうので、GitHub 側でも push を止めます。push protection は、既知の形式の秘密を含む push を リポジトリに届く前にブロック する機能です。

種類 既定 効く範囲
ユーザー単位の push protection GitHub.com で既定で有効 自分が 公開リポジトリ へ push するとき
リポジトリ単位の push protection 既定は無効(管理者が有効化) そのリポジトリへの全員の push。非公開リポジトリでは GitHub Secret Protection の有効化が必要

リポジトリでの有効化手順(GitHub Docs より):

リポジトリ → Settings
  → 左メニュー「Security and quality」の Advanced Security
  → 「Secret Protection」の Enable(未有効の場合)
  → 「Push protection」の Enable

ブロックされた push は、書き込み権限があれば理由を選んでバイパスできますが、バイパスはアラート・監査ログ・管理者へのメール通知として記録 されます。「テストで使う」「誤検知」を選ぶとアラートは閉じた状態で、「後で直す」を選ぶと開いたアラートとして残ります。組織では バイパスできる人を限定する(delegated bypass) 設定も検討してください。

⑥ CI でも全履歴を検査する(GitHub Actions)

フックを入れていない人のコミットや、push protection が対応していない形式の秘密に備えて、PR と定期実行で検査します。gitleaks-action の README の例をベースにしています。

# .github/workflows/gitleaks.yml
name: gitleaks
on:
  pull_request:
  push:
  workflow_dispatch:
  schedule:
    - cron: "0 4 * * *"   # 毎日 4:00 UTC
permissions:
  contents: read
  pull-requests: write    # PR へのコメント用
jobs:
  scan:
    name: gitleaks
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0  # 全履歴を取得(浅い clone だと過去のコミットを検査できない)
      - uses: gitleaks/gitleaks-action@v3
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}  # Organization のリポジトリのみ必要
  • 個人アカウント のリポジトリはライセンス不要、Organization のリポジトリは gitleaks.io で取得できる無料ライセンスキーを Secrets に登録します(README 記載)。
  • CI 側で使う秘密(デプロイ用の鍵など)は、ワークフロー YAML に直書きせず リポジトリ / Environment の Secrets に登録し、${{ secrets.XXX }} で参照します。

⑦ 漏れたときの手順:「失効 → 影響確認 → 履歴削除」の順

検出されたら、最初にやるのは履歴の書き換えではなく秘密の無効化 です。履歴を消している間も、すでに取得された鍵は使えてしまうからです。

1. 失効・ローテーション(最優先)

漏れたもの すぐやること
クラウドのアクセスキー 新しいキーを発行 → アプリを切り替え → 古いキーを無効化・削除
決済・外部 API のシークレット 管理画面で再発行(ロール)し、古いキーを失効
DB パスワード パスワード変更 + 接続元の制限を確認
SSH 秘密鍵 サーバーの authorized_keys から対応する公開鍵を削除し、鍵を作り直す
WordPress の wp-config.php DB パスワード変更 + 認証用キー・ソルト(AUTH_KEY 等)の再生成(全ユーザーが再ログインになる)

2. 影響確認

漏れていた期間(コミット日時 〜 失効日時)について、各サービスの利用履歴・請求・アクセスログに 見覚えのない利用がないか を確認します。公開リポジトリだった場合は特に念入りに確認してください。

3. 履歴から削除(必要な場合)

失効済みでも、社内情報やパスワードの使い回し等で履歴に残したくない場合は、GitHub 公式が案内する git-filter-repo(2.47 以降)で書き換えます。作業前にリポジトリのバックアップを取り、開いている PR は先にマージかクローズ しておきます。

# 新しく clone した作業用コピーで実施する
git clone git@github.com:OWNER/REPO.git REPO-cleanup
cd REPO-cleanup

# ファイルごと全履歴から削除する場合
git-filter-repo --sensitive-data-removal --invert-paths --path .env

# 特定の文字列だけ置換する場合(passwords.txt に 1 行 1 件で記載)
# git-filter-repo --sensitive-data-removal --replace-text ../passwords.txt

# 影響した PR の数を確認(想定より多ければ中止して clone をやり直す)
grep -c '^refs/pull/.*/head$' .git/filter-repo/changed-refs

# 内容を確認してから強制 push(ここから先は元に戻せない)
git push --force --mirror origin

その後の注意点(GitHub Docs より):

  • refs/pull/ への push は失敗します(読み取り専用のため)。PR の参照やキャッシュされた表示は GitHub Support に依頼 して削除してもらいます。
  • 共同作業者には 古い clone を削除して clone し直す よう依頼します。古い履歴のまま git pull → git push すると、削除した内容が戻ってきます。
  • コミットハッシュが変わり、コミット・タグの署名も外れます。ブランチ保護は一時的に解除が必要な場合があります。
  • fork は持ち主ごとの対応になり、こちらからは消せません。だからこそ 1 の失効が最優先 です。

まとめ

# 設定 止める場所
① .gitignore をリポジトリと PC 全体の二重に / 追跡済みは git rm --cached / .dockerignore 手元
② 秘密はコードに書かず、ドキュメントルート外の .env や環境変数から読む コード
③ gitleaks git で全履歴を棚卸し 手元(初回)
④ pre-commit + gitleaks でコミット時に止める 手元
⑤ GitHub の push protection を有効化・バイパスできる人を限定 GitHub
⑥ gitleaks-action で PR・定期実行の全履歴検査 CI
⑦ 漏れたら「失効 → 影響確認 → git-filter-repo」の順 事後対応

「消せば大丈夫」ではなく、入れない・止める・漏れたら失効 の 3 段構えにしておけば、1 人のうっかりがサービス停止や不正利用につながる確率を大きく下げられます。

関連記事


本記事のような設定の抜けは、Web サイトを 9 つの守りでまるごと守るサイトドックでまとめて対策できます → https://sitedock.jp

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?