starhaven-io/pinprick

GitHub: starhaven-io/pinprick

一款用于 GitHub Actions 供应链安全的 CLI 工具,支持固定 action SHA、检查更新、审计 runtime 获取模式并进行安全态势评分。

Stars: 10 | Forks: 1

# 针扎 [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/starhaven-io/pinprick/actions/workflows/ci.yml) [![License: AGPL-3.0-only](https://img.shields.io/badge/License-AGPL--3.0--only-blue.svg)](LICENSE) 一个用于 GitHub Actions 供应链安全的 CLI 工具。它将 action 引用固定为完整的 SHA,检查更新,审计绕过固定的 runtime 获取模式,并对仓库的安全态势进行评分。 名称由来:**pin**(SHA 固定)+ **prick**(一种小巧、敏锐的探测器,用于发现你供应链中的微小漏洞)。 ## 为什么使用 对于工作流文件的静态分析——例如模板注入、过度授权、凭据泄漏——请使用 [zizmor](https://github.com/zizmorcore/zizmor)。它非常出色。 pinprick 则接手静态分析无法触及的部分。为 action 固定 SHA 是最基本的操作,但即使是固定了的 action,也可能在 runtime 使用 `curl` 下载 `releases/latest`。pinprick 会固定你的 action 并保持其更新,同时审计 shell 脚本、JavaScript、Python 和 Dockerfiles 中未指定版本的 runtime 获取模式,最后为你提供一个可长期追踪的综合评分。 ## 安装 ### Homebrew ``` brew install starhaven-io/tap/pinprick ``` ### crates.io ``` cargo install pinprick ``` ### 从发布版安装 从 [GitHub Releases](https://github.com/starhaven-io/pinprick/releases) 下载预编译的二进制文件。 ### 从 git 安装(未发布的 HEAD) 尝试来自 `main` 分支的未发布更改: ``` cargo install --git https://github.com/starhaven-io/pinprick ``` ### GitHub Action 对于 CI 审计运行,请使用已发布的 [`starhaven-io/pinprick-action`](https://github.com/starhaven-io/pinprick-action)。它会安装一个固定版本的 pinprick 发行版,验证归档校验和,运行 `pinprick audit`,并可以上传 SARIF: ``` name: GitHub Actions supply chain audit on: push: branches: - main workflow_dispatch: permissions: {} jobs: pinprick: runs-on: ubuntu-24.04 permissions: security-events: write contents: read actions: read steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Run pinprick uses: starhaven-io/pinprick-action@834248de335b3a7c862e1c75b2c7a4dd7940c7d0 # v0.3.0 ``` 该 action 仅封装了 `pinprick audit`。对于 `pin`、`update` 和 `score`,请直接使用 CLI。如果需要控制台模式的 Pull request 反馈,请设置 `advanced-security: false`;完整配置矩阵请参阅该 action 的 README。 ## 用法 所有命令默认指向当前目录。传入一个路径可指定不同的仓库根目录。使用 `--json` 获取机器可读的输出。 ``` # 将 action 标签固定为完整的 SHA pinprick pin # 将更改写入文件 pinprick pin --write # 检查固定的 actions 是否有更新的 release(dry-run) pinprick update # 将更新写入文件 pinprick update --write # 仅检查特定的 action 或 org pinprick update --only actions/checkout # 审查绕过 pinning 的运行时 fetch 模式 pinprick audit # 针对特定的 repo pinprick audit /path/to/repo # 显示每个匹配的模式,包括允许的匹配 pinprick audit --verbose # 为 GitHub code scanning 输出 SARIF 2.1.0 pinprick audit --sarif > pinprick.sarif # 对 repository 的 Actions 供应链安全姿态进行评分 pinprick score # 以 JSON 或自包含的 HTML report 形式输出完整的评分报告 pinprick --json score pinprick score --html > report.html # 清除本地缓存的 audit 结果 pinprick clean # 生成 shell 自动补全 pinprick completions zsh ``` ### 固定 将 action 的 tag 引用解析为完整的 SHA(默认为试运行): ``` $ pinprick pin .github/workflows/ci.yml actions/checkout @v7 -> @9c091bb21b7c… # v7.0.0 actions/upload-artifact @v7 -> @043fb46d1a93… # v7.0.1 ! actions/checkout@v7 -- sliding tag, resolved to v7.0.0 ! Homebrew/actions/setup-homebrew@main -- branch ref — pin to a SHA manually Would pin 2 actions across 1 file (2 skipped) Run with --write to apply. ``` 像 `@v7` 这样的滚动 tag 会被解析为其确切版本。像 `@main` 这样的分支引用会被标记。 ### 更新 检查已固定的 action 是否有更新的发布版本(默认为试运行): ``` $ pinprick update .github/workflows/ci.yml actions/checkout v4.1.0 -> v7.0.0 1 update available. Run with --write to apply. ``` ### 审查 扫描绕过固定的 runtime 获取模式: ``` $ pinprick audit HIGH .github/workflows/ci.yml:42 action: some/action@abc123de curl -L "https://github.com/.../releases/latest/download/tool.tar.gz" curl fetching from a 'latest' URL — can change without notice 1 finding (1 high, 0 medium, 0 low) ``` 如果没有 GitHub token,audit 将扫描本地工作流的 `run:` 代码块以及通过 `uses: ./...` 引用的本地 action。如果提供了 token(通过 `GITHUB_TOKEN` 或 `gh auth`),它还会获取并扫描外部 action 的源代码——包括 JavaScript、Python、Dockerfiles 和复合 action 步骤。 传入 `--sarif` 可生成 SARIF 2.1.0 格式的结果以便上传至 [GitHub 代码扫描](https://docs.github.com/en/code-security/code-scanning)。传入 `--verbose` 可查看每一个匹配项,包括那些通过了版本检查或被受信任主机、数据格式、jq 管道或校验和规则降级为允许匹配的项目。 ### 评分 根据 [`docs/scoring.md`](docs/scoring.md) 中的公开版本化准则计算安全态势评级: ``` $ pinprick score pinprick score v0.7.0 rubric Grade: A (95 / 100) Findings (1 unique, 1 occurrences): medium -5 pin.sliding actions/checkout@v4 3 workflows scanned, 8 unique actions. Run with --json for the full report. ``` `source.unverified` 仅供参考:它会盘点基线受信任集合(`actions`、`github`)以及你配置的 `trusted-owners` 之外的发布者,但它不会扣除任何分数,也不会导致 CI 失败。仅当至少有一项发现扣除了分数时,`score` 才会以退出码 1 退出。使用 `pinprick score --html > report.html` 生成可分享的静态报告。 ### 配置 位于仓库根目录的 `.pinprick.toml` 文件(或全局的 `~/.config/pinprick/config.toml`)可自定义行为。所有键均为可选: ``` # 要报告的最低严重级别:"low"(默认)、"medium" 或 "high" severity = "low" # 从 pinprick.rs 获取已审查的 actions 目录,而不是仅使用 # 编译到 binary 中的 bundle。在 CI 中非常有用。 fetch-remote = false # 其未版本化 URL fetch 被降级为允许匹配的 Hosts。 # 不区分大小写的完全匹配。仅适用于未版本化 URL 规则。 trusted-hosts = ["crates.io"] # 在 source.unverified 评分备注中受信任的额外 GitHub 拥有者。 trusted-owners = ["my-org"] # 额外的文件扩展名(除了 .json/.yaml/.toml/.csv/.tsv/.xml/.md/.rst/.txt) # 作为数据格式处理以用于未版本化 URL 豁免。 extra-data-formats = ["proto"] [ignore] # 完全跳过这些 actions(基于 owner/repo 的前缀匹配)。 actions = ["actions/checkout"] # 抑制描述包含任何这些字符串的 findings。 patterns = [] ``` ### 清理 删除本地缓存的审计结果(`~/.cache/pinprick/audited/`): ``` $ pinprick clean Cache cleaned. ``` ## 审计检测的内容 | 类别 | 示例 | 严重性 | | ------------- | --------------------------------------------------------------------------------------------------- | -------- | | 管道传入 shell | `curl`/`wget` 传入 `sh`/`bash`/`python`(任何 URL) | High | | 管道传入 shell | `bash <(curl ...)`, `bash -c "$(curl ...)"`, `eval "$(curl ...)"` | High | | 管道传入 shell | PowerShell `iex (iwr ...)` / `Invoke-Expression (... DownloadString ...)` | High | | Shell | `curl`/`wget` 指向 `/latest/` URL | High | | Shell | `curl`/`wget` 指向无版本号的 URL | Medium | | Shell | 未指定 tag 的 `gh release download` | Medium | | Shell | 未固定 `--branch` 引用的 `git clone`(除非在 3 行内有 `git checkout ` 跟随) | Medium | | Shell | `go install @latest`,未固定的 `pip`/`npm`/`cargo install`/`gem install` | Low | | PowerShell | `Invoke-WebRequest`/`iwr`/`irm` 指向 `/latest/` URL | High | | PowerShell | `Invoke-WebRequest`/`iwr`/`irm` 指向无版本号的 URL | Medium | | JavaScript | `fetch()`/`axios`/`got` 指向 `/latest/` URL | High | | JavaScript | `exec("curl ...")`, `child_process` curl | High | | Python | `requests.get`/`urllib` 指向 `/latest/` URL | High | | Python | 使用 `subprocess` 执行 `curl`/`wget` | High | | Docker | `FROM :latest` 或无 tag | High | | Docker | `RUN curl`/`wget` 传入 shell | High | | Docker | 在 `RUN` 指令中的 `curl`/`wget` | Medium | | Docker | 源地址为 `http(s)://` URL 的 `ADD` | Medium | 即使 URL 带有版本号,管道传入 shell 的行为也会被标记——通过管道传输的 payload 绝不会写入磁盘,因此无法进行校验和验证,且带版本的路径仅固定了 URL 而非其内容。 当 URL 路径以数据格式扩展名(`.json`、`.yaml`、`.toml`、`.csv` 等)结尾,或者获取的内容被通过管道传给 `jq` 时,无版本号的 URL 规则不会触发——因为这些 payload 是作为数据被消耗,而不是被执行。这些匹配仅在 `--verbose` 模式下可见。(但管道传入 shell 的规则具有最高优先级,因此 `curl … | jq … | bash` 仍然会被标记。) 如果发现在随后的 3 行内跟有校验和验证(`sha256sum`、`gpg --verify` 等),则会被抑制并记录为允许的匹配项(在 `--verbose` 下显示)——因为校验和能确定性地检测篡改,所以下载内容已通过其本身固定。管道传入 shell 的发现不受此豁免;通过管道传输的 payload 绝不会写入磁盘以供校验和验证。 ## 退出码 | 代码 | 含义 | | ---- | ---------------------------------------------------------------------------------------------- | | 0 | 干净——无发现,无待处理更新 | | 1 | 存在发现、存在扣分,或有可用更新(update 试运行) | | 2 | 错误 | ## 构建 [justfile](https://github.com/casey/just) 提供了常用任务: ``` just build # Build the project just build-release # Build in release mode just test # Run tests just clippy # Run clippy just fmt # Format code just typos # Check for typos just audit # Audit GitHub Actions workflows just check # Run all checks (clippy, fmt, typos, zizmor, test, site) just install-hooks # Install git hooks: pre-push check + DCO sign-off (once per clone) ``` ## 贡献 提交必须遵循 [Conventional Commits](https://www.conventionalcommits.org/) 格式,并包含 DCO 签名认证(`git commit -s`)。每次克隆后运行一次 `just install-hooks` 即可启用 git hooks(包含推送前的 `just check` 和 DCO 签名强制执行)。 ## 许可证 本项目基于 [GNU Affero General Public License v3.0](LICENSE) (`AGPL-3.0-only`) 授权。 Copyright (C) 2026 Patrick Linnane
标签:GitHub Actions, 可视化界面, 自动笔记, 通知系统