摘要:Wiz Red Agent披露的一次GitHub Actions攻击演示,把AI补丁、未可信输入、工作流令牌与Jira凭据串成了一条风险链。真正值得警惕的不是“AI又写错了一行代码”,而是AI生成的改动正在更快进入拥有部署权和密钥的CI/CD控制面。
一条看起来只是在修复安全告警的代码建议,为什么会把攻击面一路带到企业的 Jira?
Wiz Research 公开的 Red Agent 案例给出了一个很有代表性的答案:风险并不一定发生在业务应用里,而可能藏在仓库的 GitHub Actions 工作流中。研究人员沿着公开提交、工作流触发条件、表达式展开方式和可用凭据进行验证,展示了外部贡献者如何把可控文本送进 runner 的 shell,再利用工作流环境中的权限与秘密完成后续动作。Jira 不是漏洞本身,而是这条权限链能够触达的高价值系统之一。
这个案例最容易被压缩成一句耸动结论——“Copilot 自动修复制造了漏洞”。但严格说,这个归因存在争议。Wiz 从提交历史和补丁形态出发,把关键改动与 Copilot Autofix 建议联系起来;GitHub 对相关表述的回应则强调,不能仅凭最终提交就证明建议代码未经人工修改、原样进入生产,也不能把工作流此前已有的危险设计全部归因于 Copilot。因此,更准确的说法是:研究团队在包含 AI 辅助修复痕迹的变更链中,演示利用了一个真实的 GitHub Actions 注入问题;公开材料能够证明可利用性,却不足以证明 AI 是唯一成因。
同样需要划清另一条边界:Wiz 披露的是安全团队在授权、受控条件下完成的攻击演示。公开资料没有给出外部攻击者已经利用该路径入侵项目或 Jira 的证据。能被演示利用与已经发生野外利用,是两种完全不同的事实状态。
漏洞不在“AI代码”,而在信任边界被抹掉
GitHub Actions 的危险之处,是同一个 YAML 文件同时描述了事件、数据、程序和权限。开发者看到的是“把 PR 标题写进环境变量,再执行后续脚本”;runner 实际经历的却是多层解释:GitHub 先展开 ${{ ... }} 表达式,生成临时脚本,shell 再进行引号、变量、命令替换和重定向解析,最后工作流还可能调用第三方 Action、API 或部署工具。
只要 PR 标题、分支名、issue 正文、提交信息、作者邮箱等攻击者可控字段被直接嵌进 run:,数据就可能在脚本生成阶段变成代码。典型危险写法是:
1 | - name: Prepare metadata |
许多人误以为双引号足以隔离输入,事实上表达式在 shell 启动前已经被替换。若标题中包含引号、换行、命令替换或 shell 控制符,最终脚本的语义就可能被改写。GITHUB_ENV 也不是一个普通文本文件:runner 会把写入内容解析为后续步骤的环境变量。多行边界、键名和内容没有严格约束时,攻击者不仅可能污染一个值,还可能额外注入变量,影响后续工具、脚本查找路径或认证流程。
安全写法不是“换一种 echo”,而是先把表达式结果放入 step 级环境变量,让 GitHub 通过进程环境传递数据,再在脚本里只引用 shell 变量,并对允许的格式做验证:
1 | - name: Prepare metadata |
更稳妥的方案是避免 shell:使用 JavaScript/Python 读取 GITHUB_EVENT_PATH 中的 JSON,按结构取值;需要跨步骤传递复杂内容时采用明确的编码、长度上限和随机 heredoc 分隔符,并拒绝意外换行。关键原则只有一句:未可信内容可以成为参数,不能参与生成程序。
为什么一处注入能够“打开 Jira”
单看模板注入,它只是 runner 上的一次命令执行;真正决定后果的是上下文权限。CI/CD runner 往往同时握有 GITHUB_TOKEN、云端部署凭据、包仓库令牌、签名材料以及 Jira、Slack 等协作系统的 API token。攻击者一旦在有秘密的 job 中执行命令,目标就不再是“让测试失败”,而是读取凭据、修改仓库、污染制品或横向访问外部系统。
风险会被以下组合显著放大:
- 工作流由
pull_request_target、issue、评论或其他外部可触发事件启动; - job 检出并执行 PR 分支代码,或把事件字段内联进 shell;
permissions没有显式收紧,沿用仓库或组织默认权限;- job 能读取 Jira 等第三方秘密;
- 第三方 Action 只固定到可移动标签,甚至直接使用分支;
- 环境没有审批门、允许任意分支使用生产秘密。
尤其要警惕 pull_request_target。它的合理用途是在基础分支的可信上下文中给外部 PR 打标签、留言或做元数据处理;它不应该检出并运行攻击者提交的代码。把“可信权限上下文”和“不可信代码”放在同一个 job,相当于主动拼好攻击链。
Copilot 到底该负多少责任
这场争议的重要性,不在于给某个产品判一次输赢,而在于暴露了 AI 修复的证据缺口。
第一,AI 建议通常不是独立制品。开发者可以接受、改写、拆分或与其他提交合并;最终 Git 历史能证明“什么代码被提交”,却未必能证明“模型原始输出是什么、人工改了什么”。如果企业没有保存告警、建议补丁、接受动作和审查记录,就很难准确追责。
第二,局部修复可能掩盖系统性风险。模型看到的是某一条 CodeQL 告警,倾向于最小修改;但工作流安全需要同时理解触发器、权限、秘密、环境保护规则、被调用 workflow 和第三方 Action。把危险表达式从一处移动到另一处,或者用 GITHUB_ENV 包装一下,并不等于恢复了信任边界。
第三,AI 补丁具有“安全权威”错觉。普通重构还会引起评审者警觉,标着 Autofix、security fix 的 PR 反而容易被快速合并。速度优势在这里会反转为审查折扣。
所以,不能因一次案例得出“Copilot 建议必然不安全”;也不能因为有人类点击合并,就把 AI 生成链条排除在供应链治理之外。合理的责任模型应当是:模型提供方负责建议的安全基线和可追溯性,平台负责默认权限与危险组合告警,维护者负责上下文审查,组织负责合并门禁和秘密隔离。
从一条补丁扩展到AI补丁供应链
传统软件供应链治理关注依赖包、构建器和制品来源。AI 编程普及后,还要增加一种新对象:补丁来源。企业至少应记录模型与功能版本、提示上下文、原始 diff、人工编辑、静态分析结果、批准人和最终提交。这里不是为了监控开发者,而是为了在事件发生时回答三个问题:代码从哪里来,经过了什么检查,谁基于什么证据放行。
对于 .github/workflows/**、部署脚本、IaC、签名和发布配置,AI 生成内容应自动进入高风险审查通道。仅有“另一名开发者点了 Approve”不够;评审清单必须覆盖事件来源、攻击者可控字段、shell 边界、token 权限、secret 可见性、环境审批和第三方依赖固定方式。
这也解释了为什么 CI/CD 正在成为新的高风险入口。AI 让业务代码修改变快,而工作流代码往往被当作配置;前者有完整测试,后者却可能在合并后立即获得写仓库、发包、部署和访问企业 SaaS 的能力。攻击者不需要攻破模型,只需等一个“看起来合理”的补丁把不可信数据送入高权限解释器。
如何复核一条可疑的Actions补丁
面对类似披露,安全团队不应只看研究文章的截图,也不应只看修复后的主分支。可复现审计至少包含四层证据。
第一层是提交证据。定位引入问题的 commit、关联 PR、审查记录和后续修复,逐行比较工作流在变更前后的触发器、表达式、run 脚本与权限。若要讨论 Copilot 归因,还必须寻找 Autofix 建议页面、机器人标记或平台审计日志;仅凭代码风格判断“像 AI 写的”没有证明力。
第二层是事件证据。下载真实 webhook payload,标出哪些字段由仓库维护者控制,哪些字段可被 fork 作者、issue 用户或评论者控制。GitHub 官方文档列出的典型未可信输入包括标题、正文、分支名、提交信息和邮箱,但具体边界取决于事件类型。pull_request、pull_request_target 和 workflow_run 即使处理同一个 PR,也可能运行在完全不同的权限上下文。
第三层是执行证据。把表达式展开后的临时 shell 脚本完整还原,而不是盯着 YAML 猜测。测试输入应覆盖引号、反引号、$()、分号、换行、Unicode 和超长字符串;同时检查写入 GITHUB_ENV、GITHUB_OUTPUT、GITHUB_PATH 的内容是否会改变后续步骤。验证时使用隔离仓库和假凭据,记录网络请求与文件访问,绝不能把真实 Jira token 放进复现环境。
第四层是影响证据。确认被攻陷 job 实际拥有的 GITHUB_TOKEN scope、可见 secrets、环境授权、runner 网络出口和第三方 API 权限。能够执行 id 与能够读取 Jira 项目、创建管理员账户或修改发布包,不是同一级别的结论。安全报告应把“命令执行成立”“凭据可读”“外部系统动作成功”“观察到真实攻击”分开陈述,避免把理论上限写成已经发生的事实。
这种复核方法也适用于 AI 补丁:先保存模型建议,再由自动测试证明危险输入不能改变控制流,最后由人工确认权限面没有扩大。只有这样,企业才能把“相信工具”转换成“相信证据”。
一套可以落地的防线
1. 先做权限最小化
在顶层写明 permissions: {},再按 job 授予 contents: read 等最低权限。需要写权限的发布 job 与处理外部输入的检查 job 必须分离。fork PR 默认不能接触生产秘密;Jira、云平台和包仓库 token 应使用短期、细粒度凭据,并绑定 GitHub Environment、分支规则与人工审批。能用 OIDC 换取短期云凭据,就不要长期保存云密钥。
2. 固定第三方Action到完整提交SHA
标签和分支都可移动,v4 也不是密码学身份。生产工作流应固定完整 commit SHA,由 Dependabot 或 Renovate 提交升级 PR,同时核对发布者、变更内容和来源。固定 SHA 不能消除上游恶意代码,但可以把“静默漂移”变成可审计变更。对于组织内部复用的 Action,也应采用同样标准:源码仓库受保护、发布过程可追溯、调用方固定版本,并定期清理已经无人维护却仍能读取秘密的自动化组件。只对外部 Action 严格、对内部脚本宽松,是常见而危险的盲区。
3. 把工作流当代码扫描
CodeQL 适合发现部分 Actions 表达式注入;zizmor 则专门检查 GitHub Actions,能够提示 template injection、危险触发器、过度权限、未固定 Action 等问题。建议在 PR 中同时运行 YAML 语法检查、zizmor、CodeQL/静态规则和 secret scanner,并让扫描 workflow 本身只拥有读权限。静态扫描不是最终裁判,但它非常适合阻止最常见的危险模式进入主分支。
zizmor 的价值还在于它把散落的安全经验变成可以持续执行的仓库策略。企业可以先在全部仓库做基线扫描,按“外部可触发且有写权限”“能读取生产秘密”“普通只读检查”分级,再逐步把高风险规则从告警升级为合并阻断。对历史遗留问题可建立带到期日的豁免,但不能用全局忽略文件把噪声永久盖住。扫描器版本本身也应固定并受依赖更新流程管理,否则安全门的判断标准会无声变化。
4. 建立针对工作流的人工评审
CODEOWNERS 应覆盖 .github/workflows/、自托管 runner 配置、发布与部署目录;高风险变更至少由一名熟悉 Actions 安全的人批准。评审时不要只看 diff,要把触发事件、调用链、权限和秘密画在一起。凡是出现 pull_request_target、workflow_run、可复用 workflow、动态 uses、写权限或外部系统 token,都应提升审查等级。
5. 对AI补丁实行“先证明,后合并”
自动修复可以生成 PR,不能自动获得信任。安全补丁必须附带告警复现、攻击输入测试、修复后的负面测试、权限差异和扫描结果。对于工作流,最好用无生产秘密的测试仓库验证恶意标题、正文、分支名和多行输入。AI 可以提速,但不能跳过威胁建模。
结语:CI/CD不是胶水层,而是生产控制面
Wiz Red Agent 案例最值得记住的,不是“AI 写了一段坏代码”,而是一个组织如何让外部输入、自动生成补丁、高权限 runner 和企业 SaaS 凭据出现在同一条执行链上。只要这四者相遇,一行 YAML 就可能拥有超过数万行业务代码的破坏半径。
对“Copilot 是否直接造成漏洞”,目前应保留归因边界;对漏洞是否可利用,研究演示已经给出肯定答案;对是否发生外部攻击,公开材料没有证据。把这三句话同时说清楚,才是负责任的安全讨论。
下一阶段的软件供应链治理,不能只问依赖是否可信,还要问补丁是谁生成、怎样验证、在什么权限下运行。AI 写代码不可逆转,真正需要改变的是合并制度:任何进入 CI/CD 控制面的自动修复,都必须先被当作潜在供应链输入,而不是天然可信的安全答案。
参考资料
- Wiz Research / Wiz Red Agent,《Red Agent Exploits Snowflake Vuln Missed by GitHub Copilot Autofix》,2026:https://www.wiz.io/blog/red-agent-snowflake-copilot-cicd-bug
- GitHub Docs, Security hardening for GitHub Actions。
- GitHub Docs, Secure use reference。
- GitHub Docs, Workflow syntax for GitHub Actions — permissions。
- GitHub Docs, Events that trigger workflows — pull_request_target。
- GitHub Docs, Variables — GITHUB_ENV。
- GitHub Docs, Using immutable releases and pinning actions to a full-length commit SHA。
- GitHub CodeQL, Code injection in GitHub Actions。
- zizmor Documentation, Audits(包括 template-injection、dangerous-triggers、excessive-permissions、unpinned-uses 等检查)。
- zizmor, woodruffw/zizmor,GitHub Actions 静态安全分析器。
- GitHub Security Lab, Keeping your GitHub Actions and workflows secure。
- OpenSSF, Scorecard — Pinned Dependencies。
注:Wiz 研究所涉及的公开仓库提交、GitHub Actions 文件和修复 PR,应以原文链接到的历史版本为准;提交历史证明的是代码变更与可利用条件,不应被外推为外部攻击已经发生,也不应在缺少模型原始建议记录时被表述为“全部代码由 Copilot 原样生成”。