症狀
自建 runner 第一次把 CI 完整跑起來,gitleaks job 報了 3 筆洩漏。可是本機的掃描指令一直是綠的,用同樣參數在本機重現,也還是綠的。
秘密掃描最怕的就是這種狀況:兩邊結論不一樣,你不知道該信哪一邊。如果信了本機的綠,CI 那 3 筆就會被當成誤報忽略掉。
原因
本機綠、CI 紅,原因有兩層。
第一層:兩邊掃的範圍根本不同
- 本機的掃描指令都用了
--no-git,只掃目前目錄裡的檔案。 - CI 用
--log-opts=--all掃整個 git 歷史。 - 這 3 筆在已經刪除的檔案裡,現在的工作目錄看不到,只存在歷史中。
這是「從工作目錄刪掉的秘密,其實還留在歷史裡」最直接的例子。
官方 README 對兩種模式的描述(以 v8.18.4 版本的 README 為準):--no-git 是「scan files and directories」;--log-opts 則是設定 git log 要掃過哪些 commit,接受任何 git log -p 的選項(gitleaks v8.18.4 README)。
補充:新版 gitleaks 已經把指令改成 git、dir、stdin 三種掃描模式,detect/protect 從 v8.19.0 起標為 deprecated,但仍可使用(gitleaks README)。新寫法裡,dir 對應原本的 detect --no-git,git 對應掃歷史。
第二層:重現時又踩到假綠
我在 git worktree 裡用 docker run -v $PWD:/w 跑 git 模式,想在本機重現 CI 的結果。
- worktree 的
.git只是一個指回主 checkout 的檔案,容器只掛了 worktree 目錄,讀不到真正的 git 資料。 - gitleaks 印出
ERR [git] fatal: not a git repository,接著照樣印INF no leaks found,看起來就像通過。 - 同一條指令改在主 checkout 掛載,就掃到 54 個 commit、找到 3 筆,跟 CI 一致。
這次觀察到的版本是 v8.21.2。官方 README 寫的 exit code 是「0 - no leaks present」「1 - leaks or error encountered」(gitleaks README),照字面看,讀 git 出錯應該回 1。(未查證:這次 exit code 是否真的是 0、以及這算不算預期行為,我沒有到 issue tracker 查到對應回報,只在 v8.21.2 上觀察過。)
修法
- 讓 CI 留下看得懂的報告。 原本 CI 只印「找到 3 筆」,無從處理。加上
--report-format json --report-path gitleaks-report.json,失敗時存成 artifact。因為已經開了--redact,報告裡的值是遮掉的,報告本身不含憑證。 - 從報告拿到規則、檔案、commit,用「只看結構」的方式判斷。 解碼後跟佔位字樣比對,只輸出「像不像佔位、長度多少」,值本身不印出來。最後再人工確認是佔位值。
- 用
.gitleaksignore依指紋逐筆放行,每筆寫理由。 指紋格式是<commit>:<file>:<rule>:<line>,README 說明每筆發現都有一個唯一的 fingerprint,加進.gitleaksignore就只忽略那一筆(同一段也註明這個功能是 experimental)(gitleaks README)。不整檔豁免,也不改寫歷史。 - 在主 checkout 用全歷史重掃,確認是 0。
為什麼這樣解
本機維持 --no-git 是對的。 pre-commit 和本機掃描要快,守的是「這次要進版控的東西」;掃歷史交給 CI。兩者分工沒問題,但要清楚它們量的不是同一件事,不能拿本機的綠去否定 CI 的紅。
指紋放行,而不是規則或路徑放行。 規則或路徑放行會把未來同類的真洩漏一起放掉,指紋只對應那一筆。
不改寫歷史。 這次的值是佔位,改寫主幹歷史的成本遠大於收益。如果是真憑證,順序一定是先輪替、再談要不要清歷史——輪替才是真正讓外洩失效的動作,清歷史只是善後。
怎麼預防再次發生
- 在 worktree 裡用容器跑任何讀 git 的工具(gitleaks git 模式、
git log類分析),要一起掛載主 checkout 的.git(用git rev-parse --git-common-dir找路徑),或乾脆到主 checkout 跑。 - 輸出裡出現 ERR,就不採信同一次輸出的結論,不管最後一行寫什麼。
- CI 的掃描 job 一律產出「失敗時看得懂的報告」,不能只有一個數字。
- 任何 repo 第一次接上全歷史掃描時,都可能冒出歷史裡的舊憑證。先準備好流程:報告 → 只看結構判斷 → 人工確認 → 指紋放行或輪替。
相關
- 資安工具概觀:secret 掃描工具(gitleaks、TruffleHog 等)在整體資安工具地圖裡的位置
- 相關主題:自建 GitLab runner(Kubernetes executor)第一次上線時,CI 第一次實跑挖出的其他舊帳