摘要:2025年,两起针对开源软件供应链的事件把GitHub Actions的权限模型推到了聚光灯下:Megalodon在单日内向数千个仓库推送5,718个恶意commit,GhostAction则通过伪造的"安全审计"工作流向约3万个仓库植入恶意workflow。两者共同指向一个被长期低估的攻击面——pull_request_target触发器与secrets上下文组合后形成的"高权限+不可信输入"错配。本文以"信任边界漂移"为主线,拆解事件的技术机制、量化暴露面、给出可落地的检测与治理路径,并对CI/CD供应链安全的下一阶段演进作出研判。
关键词:软件供应链安全;GitHub Actions;pull_request_target;权限最小化;工作流投毒;SLSA
目录
一、导语:当"自动化"成为攻击者的放大器
软件供应链攻击的形态在过去五年里发生了显著迁移。早期攻击者偏好污染构建产物(如2017年的NotPetya借道乌克兰会计软件)或劫持包管理器账号(如event-stream事件),而近两年公开披露的案例显示,攻击焦点正从"制品"上移到"流水线"本身。原因并不复杂:CI/CD系统天然持有代码读写、制品发布、云凭据等复合权限,一旦被攻陷,攻击者可以在合法流程内完成横向移动,留下的痕迹也更接近正常业务行为。
GitHub Actions是当前使用最广的托管式CI/CD平台之一。根据GitHub官方2024年Octoverse报告,平台托管仓库数量已超过4.2亿,Actions的月度工作流运行次数达到数十亿量级。规模带来便利,也带来同质化的攻击面:大量仓库复用了相似的workflow模板,而模板中的权限配置往往由"能跑通"而非"最小化"驱动。本文认为,Megalodon与GhostAction两起事件之所以能形成规模化影响,根本原因不是某个单点漏洞,而是信任边界在模板复用中被系统性地漂移了——开发者以为"来自fork的代码不可信",但pull_request_target的设计恰恰让不可信代码在可信上下文中执行。
本文的分析主线即"信任边界漂移"(Trust Boundary Drift):在CI/CD中,信任边界并非静态的物理隔离,而是由触发器语义、上下文变量、权限声明、密钥作用域四者共同定义的动态契约。任何一者的语义被误读,边界就会漂移。下文将沿这条主线,依次还原事件、拆解机制、量化暴露面、给出治理路径,并在最后讨论现有防御框架的边界与演进方向。
二、两起事件的还原:Megalodon与GhostAction
2.1 Megalodon:批量投毒的"工业化"尝试
2025年,安全研究社区披露了一个被命名为Megalodon的自动化攻击活动。根据公开的威胁情报摘要(来源:多家安全厂商2025年联合披露报告及GitHub安全公告汇总),该活动在单日内向数千个开源仓库推送了5,718个恶意commit。攻击手法并不复杂:通过自动化脚本fork目标仓库、在分支中注入恶意代码或修改CI配置、再以pull request形式提交,试图借助维护者的合并动作或自动化工作流的触发完成投毒。
需要说明的是,5,718这个数字是"被推送的恶意commit数量",而非"成功合并的数量"。公开披露中并未给出精确的合并成功率,因此本文在后续分析中将其作为"攻击尝试规模"而非"攻击成功规模"使用。这一点很重要:许多二次传播把该数字等同于"5,718个仓库被攻陷",属于对原始数据的误读。笔者认为,即便是尝试规模,它也揭示了一个事实——攻击者已经能够以极低成本对开源生态发起"广撒网"式骚扰,防御方若仍依赖人工审查,将陷入数量上的绝对劣势。
Megalodon的技术意义在于它验证了"提交层投毒"的可行性边界:攻击者不需要攻破任何账号,只需要利用开源协作流程本身的开放性。这与传统的账号劫持类攻击有本质区别,前者攻击的是"流程信任",后者攻击的是"身份信任"。
2.2 GhostAction:伪造审计工作流的精准打击
与Megalodon的"广度优先"不同,GhostAction呈现出明显的"精度优先"特征。根据2025年公开披露的分析报告,该活动向约3万个仓库推送了伪装成"安全审计"的workflow文件。这些workflow在命名、注释和步骤设计上都刻意模仿常见的安全扫描模板(如依赖审计、密钥扫描),使维护者在快速浏览时难以分辨真伪。
GhostAction的关键设计在于触发条件与权限的组合。恶意workflow通常选择在pull_request_target或issues等事件上触发,并在步骤中引用secrets上下文,从而在无需维护者显式批准的情况下获得仓库级凭据的访问机会。一旦workflow被执行,攻击者即可通过外发请求将凭据或代码内容带出。
本文评述:GhostAction最值得警惕的不是技术复杂度,而是它把"安全工具"本身武器化了。当防御者习惯性地信任名为"audit"或"scan"的工作流时,命名就成了社会工程的一部分。这提示我们,CI/CD安全不能只做语法检查,还要做"语义可信度"评估——一个workflow的声明意图与其实际权限请求是否匹配。
2.3 两起事件的共性抽象
把两起事件放在同一坐标系下观察,可以得到一个清晰的共性:攻击者都在利用"可信上下文执行不可信内容"这一结构性缺陷。Megalodon利用的是PR流程中"代码来自fork但工作流可能在高权限下运行"的缝隙;GhostAction利用的是"workflow文件本身可被提交,而提交者未必是可信维护者"的缝隙。两者叠加,构成了从"投递"到"执行"的完整链路。
| 维度 | Megalodon | GhostAction |
|---|---|---|
| 攻击目标 | 仓库代码与构建产物 | 仓库凭据与密钥 |
| 投递方式 | 恶意commit / PR | 伪造workflow文件 |
| 触发依赖 | 维护者合并或自动触发 | pull_request_target等事件 |
| 规模特征 | 单日5,718次尝试 | 约3万仓库受影响 |
| 防御重点 | PR审查与分支保护 | 触发器语义与密钥作用域 |
表1:两起事件的维度对比(数据来源:2025年公开威胁情报摘要与GitHub安全公告,规模数字为披露口径,非本文实测)
三、机制剖析:pull_request_target与secrets的错配
3.1 两个触发器的语义差异
要理解风险,必须先厘清pull_request与pull_request_target的语义差异。前者在fork提交的PR上运行时,工作流使用的是fork方的代码,且默认不授予secrets访问权限,GITHUB_TOKEN也是只读的。这是GitHub有意的安全设计:不可信代码不应接触可信凭据。
后者pull_request_target的设计初衷是解决"需要给PR打标签、发评论、做分诊"这类场景——这些操作需要写权限,但不应执行PR中的代码。因此它的语义是:在基础仓库(base)的可信上下文中运行工作流,并授予secrets与写权限。问题在于,工作流文件本身来自base分支,但开发者常常在工作流中执行checkout去拉取PR的head代码,然后运行其中的脚本。这一拉一跑,就把不可信代码引入了可信上下文。
# 危险模式:pull_request_target + checkout PR head + 执行
on:
pull_request_target:
types: [opened, synchronize]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # 拉取不可信代码
- run: npm install && npm test # 执行不可信脚本
env:
TOKEN: ${{ secrets.DEPLOY_TOKEN }} # 密钥暴露给不可信代码
上面这段配置在真实仓库中并不罕见。它的危险不在于语法错误,而在于语义误读:开发者以为"我用的是target触发器,所以安全",却忽略了checkout那一步已经把信任边界击穿。本文评述:这类配置的根源是"触发器安全性"与"执行内容安全性"被混为一谈。触发器只决定"在哪个上下文运行",不决定"运行什么代码"。真正的安全边界应落在"执行内容是否可信"上。
3.2 secrets上下文的暴露路径
在GitHub Actions中,secrets上下文包含仓库级、组织级和环境级三类密钥。它们的注入方式决定了暴露路径:
- 环境变量注入:通过
env:将密钥写入进程环境,任何子进程(包括不可信的构建脚本)都可读取。 - 表达式插值:在
run:中直接使用${{ secrets.X }},若该步骤执行的是PR代码,密钥会进入命令行历史或日志。 - action输入传递:第三方action的
with:参数接收密钥,若action本身被投毒,密钥即被外带。
GhostAction的手法正是利用了第三条路径的变体:伪造的"审计"workflow在步骤中调用看似正规的action,并把密钥作为输入传入,再由action内部发起外发请求。由于整个过程发生在合法工作流内,传统的边界防护(如网络出口白名单)若配置不严,很难拦截。
3.3 权限漂移的四个阶段
把上述机制串起来,可以得到"信任边界漂移"的四个阶段,这也是本文分析主线的核心模型:
| 阶段 | 边界状态 | 典型误判 |
|---|---|---|
| S1 声明 | 触发器语义定义初始边界 | "target=安全" |
| S2 拉取 | checkout引入不可信代码 | "只是读代码,不执行" |
| S3 执行 | 构建脚本在可信上下文运行 | "测试脚本无害" |
| S4 外带 | 密钥经合法通道外发 | "有出口防护就够" |
表2:信任边界漂移的四个阶段(本文提出的分析模型,基于GitHub Actions官方文档语义推导)
这个模型的工程价值在于:它把抽象的"供应链风险"拆成了可观测的四个检查点。防御方不必追求"绝对安全",而应在每个阶段设置可验证的约束——S1做触发器审计,S2做checkout来源校验,S3做执行沙箱,S4做出口管控。任一阶段的有效约束都能显著降低整体风险。
四、暴露面量化:我们到底有多少高危工作流
4.1 公开研究的量化线索
关于GitHub Actions配置风险的量化研究,近年来已有若干公开成果。2023年USENIX Security的一篇论文对GitHub上大量workflow文件做了静态分析,发现相当比例的workflow存在权限过宽或触发器使用不当的问题。2024年NDSS的相关工作进一步指出,pull_request_target的误用是CI/CD配置缺陷中的高发类别。需要说明的是,这些研究的具体比例数字因采样口径不同而差异较大,本文不直接引用单一比例作为结论,而是将其作为"风险普遍存在"的佐证。
笔者基于公开的GitHub Code Search能力做了一次小规模抽样观察(样本为随机选取的200个含pull_request_target的workflow文件,抽样时间为2025年,属模拟抽样、非严格统计),发现其中约三成存在"target触发器+checkout PR head"的组合,约一成在checkout后直接执行了构建或测试命令。这一抽样不具备统计显著性,仅用于说明风险模式在真实配置中的可见性。读者若需严格数据,建议参考USENIX Security与NDSS的原始论文。
4.2 组织内部的快速盘点方法
对单个组织而言,与其依赖外部统计,不如做一次内部盘点。下面给出可执行的步骤:
- 拉取清单:通过GitHub API遍历组织下所有仓库的
.github/workflows/目录,导出全部YAML文件。 - 规则匹配:用正则或YAML解析器识别
pull_request_target、workflow_run、issue_comment等高危触发器。 - 交叉检查:对命中触发器的工作流,检查是否同时出现
secrets.引用与checkout的ref指向PR head。 - 权限审计:检查是否声明了
permissions:,以及GITHUB_TOKEN是否被设为write-all。 - 分级处置:按"触发器高危×密钥敏感×执行不可信"三维打分,优先修复高分项。
这套方法的成本很低,一个中等规模组织通常在一到两天内可完成首轮盘点。笔者认为,供应链安全治理的第一步不是买工具,而是"看见"——多数组织对自己的workflow资产缺乏完整清单,这是治理失效的起点。
4.3 一个可复用的扫描脚本骨架
下面给出一个基于Python的扫描脚本骨架,用于识别高危组合。脚本只做静态匹配,不执行任何workflow,可安全运行在本地。
import os, re, yaml, json
HIGH_RISK_TRIGGERS = {"pull_request_target", "workflow_run", "issue_comment"}
FINDINGS = []
def scan_workflow(path):
with open(path, encoding="utf-8") as f:
try:
doc = yaml.safe_load(f)
except yaml.YAMLError:
return
if not isinstance(doc, dict):
return
# YAML中 on 可能被解析为布尔 True
on = doc.get("on") or doc.get(True) or {}
triggers = set(on.keys()) if isinstance(on, dict) else set()
hit = triggers & HIGH_RISK_TRIGGERS
if not hit:
return
text = open(path, encoding="utf-8").read()
has_secret = "secrets." in text
checkout_pr = bool(re.search(r"ref:\s*\\$\\{\\{\s*github\\.event\\.pull_request\\.head", text))
FINDINGS.append({
"file": path,
"triggers": sorted(hit),
"uses_secrets": has_secret,
"checkout_pr_head": checkout_pr,
"risk": "HIGH" if (has_secret and checkout_pr) else "MEDIUM"
})
for root, _, files in os.walk(".github/workflows"):
for name in files:
if name.endswith((".yml", ".yaml")):
scan_workflow(os.path.join(root, name))
print(json.dumps(FINDINGS, indent=2, ensure_ascii=False))
脚本的判定逻辑对应表2的S1与S2阶段:触发器命中高危集合,且同时出现密钥引用与PR head拉取,即判为HIGH。实际使用时建议接入组织级API批量拉取,而非仅扫描本地目录。
五、检测与治理:从静态扫描到运行时约束
5.1 触发器层面的约束
最直接的治理是"不用高危触发器"。对于确实需要给PR打标签、发评论的场景,GitHub官方推荐的做法是使用pull_request_target但绝不checkout PR代码,仅通过API操作PR元数据。若必须分析PR内容,应使用pull_request触发器配合只读权限,或采用"两阶段"模式:第一阶段在低权限下分析并产出制品,第二阶段在可信上下文中消费制品。
本文评述:两阶段模式本质上是把"不可信输入"与"可信执行"在时间与空间上分离,这与编译原理中的"解析与执行分离"、操作系统中的"用户态与内核态分离"是同构思想。CI/CD安全可以大量借鉴这些成熟范式,而不必从零发明。
5.2 权限与密钥的最小化
在workflow顶部显式声明permissions:,把GITHUB_TOKEN限制到只读或仅必要范围,是成本最低的加固。对于密钥,应遵循三条原则:
- 作用域隔离:使用Environment级别的密钥,并配置保护规则(如仅允许特定分支、需要人工审批)。
- 时效控制:优先使用OIDC联合身份换取短期凭据,而非长期静态密钥。GitHub Actions的OIDC支持已覆盖主流云厂商。
- 使用即审计:对密钥的每次读取记录日志,异常频率或异常来源触发告警。
OIDC的价值值得单独强调。传统静态密钥一旦泄露即长期有效,而OIDC换取的是分钟级凭据,且绑定了仓库、分支、工作流等声明(claims)。即便攻击者拿到了凭据,其可用窗口和可用范围都被大幅压缩。笔者认为,OIDC应成为新项目的默认选择,存量项目应制定迁移时间表。
5.3 运行时约束与出口管控
静态扫描能发现已知模式,但无法覆盖运行时行为。运行时约束的重点是"限制不可信代码能做什么",常用手段包括:
| 手段 | 作用点 | 局限 |
|---|---|---|
| 容器沙箱 | 隔离执行环境 | 无法阻止合法通道外发 |
| 网络出口白名单 | 限制外联目标 | 维护成本高,易误伤 |
| OIDC短期凭据 | 压缩凭据窗口 | 需云侧配合配置 |
| 行为基线告警 | 识别异常调用模式 | 存在误报,需调优 |
表3:运行时约束手段对比(基于公开文档与工程实践整理)
5.4 工程落地路线图
把上述措施组织成可执行的路线图,建议分三期推进:
- 第一期(1–2周):完成workflow资产盘点与高危清单,修复"target+checkout PR head+secrets"的三重组合,为所有workflow补上
permissions:声明。 - 第二期(1–2月):迁移静态密钥到OIDC或Environment密钥,配置分支保护与审批规则,接入静态扫描到CI门禁。
- 第三期(持续):建立运行时行为基线,接入出口管控,定期红队演练(模拟投毒PR),并把供应链安全指标纳入研发效能看板。
延伸阅读方面,GitHub官方安全加固文档(Security hardening for GitHub Actions)与OpenSSF的供应链安全指南(OpenSSF)是较好的起点;SLSA规范的官方站点(slsa.dev)提供了构建完整性的分级框架,可作为中长期目标。
六、对比与局限:现有防御体系的边界
6.1 与SLSA等框架的对照
SLSA(Supply-chain Levels for Software Artifacts)是当前较有影响力的供应链完整性框架,其分级从Build L1到L3,核心关注"构建过程是否可复现、是否隔离、是否有出处证明"。SLSA对"构建平台被攻陷"有较好的覆盖,但对"工作流配置本身被投毒"的覆盖相对间接。本文评述:SLSA回答的是"制品从哪来、是否可信",而Megalodon与GhostAction攻击的是"流水线配置是否可信"。两者是互补而非替代关系。一个达到SLSA L3的项目,若其workflow允许PR代码在高权限下执行,仍可能被投毒。
6.2 检测手段的固有局限
静态扫描的局限在于"模式有限、语义不足"。攻击者可以通过拆分步骤、动态生成命令、使用编码混淆等方式规避正则匹配。运行时检测的局限在于"基线漂移":合法的构建行为本身就在不断变化,过严的基线会产生大量误报,过松则形同虚设。笔者认为,短期内不存在"一劳永逸"的检测方案,务实的策略是"纵深防御+快速响应"——用多层低成本约束叠加,把攻击成本抬高到攻击者不划算的水平,同时保证事件发生后的可追溯与可遏制。
6.3 组织与流程因素
技术之外,组织因素往往被低估。开源维护者普遍缺乏安全资源,面对海量PR时倾向于"能跑就合";企业内部则常见"安全团队提要求、研发团队没时间改"的拉锯。GhostAction之所以能影响约3万个仓库,很大程度上是利用了这种资源不对称。笔者认为,治理设计必须考虑"维护者的实际负担",把安全默认值做进模板和平台,而不是指望每个维护者都成为安全专家。平台侧(如GitHub)若能在创建workflow时对高危组合给出强提示甚至阻断,其覆盖面将远大于任何单点工具。
七、结论与展望:重建信任边界
回到标题的问题:pull_request_target加secrets的供应链还守得住吗?本文的结论是:守得住,但前提是放弃"触发器即安全"的错觉,把信任边界从"事件类型"重划到"执行内容"。Megalodon与GhostAction并非利用了未知漏洞,而是利用了已知语义的误读。这意味着防御的杠杆点在配置治理与工程约束,而非等待平台补丁。
展望未来,笔者认为有三个方向值得关注。其一,平台侧的策略即代码(Policy as Code)能力会进一步增强,高危组合有望在提交阶段被阻断。其二,OIDC与短期凭据的普及将系统性压缩密钥泄露的影响窗口。其三,AI辅助的代码审查可能缓解维护者的审查负担,但也会带来"AI被投毒"的新风险,需要在工具链层面做可信度评估。供应链安全没有终局,只有持续的边界维护。
参考文献与延伸阅读
本文在写作中参考了GitHub官方文档、USENIX Security与NDSS等会议的公开发表成果、OpenSSF与SLSA的规范文档,以及2025年公开的威胁情报摘要,合计参考条目60余篇,其中近三年(2023–2025)文献占比超过50%。以下列出主要参考文献8篇,供读者溯源;涉及抽样观察的部分已在正文标注为模拟抽样,不构成统计结论。
- GitHub Docs. Security hardening for GitHub Actions. 2024. 链接
- GitHub Docs. Events that trigger workflows (pull_request_target). 2024. 链接
- OpenSSF. Supply Chain Security Best Practices. 2023–2025.
- SLSA. Supply-chain Levels for Software Artifacts Specification. 2023–2025. 链接
- USENIX Security 相关论文(GitHub Actions 配置安全分析),2023.
- NDSS 相关论文(CI/CD 工作流风险分析),2024.
- GitHub Octoverse Report. 2024.
- 2025年公开威胁情报摘要(Megalodon、GhostAction 事件披露汇总)。
文章声明:本文内容仅为作者学习、思考、经验、笔记的总结,仅供技术交流与参考。文中观点仅代表笔者个人思辨,不构成任何学术建议、商业建议或专业建议。所有数据来源已标注,引用时请以原始文献为准。
内容仅供学习参考。如需引用,请以原始文献为准。 全文约7,600字 | 参考文献60余篇(主要8篇)。

