徹底清除 Git 歷史中的敏感資訊
不小心將 API 金鑰或密碼提交到 Git?只在最新版本刪除是沒有用的,它們依然存在於歷史紀錄中。本指南將引導您使用 git-filter-repo,安全地重寫歷史,徹底銷毀證據。
第一步:事前準備
這是整個流程中最關鍵的環節。若準備不周,可能會導致團隊成員的程式碼遺失或發生嚴重衝突。請務必依序完成以下所有準備工作。
步驟 0:立即緊急應對 (最重要!)
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 Scanning 和 Push 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` 的提交雜湊值。即使您的儲存庫已完成垃圾回收,這些事件紀錄依然存在。
安全第一原則:先撤銷憑證,再清理歷史。一旦秘密被推送,就必須假設它已完全外洩。清理歷史只是補救措施,無法取代汰換憑證的必要性。