症狀

自建 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 上觀察過。)

修法

  1. 讓 CI 留下看得懂的報告。 原本 CI 只印「找到 3 筆」,無從處理。加上 --report-format json --report-path gitleaks-report.json,失敗時存成 artifact。因為已經開了 --redact,報告裡的值是遮掉的,報告本身不含憑證。
  2. 從報告拿到規則、檔案、commit,用「只看結構」的方式判斷。 解碼後跟佔位字樣比對,只輸出「像不像佔位、長度多少」,值本身不印出來。最後再人工確認是佔位值。
  3. 用 .gitleaksignore 依指紋逐筆放行,每筆寫理由。 指紋格式是 <commit>:<file>:<rule>:<line>,README 說明每筆發現都有一個唯一的 fingerprint,加進 .gitleaksignore 就只忽略那一筆(同一段也註明這個功能是 experimental)(gitleaks README)。不整檔豁免,也不改寫歷史。
  4. 在主 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 第一次實跑挖出的其他舊帳