关于 actionpin
actionpin 审计 GitHub Actions 工作流和复合 Action 引用,并生成将可变引用固定到提交 SHA 的计划。它面向需要基于证据进行供应链检查的维护者和 Agent,同时提供命令行与 MCP 接口。
主要功能
- 01按固定状态分类 Action 引用
- 02报告工作流审计结论
- 03生成 SHA 固定计划
- 04在修复模式中改写可解析的引用
- 05扫描指定的复合 Action 定义
适用场景
- 01检查仓库的 Actions 供应链风险
- 02发布前审查工作流中的 Action
- 03预览 SHA 更改而不修改文件
MCP 相关
MCP 服务提供 `audit_workflows` 以审计仓库,以及 `pin_plan` 以生成 SHA 固定计划而不修改文件。可通过文档所示的 `npx` 配置由 MCP 客户端调用。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
2收录的工具列表 · 更新于 2 天前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| path | string | 是 | 仓库根目录、.github/workflows 目录或单个工作流文件 |
详细介绍
actionpin
版本标签是一种任何人都能破坏的承诺。提交 SHA 则不是。
GitHub Actions 是可执行的供应链。当工作流写着 uses: actions/checkout@v4 时,该标签可能会变动。当第三方操作的标签被移到遭到入侵的提交时,所有信任该标签的工作流都会使用该工作流的令牌运行新代码。固定到完整的 40 个字符提交 SHA 是标准的防护措施,但几乎没人能始终如一地做到,因为手动处理每个工作流既繁琐又容易出错。
actionpin 会审计仓库中的每个工作流,准确报告哪些操作已固定、哪些没有,还可以替你将未固定的操作改写为 SHA,并保留原始标签作为注释,让固定后的引用仍然易读。
为什么要开发这个工具
GitHub Actions 是过去一年中已确认的第二大开源供应链攻击面,仅次于 npm。维护者账户和操作标签曾遭劫持,而可变标签意味着 CI 今天运行的代码不一定是你审查过的代码。解决方法早已明确,也很简单:固定到 SHA。actionpin 将这个简单的修复变成一条命令,并让 Agent 可以通过 MCP 调用审计,这样它就能在贡献和发布工作流中运行,而不只是供人手动执行。
检查内容
针对 .github/workflows/*.yml 和 *.yaml 中的每一行 uses::
| 分类 | 含义 |
|---|---|
PINNED | 引用是完整的 40 个字符提交 SHA(或容器镜像的完整 sha256 或 sha512 内容摘要) |
MUTABLE | 引用是版本标签,例如 v4 或 v4.2.1。标签可能会变动。 |
BRANCH | 引用是分支,例如 main、master 或 HEAD。分支本来就会变动。 |
LOCAL | 本地路径,例如 ./local/action。永远不会被改写。 |
所有者类型:official 表示由 actions 或 github 所有的操作,third-party 表示其他所有者,docker 表示 docker:// 容器操作,local 表示本地路径。
判定规则:
- PINNED:所有非本地操作都固定到了完整 SHA。退出代码为 0。
- EXPOSED:至少有一个第三方或容器操作使用了可变标签或分支。退出代码为 1。
- PARTIAL:只有官方操作未固定。退出代码为 1。
- 用法错误:退出代码为 2。
修复模式会通过 GitHub API 将标签或分支解析为提交 SHA,并将该行改写为 owner/repo@<sha> # <original ref>,同时保留缩进和其他所有内容。文件按行编辑;不会解析再重新输出 YAML,因此格式得以保留。如果无法解析某个引用,该行将保持不变,并报告失败。Docker 镜像引用会报告,但不会改写;请将其固定到摘要。
用法
CLI
# Audit the current repo
npx @reneturcios/actionpin audit
# Audit another path, machine readable
npx @reneturcios/actionpin audit ~/src/my-repo --json
# Preview the pinning plan without writing
npx @reneturcios/actionpin fix --dry-run
# Pin everything resolvable, in place
npx @reneturcios/actionpin fix
输出示例:
actionpin audit .: EXPOSED
workflows: 2, actions: 6, pinned: 1, mutable: 4, branch: 1, local: 0
[PINNED] .github/workflows/ci.yml:12 actions/checkout@0123456789abcdef0123456789abcdef01234567 (official)
[MUTABLE] .github/workflows/ci.yml:13 actions/setup-node@v4 (official)
[MUTABLE] .github/workflows/publish.yml:20 docker/build-push-action@v6 (third-party)
EXPOSED: at least one third-party or container action uses a mutable tag or branch ref.
在修复模式下设置 GITHUB_TOKEN,即可获得更高的 GitHub API 认证速率限制。审计无需凭据,也无需联网。
MCP 服务
添加到 MCP 客户端配置中:
{
"mcpServers": {
"actionpin": {
"command": "npx",
"args": ["-y", "@reneturcios/actionpin", "mcp"]
}
}
}
工具:
audit_workflows:审计仓库路径,并返回每个操作的分类、所有者类型、文件和行号,以及判定结果。pin_plan:返回指定路径的完整 SHA 固定计划,不会写入任何文件。
Docker
docker run -i ghcr.io/cloudcorprecords/actionpin mcp
docker run --rm -v "$PWD:/work" -w /work ghcr.io/cloudcorprecords/actionpin audit .
限制(由加固测试套件验证)
这些行为都是有意设计、经过测试且明确说明的,确保绿色报告不会被误读:
- 范围包括工作流文件和仓库内的复合操作。 目录审计会扫描
.github/workflows/*.yml和*.yaml,也会扫描位于被扫描仓库内的操作定义:根目录下的action.yml或action.yaml(即使工作流未引用,也会扫描被扫描仓库自身发布的操作定义;这是发布操作的仓库通常采用的结构)、.github/actions/下的每个action.yml和action.yaml,以及工作流通过./使用路径引用的任何本地操作目录。系统会盘点这些文件的内部步骤,并像工作流步骤一样进行分类、标记为复合操作引用,并以相同方式决定审计结论。根操作中引用仓库自身操作(通过./或已发布的名称)的步骤只扫描一次;自引用不会递归扫描或重复计数。报告会列出扫描了多少个复合操作文件,并指出仓库其他位置发现但未扫描的操作文件(不是根操作文件、不在.github/actions下,且未被工作流引用),因此绝不会暗示审计覆盖范围超出实际范围。直接传入action.yml文件路径也会像解析其他文件一样对其进行解析。作业级uses:行(可复用工作流调用)也在范围内,会像步骤级调用一样进行审计和固定。 - 前缀格式错误的本地路径仍是本地路径,并会保留拼写错误。 像
$/.github/actions/issues-helper这样的uses值(真实仓库中确实存在这种把./前缀中的点替换成多余字符的情况)并未指定远程仓库,因此会被归类为 LOCAL,修复模式下绝不会通过 GitHub API 解析。该发现会附带说明,指出该前缀不是有效的远程引用,看起来像本地路径拼写错误;因为即使它不是固定版本方面的风险,这种拼写在目标仓库中仍是实际缺陷。 - 动态值会被跳过,而不是靠猜测处理。 含有 GitHub 表达式(
${{ matrix.action }}、actions/checkout@${{ inputs.ref }})的uses:值会在运行时确定,无法静态分类。actionpin 会将其完全跳过,而不是凭空编造操作或审计结论。如果工作流会动态构造操作,请审计矩阵值或输入值本身。 - 短 SHA 永远不会被视为已固定。 只有完整的 40 个字符 SHA(或
docker://镜像的完整 sha256 或 sha512 摘要)才会被标记为PINNED。像abc1234这样的短 SHA 会归入BRANCH类别,并导致审计结论不通过,因为短前缀不是稳定的固定版本。 - 容器标签永远不会被视为已固定。 对于
docker://镜像,只有完整的内容摘要才算固定版本。由 40 个十六进制字符组成的标签仍然是可重新推送的标签;截断的摘要或非十六进制摘要也不是真正的摘要;两者都会被标记为MUTABLE。 - YAML 锚点和别名会在文件内解析。 GitHub Actions 自 2025 年 9 月起支持它们。带锚点的
uses:值会根据其值本身分类;别名行(uses: *name或步骤别名- *name)会继承锚点分类并标记为viaAlias;锚点必须像 GitHub 一样在同一文件中先定义后使用。修复模式会固定锚点定义行,绝不会改写别名行。 - 包含斜杠的引用(
release/v1、feature/new-thing)会被归入BRANCH类别,标记为可变引用。修复模式会保留斜杠,通过 git-ref API 解析它们。 - 逐行解析。 不会检测单行流式 YAML(例如
steps: [{ uses: actions/checkout@v4 }]),也会忽略注释掉的uses:行。 - Docker 引用只报告,不改写。 请自行通过摘要固定容器镜像;修复模式会保持
docker://行不变,并在计划中说明这一点。 - 没有可审计内容时会明确说明。 如果被审计路径下没有工作流和操作定义,报告会附带一条普通说明,指出没有任何内容经过审计。审计结论根据零个操作计算,不表示该路径已通过审计。如果仓库中唯一可审计的内容是根操作定义,系统会审计该定义,并且不会附带无可审计内容的说明。
- 目标缺失或类型错误属于用法错误,不会给出审计结论。 路径不存在时会以错误状态码 2 退出,在标准错误输出中显示错误且不生成报告;这与 hookcheck 和 injectcheck 的行为一致,因此在 CI 中接入时,路径拼写错误会导致失败,而不会因无内容可审计而误报通过。传入单个文件时,只有
.yml或.yaml文件会被审计;其他任何普通文件(如package.json、README)都会导致用法错误,状态码为 2,而不会被当作工作流。既不是文件也不是目录的目标同样属于用法错误。 - 绝不会打开非普通文件。 发现过程会先对每个候选文件执行 stat,再打开文件;任何非普通文件都会被跳过。因此,即使有人在工作流或操作文件路径放置 FIFO(命名管道)、套接字或设备,也不会导致审计挂起或阻塞;周围的普通文件仍会正常审计。
- 管道输出要么完整送达,要么运行失败。 报告通过支持刷新机制的输出路径写入,因此无论报告多大,都能通过管道完整送达,符合 Agent、脚本和 CI 的使用方式。如果读取端在报告送达前关闭管道,actionpin 会以状态码 141 退出(在 Shell 中等同于 SIGPIPE),而不会在输出无人接收的情况下报告成功。此问题已在 0.1.2 中修复;Round 1 的真实仓库验证发现,报告恰好在 65,536 字节处被静默截断,但退出状态码仍为成功。
工具包
actionpin 是同一作者开发的一系列小型单用途开发工具之一,它们采用相同的构建方式:一个二进制文件同时提供 CLI 和 MCP 服务,执行带有证据的确定性检查,不编造答案。
- greenlight:开源贡献的提交前检查:这个问题真的可以合并吗?
- shipcheck:验证某个版本是否确实已发布到 git、npm、MCP Registry 和 GHCR。
- hookcheck:审计代码库中的自动执行钩子,检查是否存在持久化类风险。
开发
npm install
npm run build
npm test
测试使用注入了 fetch 函数的固定样例运行;没有任何测试会访问网络。
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。