GitHub上のコミット履歴から機密情報を削除する
状況
- あるメンバーが、機密情報をコミットしてGitHubにPushしてしまった
- 1年以上経ってからそれに気がついた
- 該当の機密情報をファイルから削除しても、過去のコミット履歴を見れば機密情報が参照できてしまうので、参照できないようにしたい
2つの選択肢
- git filter-branch
- gitの内部コマンドなので、追加のツールをインストールする必要がない
- 細かい調整がしやすい
- 使い方がやや複雑で、処理速度もやや遅め
- BFG Repo-Cleaner
- 細かい調整ができないけど、シンプルに使える
- brew install等でインストールして使う
シンプルにやりたいので、BFG Repo-Cleanerが良さそう
git filter-branch
BFG Repo-Cleaner
BFG Repo-Cleanerを使う場合の手順
ツールのインストール
$ brew install bfg
$ bfg --version
bfg 1.14.0
ブランチを整理する
- 不要なブランチを全て削除する
- マージした時に、自動的にブランチが削除されるように設定する
今やってる作業を整理する
- それぞれのローカルにある差分を全部GitHubにpushする
- GitHub上でOpenな状態になっているPRを全てマージするかcloseする
その他
- 使ってる全てのブランチの該当箇所から、機密情報を削除する?
適当なアプリを作って試してみる
$ mkdir bfg-sample
$ cd bfg-sample
$ git init
$ echo 'password123' > secret.txt
$ git add secret.txt
$ git commit -m 'Add secret'
(GitHubのGUIで、bfg-sampleという名前のリポジトリを作る)
$ git remote add origin git@github.com:nyshk97/bfg-sample.git
$ git branch -M main
$ git push -u origin main
$ cd ..
$ git clone --mirror git@github.com:nyshk97/bfg-sample.git
$ bfg --delete-files secret.txt bfg-sample.git
ここで以下のエラーが発生
Using repo : /Users/hoge/dev/bfg-sample.git
Found 2 objects to protect
Found 2 commit-pointing refs : HEAD, refs/heads/main
Protected commits
-----------------
These are your protected commits, and so their contents will NOT be altered:
* commit 06de1b9c (protected by 'HEAD') - contains 1 dirty file :
- secret.txt (12 B )
WARNING: The dirty content above may be removed from other commits, but as
the *protected* commits still use it, it will STILL exist in your repository.
Details of protected dirty content have been recorded here :
/Users/hoge/dev/bfg-sample.git.bfg-report/2023-07-26/12-54-34/protected-dirt/
If you *really* want this content gone, make a manual commit that removes it,
and then run the BFG on a fresh copy of your repo.
Cleaning
--------
Found 1 commits
Cleaning commits: 100% (1/1)
Cleaning commits completed in 19 ms.
BFG aborting: No refs to update - no dirty commits found??
最新のcommitは保護されているので、うまくいかないっぽい。
secret.txtを手動で削除して、その変更をcommitしてから、再度実行しろとのこと。
以下の手順でやり直す。
$ cd bfg-sample
$ rm -f secret.txt
$ git add -A
$ git commit -m 'Remove secret.txt'
$ git push origin main
この状態だと、普通に過去のcommit履歴から値を参照できる。

$ cd ..
$ rm -rf bfg-sample.git
$ rm -rf bfg-sample.git.bfg-report # bfgの実行時に生成される、レポートのディレクトリ
$ git clone --mirror git@github.com:nyshk97/bfg-sample.git
$ bfg --delete-files secret.txt bfg-sample.git
以下のメッセージが表示された。成功っぽい。
Using repo : /Users/hoge/dev/bfg-sample.git
Found 1 objects to protect
Found 2 commit-pointing refs : HEAD, refs/heads/main
Protected commits
-----------------
These are your protected commits, and so their contents will NOT be altered:
* commit 2357094b (protected by 'HEAD')
Cleaning
--------
Found 2 commits
Cleaning commits: 100% (2/2)
Cleaning commits completed in 25 ms.
Updating 1 Ref
--------------
Ref Before After
-------------------------------------
refs/heads/main | 2357094b | f5a165e6
Updating references: 100% (1/1)
...Ref update completed in 27 ms.
Commit Tree-Dirt History
------------------------
Earliest Latest
| |
D m
D = dirty commits (file tree fixed)
m = modified commits (commit message or parents changed)
. = clean commits (no changes to file tree)
Before After
-------------------------------------------
First modified commit | 06de1b9c | 371366ef
Last dirty commit | 06de1b9c | 371366ef
Deleted files
-------------
Filename Git id
-----------------------------
secret.txt | ad366d9e (12 B )
In total, 3 object ids were changed. Full details are logged here:
/Users/hoge/dev/bfg-sample.git.bfg-report/2023-07-26/13-29-27
BFG run is complete! When ready, run: git reflog expire --expire=now --all && git gc --prune=now --aggressive
$ cd bfg-sample.git
$ git reflog expire --expire=now --all && git gc --prune=now --aggressive
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), done.
Building bitmaps: 100% (2/2), done.
Total 3 (delta 1), reused 1 (delta 0), pack-reused 0
$ git push --force
Enumerating objects: 3, done.
Writing objects: 100% (3/3), 252 bytes | 252.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 3
remote: Resolving deltas: 100% (1/1), done.
To github.com:nyshk97/bfg-sample.git
+ 2357094...f5a165e main -> main (forced update)
該当のCommitを見ても、ファイルが見れなくなっている。

ミラーリポジトリについて
bfg --delete-files secret.txt bfg-sample.git や git reflog expire --expire=now --all && git gc --prune=now --aggressive
などの操作は、開発時に触っていたbfg-sampleではなく、git clone --mirror git@github.com:nyshk97/bfg-sample.gitでcloneしたミラーリポジトリbfg-sample.gitに対して行っている。
これは、ミラーリポジトリは全てのブランチ、タグ、履歴を含んでいるから。
通常のリポジトリは、たとえばmasterブランチと自分が作業したことのあるブランチの情報しか持ってない。
reflogエントリを期限切れにすることについて
過去にgitに載せてしまって機密情報を見れなくするためには、git reflog expireコマンドで過去の全てのreflog情報を削除する必要がある。
過去の全てのreflog情報を削除することで発生しうる問題は以下の2つ。
- リポジトリの全体的な履歴が失われ、過去の状態への復元が難しくなる
- 他のエンジニアが今やってる作業がうまくマージできなくなる可能性がある
チーム開発をしている場合の進め方
1. 機密情報が載っているファイルを削除する
該当のファイルを削除→commit→push→mergeする。
ファイルを削除しても、特定のファイルが含まれるcommitを見ると中身が見えてしまうことを確認する。
2. 全体ミーティングのスケジューリング
他のエンジニアのカレンダー等を見て、全員が集まれる日時でweb会議の予定を立てる
3. 状況の説明
- 機密情報がGitHubにアップされてしまっている
- 機密情報をただ消しただけでは、gitの履歴を辿れば見れてしまうので、gitの履歴から消す必要がある
- BFG Repo-Cleanerというツールを使って、gitの履歴から機密情報を完全に削除する予定
- 上記のツールを使うことで、以下の問題が発生する
- 今あるPRがうまくマージできなくなる可能性がある
- 修正前のdevelopブランチから派生したブランチの修正が、修正後のdevelopブランチにマージできなくなる可能性がある
- だから以下をやりたい
- BFG Repo-Cleanerを使うタイミングを決める
- やり途中の大きな修正がないタイミングで実行したい
- BFG Repo-Cleanerを使う前に、既存のPRを全てMerge or Closeする
- BFG Repo-Cleanerを実行する
- 実行後、各エンジニアは再度cloneリポジトリをcloneし直して、開発環境を作り直す
- BFG Repo-Cleanerを使うタイミングを決める
$ mv app-name app-name-old
$ git clone git@github.com:user-name/app-name.git
# cloneできたら、READMEを参考に環境構築をやり直す。
4. 実施
STEP3で決めた実施日に、実行する。
BFG Repo-Cleanerを実行する手順は以下。
$ git clone --mirror git@github.com:organization/app-name.git
$ bfg --delete-files hoge.txt app-name.git
$ cd app-name.git
$ git push --force
GitHubにアクセスして、該当のファイルを削除したcommit履歴にアクセスし、中身が見れなくなっていることを確認する。
5. 代わりとなるファイルを作成
機密情報が載ってない版の、似たようなファイルを作ってcommit→push→mergeする