starhaven-io/pinprick
GitHub: starhaven-io/pinprick
一款用于 GitHub Actions 供应链安全的 CLI 工具,支持固定 action SHA、检查更新、审计 runtime 获取模式并进行安全态势评分。
Stars: 10 | Forks: 1
# 针扎
[](https://github.com/starhaven-io/pinprick/actions/workflows/ci.yml)
[](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, 可视化界面, 自动笔记, 通知系统