秘密情報をGitに入れない仕組み|.gitignore・git-secrets・履歴から漏れた鍵の削除

kento_morota 11分で読めます
社内のAI・IT活用、技術がわかる相手に無料相談。 相談 →
目次

「.envをうっかりコミットしてpushしてしまった」「公開リポジトリにAWSのアクセスキーが含まれていると通知が来た」「削除コミットを積んだのに、履歴を辿れば今でも鍵が見える」。秘密情報のGitへの混入は、注意深い開発者でも一度は経験する事故で、しかも"気をつける"だけでは再発します。

この記事では、秘密情報がコミットされる典型的な経路を整理したうえで、.gitignoreと.env.exampleの運用、pre-commitフックでのgitleaks/git-secretsによる自動検知、GitHubのsecret scanning、そして漏れてしまった場合に「鍵の無効化が先、履歴の書き換えは後」である理由とgit filter-repoの手順を解説します。どこに秘密情報を保管すべきかという話はシークレット管理入門に譲り、ここでは「Gitに入れない・入っても検知する・入ったら消す」に集中します。

秘密情報がコミットされる典型経路

.envだけが問題ではない

混入経路として最も多いのは.envファイルですが、それ以外にも見落とされやすい場所があります。

  • 設定ファイル:config/database.yml、appsettings.json、application.propertiesなどに直接パスワードを書き、そのままコミットする
  • ログ・ダンプ・デバッグ出力:リクエストヘッダーごと出力したログファイルやDBダンプに、トークンやCookieが含まれる
  • IaCの状態ファイル:terraform.tfstateにはRDSのパスワードなどが平文で入る
  • ノートブック・スクリプトの実験コード:検証用に直書きしたAPIキーをそのまま残す
  • git add . と git commit -a:意図しないファイルをまとめて含めてしまう
  • プライベートリポジトリの油断:「非公開だから大丈夫」と入れた鍵が、のちに公開化・フォーク・移管で露出する

いずれも「人が毎回気をつける」以外の防御がない状態で起きています。対策は、混入を物理的に起きにくくする層と、起きても検知する層の二段構えで考えます。

.gitignoreと.env.exampleの運用

最初のコミットより前に.gitignoreを整える

.gitignoreは「追跡していないファイル」を無視するだけで、すでに追跡済みのファイルには効きません。.envをコミットしたあとに.gitignoreへ追加しても、変更は追跡され続けます。リポジトリ作成時点で用意するのが原則で、後から追加した場合は git rm --cached でインデックスから外す必要があります。

# .gitignore(秘密情報関連の最低限)
.env
.env.*
!.env.example
*.pem
*.key
*.p12
*.pfx
credentials.json
service-account*.json
terraform.tfstate
terraform.tfstate.backup
.terraform/
*.log
dump.sql

# すでに追跡してしまった.envをインデックスから外す(ファイル自体は残る)
git rm --cached .env
git commit -m "chore: stop tracking .env"

ここで注意すべきは、この操作は「これ以降のコミット」から外すだけで、過去の履歴には.envの中身が残っていることです。過去分の扱いは後半で説明します。

.env.exampleで「必要な変数」だけを共有する

チームで必要な環境変数を共有するために、値を空にした.env.exampleをコミットします。新メンバーはこれをコピーして自分の値を入れます。値の欄には「どこで取得するか」をコメントで残しておくと、Slackで鍵を送り合う行動も減ります。

# .env.example(コミットする。実際の値は書かない)
DATABASE_URL=postgres://user:password@localhost:5432/app_dev
STRIPE_SECRET_KEY=            # 1Passwordの「開発用Stripe」から取得
AWS_ACCESS_KEY_ID=            # IAM Identity Centerで一時クレデンシャルを発行
SESSION_SECRET=               # openssl rand -hex 32 で各自生成

pre-commitフックで自動検知する

gitleaksをコミット前に走らせる

gitleaksは、コミット内容や履歴全体を正規表現とエントロピー(文字列のランダムさ)で走査し、AWSキー・GitHubトークン・秘密鍵などのパターンを検知するツールです。pre-commitフレームワークと組み合わせると、コミットの瞬間に自動で止められます。

# インストール
brew install gitleaks pre-commit

# .pre-commit-config.yaml(リポジトリ直下にコミットする)
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.21.2
    hooks:
      - id: gitleaks

# 各開発者が一度だけ実行してフックを有効化
pre-commit install

# 既存の履歴全体をスキャンする(導入時に一度実施)
gitleaks git --redact -v

フックはリポジトリをcloneしただけでは有効にならず、各開発者がpre-commit installを実行する必要があります。そのためCIでも同じスキャンを走らせて、フックを入れ忘れた人のコミットも止めるのが定石です。GitHub Actionsならgitleaks公式のアクションをプルリクエストのワークフローに1ステップ追加するだけです。git-secrets(AWS製)も同様の目的のツールですが、AWSキーのパターンが中心で、近年はgitleaksの方が検知パターンの網羅性とメンテナンス頻度で選ばれることが多いです。

誤検知への向き合い方

テスト用のダミートークンやサンプルの公開鍵で検知されることがあります。その場合は.gitleaksignoreに検知結果のフィンガープリントを追記するか、対象行に gitleaks:allow コメントを付けて除外します。「面倒だからフックを無効化する」のが最も危険で、除外は必ず理由が残る形で行ってください。

GitHub secret scanningとpush protection

GitHubは、リポジトリにpushされた内容から既知のプロバイダー(AWS・Stripe・Slack・OpenAIなど)のトークン形式を検知するsecret scanningを提供しています。公開リポジトリでは自動で有効になり、検知するとプロバイダー側に通知されて鍵が自動失効することもあります。さらにpush protectionを有効にすると、検知した時点でpush自体が拒否されます。プライベートリポジトリでも、リポジトリのSettings → Code security から有効化できます(プランにより利用範囲が異なります)。

ただし、これはGitHubが知っているパターンに限られます。自社の内部APIの独自トークンや、DBの平文パスワードは検知できないため、ローカルのgitleaksとCIスキャンとの併用が前提です。

漏れてしまったときの対応|無効化が先、履歴書き換えは後

なぜ履歴の削除より鍵の失効を優先するのか

公開リポジトリにpushされた鍵は、数分以内にクローラーに収集されると考えてください。GitHubのイベントを監視するボットが多数存在し、削除コミットを積んでもforceでpushし直しても、すでに取得された鍵は戻りません。また、フォーク・ローカルクローン・CIのキャッシュ・プルリクエストの参照など、履歴を書き換えても残る場所が複数あります。したがって対応の順番は次のとおりです。

  1. 鍵を即座に無効化・ローテーションする(AWSならIAMでアクセスキーを削除、Stripeならロールして再発行)。これが済むまで他の作業をしない。
  2. その鍵で不正利用されていないかを確認する(CloudTrail・請求・監査ログ)。
  3. 新しい鍵をシークレットマネージャー経由で配布し直し、アプリを更新する。
  4. そのうえで履歴から削除し、関係者にクローンし直しを依頼する。

git filter-repoで履歴から完全に消す

git filter-branchは公式に非推奨で、後継のgit filter-repoを使います。特定ファイルを全履歴から削除する場合と、ファイル内の特定文字列だけを置換する場合の2通りがあります。

# インストール
brew install git-filter-repo

# 作業用に新しくクローンして実施する(filter-repoは安全のためfreshなcloneを要求する)
git clone --mirror git@github.com:example-org/app.git app-cleanup
cd app-cleanup

# 方法1: ファイルを全履歴から削除
git filter-repo --invert-paths --path .env --path config/secrets.yml

# 方法2: 文字列だけを置換(replacements.txt に1行1パターン)
#   AKIAIOSFODNN7EXAMPLE==>REDACTED_AWS_KEY
git filter-repo --replace-text replacements.txt

# 書き換えた履歴を強制push(保護ブランチは一時的に解除が必要)
git push --force --all
git push --force --tags

force push後は、全メンバーがローカルを捨てて再クローンする必要があります。古いローカルからpushすると、消したはずのコミットが復活します。GitHub上ではプルリクエストや到達不能なコミットにキャッシュが残る場合があるため、公開リポジトリで完全に消したい場合はGitHubサポートへの削除依頼も必要です。履歴の書き換えは通常のマージやリベースとは影響範囲が異なるため、rebaseとmergeの違いを理解したうえで、チーム全体に周知して実施してください。

トラブル事例:削除したはずの鍵で不正利用が続いた

症状:公開リポジトリにAWSアクセスキーを含む.envを誤ってpush。気づいてすぐ削除コミットをpushしたが、翌日AWSから高額な利用通知が届いた。
原因:削除コミットは履歴の先頭を変えるだけで、過去のコミットには鍵が残っていた。またpush後数分で自動収集されており、履歴を消しても意味がなかった。鍵自体を無効化していなかったため、EC2の大量起動に使われ続けた。
対処:IAMでアクセスキーを削除し、起動されたリソースをすべて停止。CloudTrailで操作範囲を確認し、AWSサポートに事象を報告した。その後git filter-repoで履歴を書き換え、gitleaksのpre-commitとCIスキャン、GitHubのpush protectionを有効化。長期的なアクセスキーの利用自体をやめ、IAMロールと一時クレデンシャルに移行した。「削除コミット」が対策にならないことをチーム全員が学ぶ契機になった事例です。

まとめ

秘密情報の混入は、.gitignoreと.env.exampleで起きにくくし、gitleaksのpre-commitフックとCIスキャン、GitHubのpush protectionで起きても止める、という多層で防ぎます。それでも漏れたときは、履歴の書き換えより先に鍵を無効化し、不正利用の有無を確認してから、git filter-repoで履歴を消して全員に再クローンを依頼します。この手順を「事故が起きてから調べる」のではなく、平時に一度リハーサルしておくことが最も効果的な備えです。開発フローにセキュリティチェックを組み込みたい場合は、Harmonic Societyのシステム開発・インフラ支援にご相談ください。

#Git#シークレット#gitleaks#セキュリティ

Harmonic Society

この記事の内容、自社の業務でも活かせそうですか?

ローカルLLM・AI・クラウドなどの技術導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK。まずは30分の無料相談から。売り込みはしません。

共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

プライバシーポリシーに基づき管理します

関連記事

Related / 9 articles

  1. プログラミング

    DDoS攻撃の仕組みと対策入門|レイヤー別の防御とCDN・クラウドの活用

    DDoS攻撃をボリューム型・プロトコル型・アプリ層に分けて仕組みを解説し、自前サーバーで防げない理由、CloudflareやAWS Shieldの標準防御、オリジンIPの隠し方、レートリミットとBot対策、攻撃を受けたときの初動、費用が跳ね上がるDenial of Walletへの備えまでわかります。

  2. プログラミング

    WAFとは?仕組み・導入パターン・誤検知対策|Webアプリを攻撃から守る実践ガイド

    WAFがファイアウォールやIDSと何が違うのか、シグネチャとマネージドルールの仕組み、Cloudflare WAF・AWS WAF・ModSecurityの比較、フォーム送信がブロックされる誤検知の調査と例外設定、ログ監視、WAFが代替できないことまで実践的に解説します。

  3. プログラミング

    セキュリティヘッダー入門|CSP・HSTS・X-Frame-Optionsの設定と効果を実践解説

    CSP・HSTS・X-Frame-Options・X-Content-Type-Optionsなど主要セキュリティヘッダーが防ぐ攻撃と、CSPのReport-Onlyからの段階導入、nonce/hash、HSTS preloadの不可逆リスク、Nginx・Next.js・Astroでの設定例、確認方法を解説します。

  4. プログラミング

    クラウドの通信費(Egress)入門|データ転送量課金の仕組みと転送コストを抑える設計

    クラウドの「受信無料・送信有料」の原則、AZ間・リージョン間・インターネット向けの単価差、NATゲートウェイ処理料の罠、CDNで転送量を減らす方法、バックアップやログ転送の見落とし、請求書で転送料を特定する手順を解説。想定外の請求を防げます。

  5. プログラミング

    開発・ステージング・本番環境の分離設計|環境差分をなくす構成とアクセス制御

    開発・ステージング・本番それぞれの目的と、構成をコードで揃える方法、環境別の設定注入、本番データを使わないテストデータ戦略、ステージングの保護(Basic認証・IP制限・noindex)、コストを抑える運用までを解説。環境差分による本番障害を防げます。

  6. プログラミング

    ngrok・Cloudflare Tunnelでローカルを公開|Webhook開発とデモ環境の作り方

    NAT内のローカル環境にStripeやLINEのWebhookを届けるトンネリングの仕組みを解説。ngrok・Cloudflare Tunnel・localtunnelの比較、固定ドメインと認証、リクエスト検査、公開時のセキュリティ、自宅サーバー公開への応用までわかります。

  7. プログラミング

    ローカル開発環境のHTTPS化|mkcert・hostsファイル・自己署名証明書の正しい使い方

    ローカル開発をHTTPS前提にすべき理由(Secure Cookie・Service Worker・OAuth)と、mkcertでローカルCAを作りhostsで独自ドメインを割り当ててVite・Next.js・Dockerで使う手順を解説。証明書警告を無視する癖の危険も理解できます。

  8. プログラミング

    localhost・0.0.0.0・127.0.0.1の違い|ポートとUnixソケットを理解して「つながらない」を解決

    localhost・127.0.0.1・0.0.0.0の意味の違い、Dockerで外から接続できない原因、host.docker.internal、ポート競合の調べ方、Unixソケットの利点と権限、1024未満ポートの制約を解説。「つながらない」を仕組みから解決できます。

  9. プログラミング

    GitOps入門|ArgoCD・FluxでGitをインフラの正とするデプロイ運用

    GitOpsのPush型とPull型の違い、差分検知と自動同期の仕組み、リポジトリ構成、ArgoCDの最小導入手順までを解説。git revertで戻せるデプロイ運用が理解でき、Kubernetes以外への応用の考え方もわかります。

Harmonic Society

「読んで終わり」にせず、自社の業務で試してみませんか?

AI・ローカルLLM・クラウドの導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK・売り込みなし。

無料・30分・オンラインOK|1営業日以内に返信します