force-push しても GitHub に残る dangling commit と特定リスク
まとめ
git push --forceで書き換えた旧 commit は GitHub 上に約 90 日残る(dangling commit)。SHA を直接指定すれば誰でも閲覧可能- private repo であってもリポジトリ所有者にアクセス権がある人なら旧 commit URL から内容を読める
- 完全に消したいなら (1) GitHub のガベージコレクション待ち (2) サポート申請 (3) repo を delete → 再作成 のいずれか
- 外部攻撃者からの SHA 特定は実質不可能。SHA 漏洩経路(CI ログ・shell history・webhook 等)の方が現実的なリスク
何が起きるか
リポジトリに会社固有のドメインを誤って含めて push してしまい、後から修正コミットを重ねた後、最終的に git checkout --orphan で履歴を 1 commit に潰して force-push した。
ローカルでは:
git log --all -p -S "jccapital" | wc -l
# → 0完全にクリーン。だが GitHub 側では旧 commit SHA を直接指定すれば commit 内容が見える状態が残る:
https://github.com/<owner>/<repo>/commit/<old-sha>これが dangling commit。どの branch・tag・PR からも到達不能なのに、SHA 経由で実体が残っている状態。
なぜ GitHub では消えないか
ローカル git では git gc を走らせれば dangling object は削除される。デフォルトでは reflog の保持期間(90 日)が経過するまでは保持されるが、git reflog expire --expire=now --all && git gc --prune=now で即時削除できる。
しかし GitHub は force-push 後も独自に旧 object を保持する。理由はおそらく:
- 誤って force-push したケースのリカバリ
- PR・issue・notification 等で参照されている SHA への解決保証
- Forks(フォーク先で旧 commit が参照されている場合)
実体は GitHub のガベージコレクションが約 90 日後に走るまで残り続ける。
特定リスクの分解
旧 SHA を完全に知らない第三者が dangling commit にたどり着く現実的な経路を整理する。
SHA エントロピーで防げる範囲
| 項目 | 値 |
|---|---|
| 完全 SHA | 40 hex 文字 = 160 bit |
| GitHub URL で受理される最短 | 4 文字(衝突しない範囲で) |
| 短縮 7 文字の総当たり | 約 2.7 億通り |
private repo は GitHub 検索インデックスに載らないので、SHA を知らない外部攻撃者にとって特定は事実上不可能。
実際にあり得る漏洩経路
| 経路 | 大きさ |
|---|---|
| CI/CD ログ(GitHub Actions の各 step 出力に SHA が混入) | 中 |
| Webhook 連携先(Slack / Datadog / 監査基盤 等)の保管ログ | 中 |
ローカルの shell history (~/.zsh_history) | 小 |
他端末からの clone(.git/objects に旧 commit が残っている) | 状況依存 |
アカウント侵害経由の git reflog 参照 | 小 |
逆に言うと、外部に SHA を流していない・他端末 clone がない・侵害がない限り、外部からの特定はゼロに近い。
確実に消す方法
| 方法 | 即時性 | 手間 |
|---|---|---|
| GitHub の自動 GC を待つ | 〜90 日 | 0 |
| GitHub Support に明示申請して GC を早める | 数日 | サポート問い合わせ |
| repo を delete → 同名で再作成 → push | 即時 | clone 状況の確認のみ |
private repo で他に clone した人が確実にいなければ、delete → 再作成が最速かつ最も確実。force-push の数十秒後にすべて消える。
教訓
- force-push は「ローカル履歴」を書き換えるだけで、「リモートに到達した過去の commit」は別物として残る
- 漏れたら困る情報は最初から commit しない(言うは易し)
- やってしまった時の判断軸は「private か」「外部経路で SHA が流れたか」「許容できる秘匿性か」の 3 つ
- 一律「force-push で消える」と思い込まないこと
GitHub の dangling commit はバグではなく仕様。書き換え後の commit が誰かの notification に残っている可能性を救うための設計だと思えば筋は通っている。が、秘密を消す目的で force-push を使うと期待が外れるので、用途に応じて消去方法を選ぶ必要がある。