Gitに機密情報をコミットしてしまったときの対処法

サーバーの秘密鍵やAPI呼び出しのトークンといった機密情報を間違ってGit commitに入れてしまったとき、どうしたらよいのかまとめました。原則として、機密情報はcommitに入れてはいけません。`.gitignore`を活用して、commitに絶対に含まれない仕組みづくりが大切です。


でも、新規開発の時など開発環境があわただしいときは、ふとした瞬間に機密情報がcommitに紛れ込んでしまう場合もなくはないですよね。。。

## 結論
- ローカルレポジトリへのcommitだけなら、commitへの参照をすべて消去して`git gc`することでcommitを消去できます。 => [対処法3](#%E5%AF%BE%E5%87%A6%E6%B3%953-rebase--gc-%E9%9D%A2%E5%80%92%E3%81%A0%E3%81%91%E3%81%A9%E7%A2%BA%E5%AE%9F)
- GitHubにpushしてしまった後なら、GitHubのサポートに連絡して消してもらうしかないです。ただ、それ以外にも善後策はあります。 => [GitHubにpush済みの場合](#github%E3%81%ABpush%E6%B8%88%E3%81%BF%E3%81%AE%E5%A0%B4%E5%90%88)

## 対処法１: revert (おすすめしませんが簡単です)
`git revert`コマンドをつかってcommitを打ち消します。こうすることで、「見かけ上は」機密情報を入れてしまったcommitをなかったことにできます。

```
git revert <commit>
```

「見かけ上」といったのは、commit履歴に機密情報commitが残ってしまうため、後からいくらでも機密情報を参照できてしまうからです。

例えば、コード追加 -> 機密情報追加 -> テスト追加の順番でcommitをした後、機密情報追加のcommitをrevertで打ち消すことを考えます。
```
$ git commit -m "add code"
$ git commit -m "add secret"
$ git commit -m "add test"

$ git revert HEAD~
```

すると、機密情報はブランチから削除することができますが、commit履歴にはしっかりと残ってしまいます。

```
$ git log --oneline

427d14d Revert "add cred"
0b17cbe add test
8476fcd add cred
87996b0 add code
```

```mermaid
gitGraph
   commit id: "add code"
   commit id: "add cred"
   commit id: "add test"
   commit id: "Revert add cred"
```

そのため、機密情報commitを指定してcheckoutすれば機密情報を見ることができてしまいます。

```
$ git checkout 8476fcd
$ cat <credential> # => 機密情報が表示される！！！
```

なんだかCTFの問題にでてきそうな状況ですね。。。

## 対処法２: rebase (まあ悪くないです)
rebaseを使ってcommit履歴を書き換えて機密情報commitを削除しましょう。

```
$ git commit -m "add code"
$ git commit -m "add secret"
$ git commit -m "add test"

$ git rebase -i HEAD~2
```

`git rebase`に`-i`オプションをつけると、rebaseがインタラクティブモードで起動します。

```
pick 8476fcd add cred
pick 0b17cbe add test
```

今回は`8476fcd`のcommitを削除したいので、この部分のpickをdropに変えます。

```
drop 8476fcd add cred
pick 0b17cbe add test
```

すると、commit履歴から機密情報commitが削除されます。

```
$ git log --oneline

41b8d7c add test
87996b0 add code
```

万事OKかと思いきや、実はcommit hashを使うと機密情報commitにcheckoutができてしまいます。

```
$ git checkout 8476fcd
$ cat <credential> # => 機密情報が表示される
```

なぜかというと、gitはcommit履歴からcommitが削除されても、すぐにはcommitの情報を消去しないからです。これには理由があって、例えば`git reset HEAD~`で間違ってresetしてしまったときなどに、元のcommitに戻るときなどに使われています。

```
$ git reset HEAD~ # 間違えた！！
$ git reflog

41b8d7c (HEAD -> main) HEAD@{0}: reset: moving to HEAD~
58f92da (HEAD -> main) HEAD@{1}: ...

$ git reset HEAD@{1} # => もとのcommitに戻る
```

rebase後のcommmit状況は以下のようになっています。

```mermaid
gitGraph
   commit id: "add code"
   branch dangling
   checkout dangling
   commit id: "add cred"
   commit id: "add test"
   checkout main
   commit id: "add test (new)"
```

rebaseによってmainブランチではコード追加commitの直後にテスト追加commitが移動しています。ただ、機密情報追加commitも情報としては残っていて、ブランチからは参照されない宙ぶらりん(dangling)の状態になっています。

この状態のcommitは、いずれは(通常90日で)gitのgarbage collectionによって消去されるので、このまま放置していてもダメというわけではないです。(参考: [Atlassian Git Tutorial](https://www.atlassian.com/ja/git/tutorials/rewriting-history/git-reflog#:~:text=%E6%97%A2%E5%AE%9A%E3%81%A7%E3%81%AF%E3%80%81%E7%9B%B4%E8%BF%91%E3%81%AE%E6%9C%89%E5%8A%B9,%E6%9C%9F%E9%99%90%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82v))

## 対処法3: rebase + gc (面倒だけど確実)
rebaseにgcを組み合わせて、commitを完全に消去しましょう。

```
$ git rebase -i <commit>
$ git reflog expire --expire=now --all # reflogを消去
$ git gc --aggressive --prune=now # commitを消去
```

gitのgc(garbase collection)は、どこからも参照されていないcommitを消去するための仕組みです。今回は、rebaseでブランチのcommit履歴から削除した機密情報commitを消去するために使います。

`git gc`を実行する前に、reflogを消去する必要があります。というのも、commitへの参照はブランチからの参照のほかにreflogからの参照も含まれるからです。

## GitHubにpush済みの場合
これまで見てきた対処法はローカルブランチで機密情報をcommitしてしまった場合についてのものです。もし、ローカルブランチでcommitした後、GitHubにpushしていると、commitの消去は難しくなります。

手順としては以下の通りです。

1. 対処法2または対処法3を使ってローカルブランチから機密情報を削除します。
1. [GitHub サポート ポータル](https://support.github.com/)に連絡して、GitHubレポジトリから機密情報を削除します。

これで機密情報のcommitを削除できます。とはいえ、一度GitHubにpushした機密情報は、漏洩したものとみなすほうがよいです。機密情報の性質に応じて、漏洩時の処理を行ってください。

GitHubにpushした機密情報がサーバー秘密鍵などだった場合は、鍵の無効化によって漏洩に対処できます。その場合、commitを急いで消去する必要性は低くなるため、GitHub サポート ポータルへの連絡はしなくてもよいかもしれません。

参考: [リポジトリからの機微なデータの削除](https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository#github)