mcpField
C
ci-cd-security

ci-cd-security

github.com/superagent-ai安全798.0k 次安装11 周前更新
代码仓库

关于 ci-cd-security

CI/CD Security Scanner 是用于审查 GitHub Actions 工作流 YAML 安全问题的技能。它按既定规则检测并给出严重级别与具体修订建议,无需安装或运行扫描工具。

主要功能

  1. 01审查工作流 YAML
  2. 02检查触发器和权限
  3. 03检测 Action 固定版本风险
  4. 04查找 Shell 模板注入
  5. 05检查缓存和制品处理
  6. 06提供具体改写建议

适用场景

  1. 01合并前审查工作流
  2. 02审计发布工作流
  3. 03检查 GitHub Actions 安全问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/superagent-ai/skills --skill ci-cd-security

安装后在 Claude Code 中输入/ci-cd-security确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
checklist.md
patterns.md
triggers.md

详细介绍

CI/CD 安全扫描器

此技能会将模型转变为工作流 YAML 扫描器。读取文件,逐一检查检测规则,并报告发现的问题、严重性和具体的改写方案。无需安装工具,也无需运行命令——分析过程就是模型读取 YAML。

这些规则体现了 Astral、OpenSSF、GitHub Security Lab、Chainguard 和 zizmor 审计规则集目前达成的共识。目标是在无需运行这些工具的情况下,标记出它们也会标记的相同模式。

心智模型

每个工作流都处于一个 2×2 矩阵中:特权与非特权,以及受信任代码与不受信任代码。只有一个单元格会导致遭到入侵:特权工作流运行不受信任的代码。以下规则用于检测工作流何时落入该单元格。

  • 特权 = 拥有密钥、写入权限,或会生成敏感构件(发布、部署、评论、标签)。
  • 不受信任代码 = 分叉 PR 作者能够影响的任何内容:PR 源代码、PR 标题、PR 正文、提交消息、分支名称、工作流读取的文件、缓存、由其他不受信任的工作流生成的构件。

无法确定某个值是否受信任时,就将其视为不受信任。误报的代价是一条代码审查意见;漏报的代价则是供应链遭到入侵。

扫描流程

对于用户提供的每个工作流文件,按顺序逐一执行以下检查。每项检查对应一类攻击。

第 1 项:危险触发器

检查 on: 块。立即标记:

  • pull_request_target — P0,除非有明确的合理理由。该触发器会使用密钥和写入权限运行,分叉 PR 可以触发它。这是典型的 pwn-request 攻击途径。即使没有对 head 执行 checkout,攻击者的输入仍会出现在 PR 标题、分支名称和提交消息中,并被插值。
  • workflow_run — P0。问题与 pull_request_target 相同,但会通过链式 pull_request 工作流的构件或元数据间接触发。
  • issue_comment、issues、pull_request_review、pull_request_review_comment — P1。它们会使用密钥运行,任何能够发表评论的人都可以触发。只有在工作流不会将用户控制的字段插值到 shell 模板中时才安全。
  • 使用宽泛通配符的 push(branches: ['*'] 或未设置分支筛选条件)— P2。攻击者一旦合并 PR,就可以通过推送后续分支来触发特权工作流。

对于每项发现:指出触发器,解释它在此工作流上下文中的危险之处,并提出改写方案(通常改为 pull_request,有时应拆分为两个工作流,有时则应说明“此需求需要 GitHub App,而非 Actions”)。

第 2 项:权限

检查工作流级和任务级的 permissions: 块。

  • 缺少顶层 permissions: 块 — P1。默认的 GITHUB_TOKEN 权限取决于仓库和组织设置;在较旧的仓库中可能是 write-all。标记并建议:“在顶层添加 permissions: {},按任务授予权限。”
  • 任何位置的 permissions: write-all — P1。
  • 任务级 permissions: 授予的权限超出任务明确所需 — P2。例如,只运行测试的任务设置了 contents: write。建议遵循最小权限原则。
  • 与第 1 项中的危险触发器同时出现 — 将严重性提高一级。
第 3 轮:操作固定版本

检查每一行 uses:。

  • 固定到标签(uses: actions/checkout@v4、@v4.1.1)—— P1。标签可变;如果攻击者攻陷操作仓库,就能强制推送该标签。
  • 固定到分支(uses: actions/checkout@main)—— P0。比固定到标签更糟;该分支上的任何提交都会立即生效。
  • 固定到 SHA,但没有版本注释—— P3 风格问题。建议使用格式 uses: owner/action@<sha> # v4.1.1,让审查固定版本更新时一目了然。
  • 固定到看起来异常的 SHA(第三方操作、可疑所有者、最近创建的仓库)——标记为需手动验证;没有 GitHub API 就无法确认是否为冒名提交,但值得指出。

对于固定到标签/分支的情况,改写方式始终是:替换为所需版本的完整 40 个字符提交 SHA,并添加 # vX.Y.Z 注释。

第 4 轮:Shell 注入(模板注入)

对于每个 run: 块,检查 ${{ ... }} 替换。

常量或非攻击者可控的值没有问题(例如 ${{ matrix.os }}、${{ secrets.MY_TOKEN }},不过在某些上下文中,即使这样也有风险)。危险字段包括:

  • github.event.pull_request.title、body、head.ref、head.sha、head.label
  • github.event.issue.title、body
  • github.event.comment.body、user.login
  • github.event.review.body
  • github.head_ref
  • github.event.workflow_run.head_branch、head_commit.message
  • github.event.commits.*.message、author.name、author.email
  • 如果工作流在特权上下文中运行,来自 workflow_dispatch 的任何 inputs.*
  • 最终来源于 actions/github-script、下载的制品或外部 API 响应的任何字段

**检测规则:**如果 run: 块直接在脚本正文中包含 ${{ github.event.* }} 或 ${{ github.head_ref }},则这是 P0 模板注入。修复方式始终是:

# vulnerable
- run: echo "Branch is ${{ github.head_ref }}"

# safe
- env:
    BRANCH: ${{ github.head_ref }}
  run: echo "Branch is $BRANCH"

还需标记(P1):

  • echo "VAR=${{ untrusted }}" >> $GITHUB_ENV——环境文件注入。攻击者可以通过加入换行符跳出变量。
  • echo "::set-env name=VAR::${{ untrusted }}"——已弃用的工作流命令,问题相同。
  • 将不可信文件 cat 到 $GITHUB_ENV 或 $GITHUB_OUTPUT 的内联脚本。
第 5 轮:不可信检出

对于每个 actions/checkout 步骤:

  • 在由 pull_request_target 或 workflow_run 触发的工作流中使用 ref: ${{ github.event.pull_request.head.sha }}(或 head.ref)—— P0。这是典型的恶意请求:在特权上下文中运行来自分叉仓库作者的代码。
  • 在不需要推送回去的工作流中使用 persist-credentials: true(默认值)—— P2。除非工作流明确需要嵌入的令牌,否则建议设为 persist-credentials: false。
第 6 轮:特权上下文中的缓存

对于每个使用 cache: 输入的步骤(最常见于 actions/setup-node、setup-python、setup-go、setup-java 或直接使用 actions/cache):

  • 发布或推送工作流中的缓存—— P0。默认分支上的任何其他工作流都可能造成缓存投毒,将恶意构建输入带入发布流程。Trivy 和 TeamPCP 攻击都利用了这一途径。
  • 处理机密信息的工作流中的缓存—— P1。
  • 缓存键未进行作用域限定,无法阻止不可信 PR 工作流与默认分支构建写入相同的键—— P2。

发布工作流的改写方式:完全移除 cache:,并添加注释说明原因(例如 # Do not cache: see https://github.com/actions/setup-node/issues/1445)。

第 7 轮:工件携带的注入

如果工作流会下载其他工作流的工件(actions/download-artifact、dawidd6/action-download-artifact 等):

  • 未验证就将工件内容用于 run: 或 $GITHUB_ENV — 如果生成工件的工作流在不受信任的代码上运行(例如来自分叉仓库的 pull_request),则为 P0。攻击者可以在工件中放入任意内容。
  • 建议进行严格验证:如果工件应当是 PR 编号,则拒绝任何非数字内容。如果是结构化文件,则解析并验证其架构。
第 8 轮:发布专属加固

如果工作流看起来是发布/推送工作流(发布到 npm、PyPI、crates.io、Docker 注册表;创建 GitHub 发布;推送标签):

  • 发布作业未声明 environment: — P1。发布凭据应限定在部署环境中,而不是仓库/组织密钥。
  • 使用长期有效的注册表令牌(secrets.NPM_TOKEN、secrets.PYPI_TOKEN),而不是 OIDC/Trusted Publishing — P2。建议使用相应注册表的 OIDC 方式。
  • 未生成证明(actions/attest-build-provenance、npm 的 --provenance、PyPI 的 PEP 740)— P3 加固建议,不属于漏洞。
  • 在发布流程的任何环节使用缓存 — P0,参见第 6 轮。
第 9 轮:自托管运行器

如果工作流的 runs-on: 使用的不是 GitHub 托管的运行器(ubuntu-*、windows-*、macos-*):

  • 分叉 PR 可访问自托管运行器 — P0。自托管运行器会在作业之间共享状态,并曾导致严重的安全事件(PyTorch)。标记出来,手动审查运行器的访问范围。
  • 这不在默认威胁模型范围内 — 记录该发现,并建议用户在 GitHub 设置中确认运行器限制。

发现项格式

按以下结构报告每项发现。按严重程度分组,P0 优先。

[P0] template-injection in .github/workflows/ci.yml:23
  Run block interpolates github.event.pull_request.title directly into shell.
  An attacker controls the PR title and can execute arbitrary code in the
  workflow context, which has access to GITHUB_TOKEN.

  Vulnerable:
    - run: echo "Title: ${{ github.event.pull_request.title }}"

  Fix:
    - env:
        TITLE: ${{ github.event.pull_request.title }}
      run: echo "Title: $TITLE"

如果用户粘贴的是没有文件名的原始 YAML,请将其称为“该工作流”,并使用片段中的行号。

严重程度等级

  • P0 — 可立即利用,无需组合其他条件。分叉 PR 作者或任意 GitHub 用户可危及密钥、仓库内容或发布。阻止合并。
  • P1 — 需要额外一步才能利用(例如需要与其他发现项组合,或需要维护者操作失误)。如果在发布流程中发现,则阻止合并。
  • P2 — 加固缺口。无法直接利用,但若与未来的漏洞组合,会扩大影响范围。在常规审查周期内修复。
  • P3 — 风格或一致性问题。为提高可读性,值得修复;不影响安全。

工作流看起来没有问题时

完成全部九轮检查后,如果没有发现任何问题:

  1. 明确说明这一点。“未发现违反标准规则集的问题。”
  2. 说明未检查的内容:组织级设置(默认令牌权限、规则集强制执行、双因素认证)、仓库级设置(分支保护、标签保护、不可变发布)、操作源代码(固定版本的操作本身是否会在运行时安装可变二进制文件),以及依赖项的运行时行为。
  3. 建议用户检查 references/checklist.md 中的“按仓库”和“按组织”条目 — 这些项目需要访问 GitHub 设置,无法通过工作流 YAML 检查。

工作流扫描干净,不代表整体安全状况良好。

参考文件

  • references/triggers.md — 包含每种 GitHub Actions 触发器的详细表格,说明各自的危险或安全之处,以及人们使用危险触发器实现常见需求时的安全模式。当工作流使用用户特别询问的触发器,或需要进一步解释某个触发器为何危险,而不只是给出一句话总结时,请阅读此文件。
  • references/checklist.md — 按工作流/仓库/组织划分的清单。当用户要求全面审计、需要扫描多个仓库或进行大规模分流时很有用。包含分流优先级顺序。
  • references/patterns.md — 常见的“如何安全地实现 X”模式。当用户询问如何替换被标记的危险模式,而不只是识别它们时,请阅读此文件。

此技能不会做什么

  • 它不会安装 zizmor、pinact 或任何其他工具。扫描是由模型读取 YAML 并应用这些规则来完成的。
  • 除非用户明确询问“我应该运行哪些工具”,否则它不会建议安装工具。即使用户这样询问,也会直接指出相关模式——此技能中的规则就是这些工具所编码的审计规则。
  • 它不会用缓解措施掩盖 pull_request_target 发现的问题。如果工作流使用了该触发器,且未置于 GitHub App 之后,这就是一个未解决的问题。
  • 它不会在不说明检查了什么、没检查什么的情况下告诉用户一切正常。对于“这样安全吗”这类问题,默认回答是“以下是扫描涵盖的内容,以及扫描发现的问题。”

评论

支持 Markdown