← All posts / Research

自動修復反成破口:Copilot 產生的修補讓 AI 紅隊偷走 Snowflake 的 Jira 權杖

一筆由 GitHub Copilot Autofix 共同撰寫的提交,移除了 Snowflake 公開儲存庫中安全的輸入消毒寫法,挖出一個 shell 注入漏洞——Wiz 的自主紅隊代理 Red Agent 在五天內發現、利用並通報,外流一枚內部 Jira API 權杖後,Snowflake 當日完成修補。

自動修復反成破口:Copilot 產生的修補讓 AI 紅隊偷走 Snowflake 的 Jira 權杖

2026 年 6 月 18 日,一個 pull request 進到了 Snowflake 的公開 GitHub 儲存庫 snowflake-connector-net。標題是「SNOW-2069227: Update jira workflows」,看起來就像例行維護——對 Jira 整合工作流程做點小整理。這筆提交的共同作者是 GitHub Copilot Autofix,那個會自動為程式碼掃描警報提出修法的 AI 功能。

五天後,一個自主運作的 AI 資安代理,已經利用這個變更在 Snowflake 的 GitHub Actions runner 內執行任意指令,並外流一枚有效的 Jira API 權杖。

這份由 Wiz Research 的 Gal Nagli 於 8 月 17 日發布的完整報告,是至今最乾淨俐落的案例研究,說明產業如今運作其中的奇特迴圈:AI 寫出不安全的程式碼、AI 找到它、AI 利用它,然後另一邊的 AI 協助修補它。攻擊鏈的每一步、兩邊的每一方,全由機器驅動。

Autofix 究竟改了什麼

出問題的工作流程 jira_issue.yml 會在儲存庫有人開 issue 時觸發,工作內容很平凡:把 issue 標題轉成一張 Jira 工單。

在 Copilot 撰寫的那筆提交之前,這個流程是安全的。Issue 標題會先經過 env: 區塊傳入環境變數,再用 jq --arg 組出給 Jira 的 JSON payload——一個結構化解析器,把輸入視為資料,永遠不當成程式碼。

PR #1218 移除了這個寫法。取而代之的,是 Autofix 把原始 issue 標題直接內插進 shell 的 run: 區塊:

TITLE=$(echo '${{ github.event.issue.title }}' | sed ...)

這是教科書級的 GitHub Actions 腳本注入反模式。GitHub 會在 shell 執行之前就展開 ${{ ... }} 模板運算式,而 sed 的轉義發生在展開之後——為時已晚。標題裡一個單引號就能掙脫 echo '...' 字串,後面所有內容都會當成任意 shell 指令執行。

換句話說:這個儲存庫原本就有一套針對此攻擊精心設計的防禦,而一個自動化「修復」把它拆掉了——很可能因為 AI 缺乏歷史脈絡,不明白那個看似笨拙的 env: + jq 寫法當年為何而存在。

那道形同虛設的安全閘門

工作流程裡確實有一個看起來具保護性的條件檢查:

if: (github.event_name == 'issues' && github.event.pull_request.user.login != 'whitesource-for-github-com[bot]')

然而在 issues 事件上,github.event.pull_request 永遠是 null。這個條件於是化簡為 null != 'whitesource-for-github-com[bot]'——永遠成立。每個 GitHub 使用者都過得了閘門,任何匿名帳號只要開一個 issue 就能觸發工作流程。

Red Agent 登場

Wiz Research 經營的「Red Agent」是一套自主運作的 AI 資安研究工具,透過漏洞揭露計畫授權掃描目標——這次的對象是 Snowflake 的 HackerOne 賞金計畫。在掃描 Snowflake 的 GitHub 組織時,代理自己標記出 jira_issue.yml 有 run: 區塊注入未受信任輸入的風險。沒有人類指路。

接下來發生的事,是整份報告最有啟發性的段落。Red Agent 第一次的外流 payload 用了 # 字元想把後續 shell 語法註解掉,結果 runner 丟出 bash 錯誤——註解連 TITLE=$(...) 的右括號也一起吃掉了。代理沒有失敗或卡住,而是分析語法錯誤、推理 shell 文法,改用 ; echo ' 正確關閉區塊後重新執行。

幾秒鐘後,Wix 的頻外監聽端收到來自某個 Azure IP 上 GitHub Actions runner 的回呼,內含 base64 編碼的 JIRA_API_TOKEN、JIRA_USER_EMAIL 與 JIRA_BASE_URL。

這枚權杖屬於 qa@snowflake.net,能登入 snowflakecomputing.atlassian.net,對 Snowflake 的工程、安全合規與賞金追蹤專案擁有讀取權限——對任何意圖不那么友善的人來說,都是一個有意義的立足點。

五天、負責任揭露、乾淨的鑑識

整個時間軸濃縮了現代資安維運的一切關鍵:

  • 6 月 18 日 —— 注入模式由提交 4a1b8ce(PR #1218)引入,共同作者為 Copilot Autofix。
  • 6 月 23 日 —— Wiz 識別、利用並透過 HackerOne 通報漏洞(報告 #3819931)。
  • 6 月 23 日當天 —— Snowflake 以 PR #1402 修補工作流程,還原安全的 env: + jq --arg 寫法。
  • 6 月 24 日 —— Jira 權杖輪替。
  • 8 月 17 日 —— 公開揭露。

Snowflake 的稽核日誌確認,在五天曝露窗口期間 Wiz 是唯一行為者,Wiz 也已刪除概念驗證期間存取的所有資料。Snowflake 在聲明中表示未發現未經授權存取的證據,並正與 Wiz 合作「把這些經驗分享給整個產業」。

為何這件事遠超過一個儲存庫

這起事件有三個值得強調的啟示。

AI 產生的 PR 不該豁免於審查。 Copilot Autofix 以機率模式預測程式碼,可能在真心想「修好」某件事的同時,復活過時、不安全的慣用寫法。Wiz 的結論很直白:AI 產生的 PR 必須接受與人類程式碼相同的靜態分析與資安檢視。自動化的變更仍然是變更。

發現窗口正在塌縮。 這個漏洞只活了五天,就被一個自動化代理發現並驗證。具備同等能力的攻擊工具,不需要賞金計畫當理由就會掃描公開儲存庫。實際後果是:權杖壽命要更短、修補週期要更快,並且必須假設 CI/CD 設定中暴露的任何內容,全天候都有機器在閱讀。

防護欄必須保護好寫法不被「整理」掉。 這個儲存庫裡的安全寫法正是為了防止 shell 注入而存在,卻被一個把它視為雜訊的工具移除。Wiz 建議建置自動化檢查,禁止 AI 代理把 jq --arg 這類結構化解析器換成直接字串內插——這條原則的適用範圍遠超過 GitHub Actions。

防禦者那一側的帳

這次揭露恰好與 OpenAI 總裁 Greg Brockman 8 月 16 日的文章《The Defender’s Window》落在同一個新聞週期。Brockman 把最近的 OpenAI–Hugging Face 代理式入侵稱為「分水嶺時刻」,預示了威脅行為者能力在未來數月的演變方向。他的論點是:壓縮攻擊者經濟學的同一批 AI 能力,也可以讓天平倒向防禦者——前提是組織現在就行動。作為親身測試,他讓 ChatGPT Work 檢視自己的靜態網站:15 分鐘內找出 13 個問題,並在一小時內修完,從 DMARC 分階段部署到移除過時的 jQuery 依賴。

Snowflake 事件正是這枚雙面硬幣的縮影。AI 引入了漏洞;AI 發現它、武器化它、通報它;人類則訂下遊戲規則(揭露計畫、短壽命憑證、稽核日誌),讓結局保持良性。這樣的分工——機器以機器速度運作、人類掌握高影響力決策——大致就是 Wiz 與 Brockman 共同指向的均衡。

對工程團隊來說,可行清單很短。把 AI 撰寫的提交當成任何其他不受信任的貢獻者。掃描 GitHub Actions 工作流程中 run: 區塊對 ${{ github.event.* }} 使用者可控欄位的內插。優先使用環境變數間接傳遞加結構化解析。憑證輪換週期以天計,而非以季計。並且假設掃描早已在進行——因為在 Snowflake 的儲存庫上,確實如此。