徹底清除 Git 歷史中的敏感資訊

不小心將 API 金鑰或密碼提交到 Git?只在最新版本刪除是沒有用的,它們依然存在於歷史紀錄中。本指南將引導您使用 git-filter-repo,安全地重寫歷史,徹底銷毀證據。

第一步:事前準備

這是整個流程中最關鍵的環節。若準備不周,可能會導致團隊成員的程式碼遺失或發生嚴重衝突。請務必依序完成以下所有準備工作。

步驟 0:立即緊急應對 (最重要!)

1.
撤銷/汰換憑證: 對於已曝露的 API key、密碼、token 等,立即到對應的服務平台將它們停用或重設。這是唯一能真正阻斷風險的步驟。
2.
暫停部署: 如果您的 CI/CD 流程會自動取用這些憑證,請立刻暫停,避免自動化系統繼續使用外洩的憑證造成更大損害。

1.1 安裝工具

git-filter-repo 是由 Python 3 開發的工具,請先確保您的環境已安裝 Python 3。然後,使用 pip 進行安裝。

pip install git-filter-repo

提示:`git-filter-repo` 是 Git 官方推薦用來取代舊有 `git filter-branch` 和 BFG 等工具的現代化選擇。

1.2 建立全新的本地副本

為了避免本地端舊有的分支、快取或未追蹤的檔案干擾清理過程,強烈建議在一個全新的資料夾中,重新clone一份儲存庫來進行操作。

git clone --mirror [GIT_SERVER]:YOUR-ORG/YOUR-REPO.git
cd YOUR-REPO.git

使用 --mirror 參數可以確保所有遠端參考(包括分支、標籤等)都被完整複製下來。

1.3 團隊協作準備工作 (互動式清單)

在開始清理作業前,請務必完成以下協調,並勾選確認。

第二步:執行清理作業

準備工作就緒後,就可以開始執行清除敏感資訊的流程。核心步驟是建立一個「替換規則」檔案,然後執行 `git-filter-repo` 指令。

2.1 建立「替換規則」檔案

在儲存庫以外的地方,建立一個純文字檔,例如 `secrets.txt`。在此檔案中,定義要如何替換找到的敏感資訊。格式為 `舊的敏感資訊==>新的取代內容`。每一行代表一條替換規則。

策略一:保留標記

將敏感字串替換成一個有意義的標示,讓開發者知道這裡曾有密鑰已被移除。

aBcDeFgHiJkLMnOp==>SECRET_REMOVED
ghIjkLmnOpQrStUv==>API_KEY_REMOVED
策略二:完全移除

將敏感字串直接替換成空字串,讓它從程式碼中徹底消失。

aBcDeFgHiJkLMnOp==>
ghIjkLmnOpQrStUv==>

提示:可以使用如 GGShield 等工具掃描整個儲存庫,找出所有外洩的 Token 或密鑰,以建立完整的規則檔案。

2.2 執行 git-filter-repo 指令

切換到你新 clone 的鏡像儲存庫資料夾路徑下 (`YOUR-REPO.git`),執行以下指令。請將 `[替換規則檔案的路徑]` 替換成你上一步建立的 `secrets.txt` 的實際路徑。

git filter-repo --replace-text [替換規則檔案的路徑]

指令執行後,工具會快速掃描並重寫所有 commit,將符合規則的敏感資訊替換掉。它會自動處理備份,比舊工具有更高的安全性。

第三步:完成與推送

本地歷史重寫完成後,最後一步就是將這個全新的、乾淨的歷史強制推送到遠端儲存庫,並處理殘留的風險。

3.1 強制推送到遠端

警告:這是一個破壞性操作。由於整個 Git 歷史已被重寫,必須使用「強制推送 (force push)」才能更新遠端的儲存庫。這會永久覆蓋遠端的歷史。請務必確認本地修改無誤後再執行。

將乾淨的儲存庫推送到主儲存庫

git remote add origin [GIT_SERVER]:YOUR-ORG/YOUR-REPO.git
git push --force --all origin
git push --force --tags origin

3.2 處理 Forks 與後端殘留

強制推送主儲存庫後,還需要處理潛在的副本和伺服器殘留:

  • 清理 Forks: 聯絡所有 `Fork` 的擁有者,請他們執行同樣的歷史重寫流程,或是直接刪除並重新 `Fork` 乾淨的儲存庫。這是防止舊歷史被意外合併回來的關鍵。
  • 請求後端垃圾回收 (可選但建議): 聯絡您的 Git 服務商 (如 GitHub Support),提出「敏感資料刪除請求」。提供您的儲存庫 URL 和相關 commit SHA,並說明您已完成歷史重寫,請求他們手動觸發伺服器端的垃圾回收,以盡快清除懸空提交

第四步:後續防範

恭喜!您已完成所有必要的清理步驟。最後,請完成後續協調並建立防護機制,避免未來再犯。

4.1 通知團隊並重新 Clone

這是至關重要的一步。由於遠端歷史已被重寫,所有團隊成員都必須

  • 刪除他們各自電腦上的舊儲存庫副本。
  • 重新 `git clone` 一份最新的版本來繼續開發工作。
  • 若之前有使用 `git stash` 暫存工作,現在可以在新的副本中用 `git stash pop` 來還原。

4.2 建立自動化防護

為了避免未來再次發生同樣的錯誤,強烈建議在團隊的工作流程中導入自動化掃描機制:

  • 啟用平台防護: 在 GitHub/GitLab 等平台上啟用 Secret ScanningPush Protection 功能。
  • 導入 Pre-commit Hook 在開發者本地端就攔截包含敏感資訊的提交。
  • 主分支保護: 設定分支保護規則,禁止對主要分支進行 `force push`。

補充說明:為何 Force Push 並不足夠?Git 的記憶比你想像的更長久

許多人認為 `git push --force` 可以「抹掉」錯誤的提交,但這是一個危險的誤解。Git 的設計使得被移除的提交並不會立即消失,它們會變成「懸空提交」(Dangling Commit),在伺服器(如 GitHub)上殘留數週甚至更久,等待垃圾回收 (Garbage Collection) 機制清理。

在它們被徹底清除前,這些敏感資料仍可能透過以下途徑被存取:

  • 分支 (Forks): 如果有人在您強制推送前 Fork 了您的儲存庫,舊的提交歷史會永久保存在他的副本中。
  • 直接網址: 只要知道提交的 SHA-1 雜湊值,任何人都可以透過 `https://github.com/<user>/<repo>/commit/<hash>` 的網址直接存取該次提交的內容。
  • 公開事件紀錄:GH Archive 這類服務會公開蒐集 GitHub 的事件資料,其中就包含 `PushEvent` 的提交雜湊值。即使您的儲存庫已完成垃圾回收,這些事件紀錄依然存在。

安全第一原則:先撤銷憑證,再清理歷史。一旦秘密被推送,就必須假設它已完全外洩。清理歷史只是補救措施,無法取代汰換憑證的必要性。