← 返回所有文章
發佈於 · 作者 Renaud Deraison

刪掉的金鑰,還留在歷史裡

10 月 8 日,一個名為 GhostAction 的攻擊行動利用兩個被劫持的維護者帳號,把一個叫 security-audit.yml 的 workflow commit 進 346 個儲存庫,其中包括 Uber 的 athenadriver,以及擁有 1.8 萬顆星的 pyxel。到了 10 月 9 日,Socket 統計的帳號已超過 500 個,儲存庫達數萬個。這個 workflow 會執行 git log -p --all,在有史以來的每一個 commit 裡 grep Anthropic、OpenAI、OpenRouter、GitHub 與 AWS 的金鑰,連下一個 commit 就刪掉的也不放過。在 Bromure Agentic Coding 裡,代理手上永遠只有替身,所以它寫進儲存庫歷史的金鑰,對找到的人毫無價值。

從檔案裡刪掉一把金鑰,只要一個 commit。前一個 commit 會一直保留這把 金鑰,直到儲存庫不存在為止。

10 月 8 日星期四 13:40 UTC,遊戲引擎 pyxel 作者 Takashi Kitao 的 GitHub 帳號開始 commit 一個名為 .github/workflows/security-audit.yml 的檔案。 四分鐘後,它已出現在他的 27 個儲存庫裡。21:10 UTC,Uber athenadriver 的原作者帳號 henrywoo 展開同樣的掃蕩。到 21:26,它已觸及 318 個儲存庫, 其中有 279 個 fork,以及這個帳號擁有寫入權限的 uber/athenadriver 本身。

Socket 在 10 月 9 日發表 分析,把這些 commit 歸於 GhostAction,一個在 2025 年 9 月首次出現的憑證竊取行動。 當天結束前,Socket 的更新已統計出超過 500 個 GitHub 帳號,自 10 月 7 日起 把這個 workflow commit 進「數萬個儲存庫」。The Hacker News 與 StepSecurity 也報導了同一波攻擊。Socket 發現,這個 workflow 已在受影響的 儲存庫裡執行過,而且在文章發表時,仍留在 kitao/pyxel 與 uber/athenadriver 的預設分支上。

security-audit.yml 做了什麼

這個 workflow 叫「Security Audit」。它在 workflow_dispatch 時執行,也在 任何分支或 tag 的每一次 push 時執行,沒有任何篩選。它的 checkout 步驟使用 fetch-depth: 0,會拉下每個分支、每個 tag 和完整的歷史。接著,單一一個 步驟做兩件事。

第一件事是衝著儲存庫的 GitHub Actions 機密而來。攻擊者在 commit 這個 workflow 之前,先讀了儲存庫現有的 workflow,把裡面用到的機密名稱寫進 payload。以 pyxel 來說,就是 CARGO_REGISTRY_TOKEN、 PERSONAL_ACCESS_TOKEN、PYPI_PASSWORD 與 PYPI_USERNAME。

第二件事是這一波新加的。這個步驟先用 13 種憑證模式 grep 工作目錄,再執行 git log -p --all,以同樣的模式 grep 最多 20 萬行歷史:

  • AI 服務: Anthropic(sk-ant-)、OpenAI 專案金鑰(sk-proj-)、 OpenRouter(sk-or-)
  • 版本控制: GitHub 傳統權杖(ghp_)、細粒度權杖(github_pat_)、 GitLab 權杖(glpat-)
  • AWS: 存取金鑰 ID、臨時金鑰 ID、私密金鑰與工作階段權杖。這個步驟還會 保留每個金鑰 ID 前後兩行內容,所以 commit 在旁邊的私密金鑰也會一起帶走。
  • SaaS 與雲端: Google API 金鑰、Slack 權杖、SendGrid 金鑰

所有東西都在一次 curl POST 裡,以未加密的 HTTP 送到 193.32.204.199。 每次執行都會回報儲存庫名稱,不論有沒有找到東西。

Socket 點出了目標族群。henrywoo 的儲存庫中有兩個是 pyllama 與 chatllama,都是機器學習專案,其貢獻者與 fork 的人「很可能持有這類金鑰」。 Anthropic 或 OpenAI 的金鑰可以當成計費的推論轉賣,而透過它送出的每個 prompt,都會經過買家之手。

歷史會留下一切

Socket 的修復建議中有一句話,任何曾經靠編輯檔案來清理外洩金鑰的人都該擔心: 「輪替在任何 commit 中找到的每一個憑證,不論它是否仍在目前的樹裡。」在下一個 commit 刪除的金鑰,仍留在前一個 commit 裡。git log -p --all 兩個都讀得到。

程式碼代理讓這段歷史增長得很快。GitGuardian 的 State of Secrets Sprawl 2026 發現, 由 Claude Code 共同撰寫的公開 commit 洩漏機密的比例為 3.2%,而整個公開 GitHub 的基準是 1.5%。同一份報告統計,2025 年與 AI 服務相關的外洩機密有 1,275,105 筆,比前一年多了 81%。

看過代理讓一個失敗的整合測試通過的人,對這個模式都不陌生。代理的環境裡有 金鑰。它把金鑰寫進設定檔、測試 fixture,或還沒被 ignore 的 .env,因為 這樣測試就會過。它 commit。之後,開發者或代理把金鑰移到環境變數,再 commit 一次。樹看起來很乾淨。歷史裡有金鑰,每個 fork 都有這段歷史,而一個 fetch-depth: 0 的 workflow 會讀它。

寫下 workflow 的那個權杖

Socket 沒有查出這兩個帳號是怎麼被奪走的。The Hacker News 點名最可能的 來源:來自資訊竊取程式日誌或憑證傾印的 personal access token。資訊竊取程式 的日誌來自開發者的電腦。在筆電上,GitHub 權杖放在 ~/.config/gh/hosts.yml、 ~/.git-credentials,也常在 GH_TOKEN 裡,任何以開發者身分執行的東西都讀得 到,包括程式碼代理安裝或執行的任何東西。

一個權杖就夠了。有了它,攻擊者就能列出這個帳號可寫入的儲存庫,讀它們的 workflow 找出機密名稱,再逐一 commit。henrywoo 的掃蕩在 16 分鐘內涵蓋了 318 個儲存庫。

一個被偷的權杖、一個 workflow、每一個 commit維護者的筆電~/.config/gh/hosts.yml~/.git-credentialsGH_TOKEN=ghp_… (真品)竊取程式日誌→ 權杖待售THN 推測的來源security-audit.yml:16 分鐘 318 個on: push (any branch), workflow_dispatchfetch-depth: 0git log -p --all | grep 13 patternsconfig/test.py 的歷史a1f3 + KEY="sk-ant-api03-…" ← 找到c92e - KEY="sk-ant-api03-…" + KEY=os.environ["…"]樹很乾淨,金鑰仍在歷史裡。193.32.204.199POST (明文 HTTP)REPO=…PYPI_PASSWORD=…sk-ant-api03-…AKIA_CTX_START…現存的金鑰與已刪的一樣每個 fork 都帶著同一段歷史。光是 henrywoo 底下,Socket 就數到 279 個 fork。
GhostAction 的攻擊鏈。來自開發者電腦的權杖 commit 了 workflow,workflow 讀取有史以來的每一個 commit,而那段歷史裡的每一把金鑰,不論現存或已刪除,都在一次 POST 中送出。

同一個儲存庫,由 Bromure 機器寫成

在 Bromure Agentic Coding 裡,代理在你 Mac 上的 Linux VM 中執行,VM 發出的 每個 HTTP 請求都會經過主機上的代理伺服器。你的 Anthropic 金鑰、GitHub 權杖和 AWS 金鑰都以加密形式留在 Mac 上。現在,把 GhostAction 放到一個由 Bromure 機器寫成的儲存庫上,以及寫它的開發者身上。

歷史裡存的是替身

在 VM 裡,每個憑證都是外形與真品相同的替身:ANTHROPIC_API_KEY 是 sk-ant-api03-brm-…,OpenRouter 金鑰是 sk-or-v1-brm-…,GH_TOKEN 與 ~/.git-credentials 存的是 ghp_ 加上 36 個字元。每一個都是用 HKDF-SHA256 從真實值與每次安裝各自的 salt 推導而來,無法還原成真實值。代理伺服器在線路上 才把真實值換進去,而且只針對送往該憑證所屬主機的請求。

所以,當代理為了讓測試通過而把金鑰寫進 config/test.py,它寫的是它手上的 東西。它 commit 時,commit 裡是 sk-ant-api03-brm-…。之後清理用的 commit 來了,歷史保留的也是同一個替身。

GhostAction 的 grep 會找到它。sk-ant- 符合 sk-ant-api03-brm-,ghp_ 符合 GitHub 權杖的替身。送往 193.32.204.199 的 POST 會把它們帶走,買家 會拿去試。Anthropic 拒絕這把金鑰,GitHub 拒絕這個權杖,因為唯一能把它們變成 有效憑證的,是你 Mac 上的代理伺服器,而且只針對離開你 VM 的請求。Socket 建議輪替任何 commit 中找到的每一把金鑰,對這個儲存庫來說答案很簡短:這些 金鑰從來沒有在你的 Mac 之外生效過,而 AI 服務商的替身名稱裡帶有 -brm-, 掃描歷史時一眼就能認出。

筆電上的權杖也是替身

這個攻擊行動的另一半是帳號本身。在 Bromure 機器裡執行代理,它們使用的 GitHub 權杖就留在 Mac 上。在 VM 裡執行的任何東西,不論是套件的安裝腳本、 竊取程式還是 prompt injection,在 ~/.git-credentials 與 GH_TOKEN 裡 找到的都是 ghp_ 替身。攻擊者從自己的電腦重放它,只會從 GitHub 拿到 401。

竊取程式還得把拿到的東西送到某個地方,而那個請求會經過代理伺服器。替身只有 一個合法目的地,就是它被產生時所對應的主機。代理伺服器會檢查每個外送請求的 標頭與內容,找出任何要送往別處的替身。一個正要送往竊取程式伺服器的 ghp_ 權杖會拿到 HTTP 451,什麼都不會轉送,VM 暫停,Mac 上會跳出 This environment may have been compromised,顯示權杖預覽、憑證名稱、它所屬的 主機,以及它被送往的主機。

workflow 的 push 要等你點擊

這一系的蠕蟲不一定等買家。上週 tensorlake 的 payload 直接從開發者的電腦把檔案 commit 進受害者的儲存庫。新的 Bromure 機器對每個 git 代管平台都使用 Prompt before write。push 是寫入,所以從 VM 把 security-audit.yml commit 進儲存庫時,會停在一個 macOS 對話框 Allow write on "github.com" from workspace "…"?,並顯示線路上原樣出現的 POST /owner/repo.git/git-receive-pack。henrywoo 的掃蕩在 16 分鐘內做了 318 次這樣的寫入。從 Bromure 機器來,第一次就會等你,而你並沒有要求代理替 318 個儲存庫加上安全稽核。

每次使用權杖都有紀錄

Security Timeline 會把每一次憑證替換記錄成一列 Credential brokering:哪個權杖、哪個主機、哪台機器、什麼時間。當你需要知道你的 GitHub 權杖是否在 10 月 8 日 21:10 到 21:26 之間 push 過任何東西,答案就在你的 Mac 上,在一份代理無法修改的紀錄裡。開啟追蹤時,bromure-cli trace leaks 會列出 每一個權杖出現在錯誤主機上的請求。

在 Bromure 裡:歷史裡是替身,真金鑰從未離開 Mac機器 VM (Ubuntu)GH_TOKEN=ghp_… (替身)ANTHROPIC_API_KEY=sk-ant-api03-brm-…代理的 commit+ KEY="sk-ant-api03-brm-…"竊取程式外送 ghp_ 替身git push security-audit.ymlVM 裡沒有真機密可偷代理伺服器,在 Mac 上只為憑證自己的主機換入真實值替身送往別的主機 → 451,VM 暫停,Mac 上警示git-receive-pack → Prompt before writeSecurity Timeline:每次替換權杖、主機、機器、時間GitHub 歷史sk-ant-api03-brm-…GhostAction 的 grep符合 sk-ant-,將它送出Anthropic: 401GitHub: 401買家拿到的字串在哪裡都無效留在 Mac 上,VM 之外Anthropic、OpenAI、OpenRouter 金鑰 · GitHub 權杖 · AWS 金鑰 · SSH 私鑰 · 追蹤紀錄 · Security Timeline
同一條攻擊鏈對上 Bromure 機器。workflow 讀到的歷史裡是替身,竊取程式在 VM 裡找到的權杖是替身,任何把替身帶往錯誤主機的請求都會在 Mac 上被攔下。

金鑰外洩後的善後

金鑰外洩後的標準建議是:輪替它、用 git filter-repo 改寫歷史、force-push, 然後祈禱沒有任何 fork 或鏡像先複製了舊的 commit。GhostAction 說明了最後這一步 為什麼會失敗。Socket 在單一命名空間裡就數到 279 個 fork,每一個都帶著那段 歷史,現在也帶著那個 workflow。一把金鑰只要進了公開的 commit,就已經回不來了, 不管目前的樹看起來如何。

Bromure 機器改變的,是一開始有什麼會進到 commit 裡。代理對金鑰可以隨便到 GitGuardian 數據所描述的程度。它可以把金鑰貼進 fixture、commit 一個 .env 然後忘了它。它從來沒有拿過一把能用的金鑰,所以那段歷史裡的任何東西都傷不了你。

commit 裡是替身

VM 裡的代理只看得到外形與真金鑰相同的替身。被寫進檔案並 commit 的金鑰, 是一串只有透過你 Mac 上代理伺服器才能用的 -brm- 字串。

收割到的只有 401

GhostAction 的模式會比對到替身並把它們送出。在你的 Mac 之外, Anthropic、OpenAI 與 GitHub 到哪裡都會拒絕它們。

被偷的權杖會讓機器停下

VM 裡的 GitHub 權杖也是替身。送往 GitHub 以外的任何地方,就會拿到 451, VM 暫停,警示會指出權杖與主機。

push 要等你點擊

Prompt before write 會把 VM 發出的每次 push 擋在顯示請求內容的 macOS 對話框,Security Timeline 則記錄每一次權杖的使用。

下一次 grep

這是 GhostAction 十三個月來的第三波。第一波在 2025 年 9 月,拿走了 3,325 個 CI 機密。第二波在今年 8 月 31 日到 9 月 30 日,鎖定 772 個儲存庫中的 2,577 個 機密。這一波加上了完整歷史與三種 AI 服務商的模式。下一波會加入更多模式,而它 讀取的歷史只會越來越長。

讓你的代理在它們碰到的金鑰都是替身的地方執行,它們留下的歷史就不會有任何買家 能用的東西。想進一步了解替身如何運作、外洩時會發生什麼,請閱讀 代理洩漏了自己的金鑰 與 那把金鑰從未過期。