Velocity Curve
預約通話
← 回到案例總表
AGTAGT-02  ·  代理控制

一份回報單裡的文字接管了建置機器,一枚沒被作廢的發布金鑰把未經授權的更新送到使用者手上

已報導損失2.3.0 · 上線約八小時
判定

任何陌生人都能觸發 Cline 公開程式碼倉庫上的問題 (issue) 分類助手,這個分類助手又有在建置該專案的機器上執行命令的能力,因此一份 issue 裡的內容被讀取後就成為接管那台機器的命令。又因為同一台機器與持有發布金鑰的 nightly 發布工作共用同一份重複使用的建置檔案存放區,一個低權限的入口就觸及了負責更新生產環境程式的憑證。

事件經過

2025 年 12 月,這個 AI 寫程式工具的維護者,在他們程式碼所在、任何人都能提交錯誤回報的公開位置加上了一個自動助手。這個助手會讀每一份新回報並回覆它。它的設定是任何帳號都能觸發,而且這個助手也有在處理該回報的那台機器上執行命令的權限。

新年初,找到濫用方法的研究人員提交了正式的安全回報,並寄信到該工具的安全信箱。數週後,nightly 建置工作與那份共用的重複使用建置檔案存放區周邊出現了異常失敗。不清楚那是另一位研究人員還是有惡意的駭客。

研究人員公開這些發現後,維護者在 30 分鐘內移除了自動助手,並讓發布工作不再重複使用那份共用存放區。隔天他們表示金鑰已經換掉。再隔一天,在收到有些金鑰可能仍然可用的回報後,他們又換了一次。

這個事件公開後八天,該工具命令列套件一個未經授權的 2.3.0 版本出現在 npm 上,是用一枚沒有被正確撤銷的 npm 金鑰發布的。不清楚是誰發布的。裡面的程式與先前合法的 2.2.3 版完全相同;差別是多了一行,它會在每一台更新過的機器上安裝另一個獨立的通用 AI 助理,那個助理能執行命令、讀取檔案並瀏覽網頁。

這個未經授權的版本上線約八小時,之後維護者發布了 2.4.0 版,把 2.3.0 標記為已淘汰,並撤銷了那枚金鑰。不清楚該工具 5 百萬以上的使用者中,有多少人在那段時間內安裝了它。維護者表示他們自己的稽核未發現編輯器擴充套件市集上有未經授權的發布。事後他們把 npm 發布改成不再依賴長效金鑰的方式處理。

2025 年 12 月 21 日Cline 的維護者在他們的 GitHub 倉庫加入了一個由 AI 驅動的議題分類 workflow,使用 claude-code-action 自動回覆新的 issue。
2025 年 12 月下旬Khan 發現這個漏洞。
2026 年 1 月 1 日Adnan Khan 提交 GHSA,並寄信到 security@cline.bot。
2026 年 1 月 31 日 - 2 月 3 日在 Cline 的 nightly workflow 中觀察到可疑的快取失敗。
2026 年 2 月 9 日Khan 公開發現內容;Cline 在 30 分鐘內修復,移除 AI 議題分類 workflow,並讓 publish workflow 不再使用快取。
2026 年 2 月 10 日Cline 確認收到通報,表示憑證已輪替。
2026 年 2 月 11 日在收到 token 可能仍然有效的回報後,Cline 再次輪替憑證。
2026 年 2 月 17 日未經授權的 cline@2.3.0 被發布到 npm(有一枚 npm token 沒有被正確撤銷)。
2026 年 2 月 17 日Cline 發布 2.4.0,將 2.3.0 標記為已淘汰,並撤銷那枚正確的 token。
2026 年 2 月 17 日GHSA-9ppg-jx86-fqw7 公開。
事故後Cline 將 npm 發布改為透過 GitHub Actions 的 OIDC provenance。

主要來源——S1:https://snyk.io/blog/cline-supply-chain-attack-prompt-injection-github-actions存檔快照

損失成因

  • 公開專案上的自動助手把一份回報單裡的文字當成要執行的工作,而它又能在建置軟體的那台機器上執行命令 陌生人打的文字,是經由與維護者自己下指令相同的管道抵達助手,而助手的設計裡沒有任何東西把兩者分開。又因為這個助手還能執行命令,一份錯誤回報裡的一句話就變成了建置機器上的一道命令。技術名稱:間接提示注入(indirect prompt injection)
  • 唯一在決定哪些文字算是命令的,就是那個會被這些文字說服的同一個 AI 關於助手該做什麼、不該做什麼的規則,是寫在它自己的指示裡,所以誤導它的那段文字,同樣通過了它對那段文字的檢查。住在被操縱物件內部的限制,不是限制。技術名稱:自我裁決的指令(self-adjudicated instructions)
  • 任何陌生人都能啟動的那個工作,和持有發布金鑰的 nightly 工作,重複使用同一份已存建置檔案的存放區 那份存放區每個專案有 10 GB 上限,一旦滿了就會丟掉最舊的項目來騰出空間,於是一個工作留下來的東西,可以取代另一個工作預期會找到的東西。nightly 建置也是用與真正發布版本相同的發布身分和套件名稱送出去的,因此碰到低信任那一側,就等於碰到了出貨產品的那一側。技術名稱:生產與開發共用基礎設施(shared prod-dev infrastructure)
  • 金鑰的更換被宣告完成,卻沒有逐一確認每一枚舊金鑰都已不再被接受 這些金鑰不會自己到期;只有在有人取消它們、而且發證公司確認之後,它們才會失效。因為完成只是被記錄下來,而不是逐一被確認,一枚仍然被接受的金鑰在清理中存活下來,後來被用來發布。技術名稱:未經逐一驗證的憑證撤銷(unverified credential revocation)

判讀

你可以把兩個人分開安排,發給他們不同的鑰匙,他們還是會走到同一個架子上拿零件。其中一個,誰來委託他都承接。另一個,負責把你的產品發出去。第一個放在那個架子上的東西,第二個之後會拿起來用,不會去看它是誰放的。

自我查核

只有你能回答的

  • 你是不是假設掛在公開專案上的自動助手只有你已經合作的人才能觸發?你有沒有曾經決定過,陌生人打進來的文字可以讓它們做到什麼程度?
  • 收到回報後有人告訴你金鑰已經換掉了,那句話在你理解裡是什麼意思?你有沒有事先和團隊約定好,「更換已完成」的證據應該長什麼樣子?
  • 為了換取更快的建置,你會在知情的情況下接受你的發布工作重複使用那些「任何外部人都能啟動的工作也可以寫入」的已存檔案嗎?

你的 AI 可以幫你查的

僅回報;不要變更任何程式碼或設定。每一個答案都要引用檔案或主控台的出處,讓別人能夠查證。

  1. 列出這個倉庫中每一個會把外部提供的文字(issue 標題、issue 內容、pull request 標題或描述、留言、抓回的網頁)傳進 AI agent prompt 的 workflow。針對每一個,說明那段文字是否被直接插入 prompt 樣板中,以及誰被允許觸發該 workflow;並引用檔案或主控台出處。
  2. 針對在 CI 中被呼叫的每一個 AI agent,回報它被授予的確切工具清單(shell、write、edit、fetch、search),以及在模型本身之外是否存在任何允許/拒絕政策來限制它可以對哪些指令採取行動,例如 runner sandbox、網路外連規則,或 token 權限範圍。如果唯一的約束只是 system prompt 裡的措辭,請明確說出來;並引用檔案或主控台出處。
  3. 回報是否有任何不具寫入權限的人就能觸發的 workflow,可以讀取或寫入與發布(release、publish)workflow 相同的 Actions 快取範圍、runner 或 artifact 存放區,以及 publish workflow 是否會還原像 node_modules 這類已快取的依賴;並引用檔案或主控台出處。
  4. 回報 nightly、預發布與正式版建置是否使用相同的市集發布者身分、相同的套件名稱與相同的 token,或者各自有不同的身分與不同的 token;並引用檔案或主控台出處。
  5. 列出這個專案為 npm 與擴充套件市集持有的每一枚發布憑證,包括任何為一次性用途而建立的。針對每一枚,回報其建立日期、是否會自行到期,以及是否有向發證登錄方查核的紀錄,顯示已被取代的 token 現在確實會被拒絕,而不只是被假定已撤銷;並引用檔案或主控台出處。

預防方式

把任何陌生人能打進來的文字,都當成會被拿去執行的東西。 向負責維護你建置環境的人要一份一頁的清單,列出掛在你公開專案上的每一個自動助手:誰能觸發它,以及它在機器上被允許做什麼。清單上任何同時「接受陌生人文字」又「能執行命令」的東西,都應該先關掉,直到你以書面方式判定這個取捨值得為止。

假設你給 AI 助手的指示是建議,不是圍籬。 助手真的需要動手的地方,把限制放在它使用的帳號和金鑰上,這樣不管它那天讀到什麼,它最多能造成的損害都是固定的。要求你的供應商明確說出:如果這個助手被說服去做你沒有要求的事,它能碰到什麼。

讓負責出貨你產品的那個工作遠離任何共用的東西。 要求書面確認:你的發布工作不會重複使用低信任工作可以寫入的已存檔案,而且你的 nightly 或測試建置是以與客戶安裝的版本不同的身分、不同的套件名稱、不同的金鑰發出去的。

把「金鑰已經換掉」當成一個要去查證的說法,不是一個結果。 任何事故之後,索取清理前存在的完整金鑰清單,並針對每一枚,取得發證公司提供的證據,證明舊金鑰現在會被拒絕。服務有提供會自行到期的金鑰時就優先採用,這樣漏掉的那一枚會自己失效,而不是一直等著。

你換掉的金鑰,在發證公司對它說不之前,都還是有效的。