幾年前,GitHub Security 發起一項計畫,評估並改善整體祕密管理衛生。作為該計畫的一部分,我們試用了當時正在開發中的祕密掃描功能。就在那時,我們發現超過 20,000 個祕密散布在 15,000 多個存放庫中。

這個數字遠高於我們的預期,但我們很快明白,成功的關鍵在於辨識哪些警示代表真正的風險、指派負責人,並安全地修復它們。九個月後,我們將未解決警示降至零。

許多新使用祕密掃描的客戶常問我們:「你們在內部是如何管理的?實際上是如何清理現有祕密的?」

與許多長期營運的軟體公司一樣,GitHub 的祕密管理方式隨著時間演進。GitHub 成立於 2008 年,當時業界還不普及集中式金鑰保存庫、自動化祕密掃描,以及專門的祕密管理平台。隨著工程實務成熟以及 GitHub 規模擴大,我們持續投資更強大的控管措施、更好的工具,以及針對遺留模式的系統性風險降低。這項工作反映了我們持續致力於改善安全性、減少暴露,並確保內部實務符合業界所期望的高標準。

本篇文章將分享我們在此過程中有效的做法,並強調你可以應用在自己祕密保護上的策略。

消除雜訊

我們首先發現,警示數量有點誤導——也就是說,20,000 則警示並不代表有 20,000 個同等風險的問題。

深入檢視資料後,我們發現僅五個存放庫就佔了約 18,000 則警示,而且這些祕密全部都已停用:測試資料、已停用的憑證,以及用於測試的看似有效卻是假造的祕密。(我們在開發祕密掃描,因此自然會有存放庫裡充滿測試用的看似合法的祕密。)

這剩下超過 2,000 則警示需要處理:潛在的即時憑證,以及數千個關於風險、輪替與修復的決策。

祕密不只存在於程式碼中

祕密修復不只涉及原始碼。我們在支援工單(客戶偶爾會附上權杖)、漏洞懸賞報告(研究人員揭露他們發現的內容,並附上完整重現步驟,包括使用權杖的 API 請求)、事件紀錄,以及 wiki 頁面中都找到了祕密。

我們與客戶支援、安全事件應變團隊,以及漏洞懸賞計畫合作,制定共用的處理手冊。在所有這些工作流程中,我們必須確保不會製造新問題,例如開立包含我們正試圖修復的祕密的 issue,或推送包含這些祕密的 commit。

我們的階段性做法

我們不可能只靠幾位安全工程師逐一處理,就關閉 20,000 則警示。我們將這件事視為任何其他營運待辦事項:先停止產生新債務,再以可重複、可衡量且不依賴單一人員知識的工作流程,逐步處理已存在的項目。

階段 1:全面啟用,停止累積

在清理現有祕密之前,我們必須先阻止新祕密繼續累積。

我們在所有企業與組織中啟用祕密掃描與推送保護。感謝 GitHub Advanced Security 的組織層級設定,這不需要在 15,000 個存放庫中逐一操作。我們強制執行該設定,讓個別存放庫與團隊無法自行退出。

推送保護在源頭阻擋新祕密,避免待辦事項成長速度超過我們能清除的速度。

階段 2:了解並分類

我們依照存放庫、祕密類型與年限將 20,000 多則警示分類,以便區分雜訊與實際工作。

深入檢視後,我們發現僅五個存放庫就佔了約 18,000 則警示,而且這些祕密全部都已停用:測試資料、已停用的憑證,以及用於測試的看似有效卻是假造的祕密。(我們在開發祕密掃描,因此自然會有存放庫裡充滿測試用的看似合法的祕密。)

對於高數量、低風險的警示,我們制定了大量關閉的標準。如果祕密位於專門的測試存放庫、從未使用過,且符合已知的測試模式,我們就能放心地標記為已解決。我們在幾天內就關閉了約 18,000 則警示。

艱難的問題

我們必須就如何修復祕密做出策略性決策。當祕密存在於 issue 中時,你應該編輯內文(可能會移除修訂歷史),還是保留稽核軌跡?當祕密已提交至存放庫時,你應該重寫 git 歷史嗎?任何曾試圖大規模重寫 git 歷史的人都知道接下來會發生什麼:強制推送會破壞開啟中的 pull request、使 commit SHA 失效,並中斷開發人員的工作。

常見的問題是:「如果存放庫已不再使用,我們可以直接刪除它嗎?」我們的答案通常是否定的。刪除存放庫會一併帶走其稽核軌跡。如果該存放庫中的祕密曾被洩露,或存放庫曾遭入侵,你就會失去事件應變時所需的鑑識紀錄。請先輪替祕密,必要時封存存放庫,但保留歷史紀錄。

只要有可能,我們會先輪替或撤銷已暴露的祕密。更困難的問題是,剩餘風險是否足以證明需要重寫 git 歷史,或是歷史中已撤銷的祕密是否可以安全地保留。這些就是產品安全團隊在處理每則警示時所面臨的問題與決策類型。

階段 3:驗證哪些是實際有效的

存放庫中的憑證可能在幾年前就已輪替,也可能仍能存取生產系統。如果你不知道兩者的差異,就無法進行優先排序。

當時祕密掃描尚未內建有效性檢查,因此我們自行建置了方法。目標很明確:判斷憑證是否仍然有效,並在適當時收集足夠的中繼資料,以便將警示分派或通知正確的負責人。

例如,針對 GitHub 權杖,一個具代表性的檢查可以是向低影響力的端點(如 GET /user)發出單一已驗證的請求:

response="$( 
  curl -sS -w '\n%{http_code}' \ 
    -H "Authorization: Bearer $TOKEN" \ 
    -H "Accept: application/vnd.github+json" \ 
    -H "X-GitHub-Api-Version: 2022-11-28" \ 
    https://api.github.com/user 
)" 

status="${response##*$'\n'}" 
body="${response%$'\n'*}" 

case "$status" in 
  200) 
    login="$(jq -r '.login // empty' <<< "$body")" 
    echo "token appears active for GitHub user: $login" 
    ;; 
  401) 
    echo "token appears invalid or revoked" 
    ;; 
  403|429) 
    echo "unable to determine validity; rate-limited or blocked" 
    ;; 
  *) 
    echo "unable to determine validity: HTTP $status" 
    ;; 
esac 

請記住,我們的目標是回答最少但有用的問題:這個憑證是否仍然有效,以及誰需要知道這件事?我們將模稜兩可的回應視為無法判斷,並避免對存放庫、組織或其他私人資源發出後續請求。

這需要與我們的隱私與法務團隊密切合作。即使是「唯讀」的有效性檢查,當你接觸到可能不屬於你的憑證時,也可能產生影響。

當我們手動執行這項工作時,產品團隊已將此解決方案內建至產品中,使剩餘工作得以大幅加速。有效性檢查現已內建於 GitHub 祕密掃描中。

階段 4:找出誰擁有什麼

這項跨職能合作也暴露出所有權問題:即使我們知道憑證仍然有效,我們仍然必須找出誰可以輪替它。

我們與客戶支援、安全事件應變團隊,以及漏洞懸賞計畫合作,為在程式碼之外回報的祕密制定共用的處理手冊。這包括在將工作分派給團隊前先遮蔽祕密值、判斷憑證屬於 GitHub 或客戶,並通知受影響的客戶或研究人員,讓他們輪替自己控制的權杖。在所有這些工作流程中,我們必須確保不會製造新問題,例如開立包含我們正試圖修復的祕密的 issue,或推送包含這些祕密的 commit。

對於 GitHub 發行的憑證(如個人存取權杖),我們與產品團隊合作,直接在警示中顯示祕密中繼資料:誰建立了權杖、何時建立,以及權杖的權限範圍。這意味著我們不需要使用權杖本身來判斷它屬於誰。

對於其他情況,所有權問題更為困難,這也暴露出更深層的問題:並非所有存放庫都有明確的負責人。

我們的內部工程標準(Engineering Fundamentals 計畫)要求服務必須有持久的所有權,我們也維護服務與存放庫之間的對應關係,但並非所有存放庫都能清楚對應到服務。我們遇到的困難促使我們啟動更廣泛的存放庫所有權計畫(使用 GitHub 的 Custom Properties),以及一項平行計畫,確保我們憑證管理員中的所有祕密都有持久的負責人。如果你找不到負責人,就無法輪替祕密。

階段 5:針對長尾警示進行人工分類

即使有驗證與中繼資料,仍有長尾警示需要人工判斷。針對每一則警示:它能存取什麼、是否已輪替、連接系統的負責人是誰,以及修復路徑是什麼?

對於每一則我們駁回的警示,我們都會確保記錄正確的處置方式(例如:已撤銷、用於測試、誤判),並附上包含相關脈絡的評論,例如修復 issue 的連結或已核准的安全例外。

這個階段需要跨團隊密切合作,以識別系統負責人、驗證修復狀態,並在自動化訊號不足時評估剩餘風險。

階段 6:系統化並建立問責機制

隨著模式逐漸浮現,我們讓這項工作變得可擴展:

  • 我們將警示導向內部 漏洞管理平台,以進行集中追蹤與報告。
  • 不同憑證需要不同的修復步驟。我們依照祕密類型撰寫處理手冊,讓團隊能夠自行處理。
  • 我們自動化通知機制,依據存放庫所有權將警示路由給正確的團隊。

最後一項是問責機制。我們將祕密修復與 GitHub 的 Engineering Fundamentals 計畫連結,讓它成為一項安全基本要求,並以此衡量各團隊。我們設定清楚的期望值,並讓團隊能看到狀態。當祕密衛生成為衡量工程健康度的一部分時,它就成為整個組織的共同責任。

我們開始九個月後,達到了收件匣歸零。

經驗分享

  1. 不要被數字嚇到。我們最初的警示數量超過 20,000 則,但其中 90% 並非有效警示。原始數字幾乎從來不是真正的工作範圍。
  2. 全面啟用並強制執行,絕不例外。部分推行會產生盲點。我們在企業層級啟用並強制執行祕密掃描與推送保護,不允許任何人退出。
  3. 在升級前先驗證。並非每一個偵測到的祕密都是有效的。驗證有助於建立優先待辦清單。
  4. 中繼資料能省下數小時。對於 GitHub 憑證,祕密中繼資料大幅減少了調查工作。如果你與第三方供應商合作,請推動他們提供類似的中繼資料,或自行建置豐富化層。
  5. 沒有所有權就無法修復。及早投資持久的所有權基礎架構。
  6. 偵測後自動化工作流程。偵測只是起點,營運挑戰在於路由警示、追蹤負責人,以及完成閉環。投資於工作流程層。
  7. 讓這成為每個人的問題。安全團隊無法獨自修復數千則警示。我們將祕密衛生與 Engineering Fundamentals 計畫連結。當領導階層關注儀表板時,團隊就會找出時間來修復問題。
  8. 記錄你的決策架構。你會遇到沒有乾淨修復路徑的祕密。請記錄你的決策方式:什麼時候輪替就足夠?什麼時候需要重寫歷史?什麼時候可以接受剩餘風險?

這對你有什麼意義

你不需要重新發明我們建立的大部分內容。我們的許多手動因應措施,包括有效性檢查、所有權識別,以及大量分類,現在都已成為祕密掃描的原生功能。

如果你今天開始:

  • 全面啟用並強制執行祕密掃描以及 推送保護
  • 依照存放庫與祕密類型分類待辦事項;大量關閉你能證明是雜訊的警示。
  • 在升級前先驗證哪些是有效的。
  • 將警示路由給負責人,並像其他工程工作一樣追蹤修復進度。

準備好開始了嗎?了解如何使用 GitHub Advanced Security 啟用祕密掃描與推送保護

即將推出:我們如何大規模解決存放庫所有權問題,以及為什麼存放庫與祕密的持久所有權是一切其他事情的基礎。