kdairatchi/ghactor

GitHub: kdairatchi/ghactor

一款面向 GitHub Actions 的安全优先 CLI,通过规则检查与自动化修复保障工作流安全。

Stars: 0 | Forks: 0

ghactor # ghactor **安全优先的 GitHub Actions CLI 工具。** Lint、修复、SHA-pin、审计已知漏洞 action、扫描机密、试运行并检查最近的运行记录——一切尽在一个二进制文件中。

Release Go License CI

## 为什么选择 ghactor GitHub Actions 是一个供应链雷区。浮动的 `@main` 标签会在你不知情时发生偏移。缺失的 `permissions:` 块默认为 *write-all*。一个粗心的 `pull_request_target` 加上对 PR head 的 checkout 就会导致完整的仓库接管。大多数团队会在 review 时发现这些问题——如果他们进行了 review 的话。 ghactor 会在 workflow 发布前将其拦截: - **`lint`** 封装了 `actionlint`,并包含 31 条专注于实际被利用场景的安全规则(pwn-request、注入、过期的 pin、cache 投毒、env 走私、tj-actions 式的污点传播、弃用的 runner、跨组织的 `secrets: inherit`)。 - **`pin`** 将每个 `uses:` 重写为 40 字符的 SHA,并将原始标签作为注释保留,因此升级时仍然可以进行干净的 diff。 - **`fix`** 可一次性自动添加 `permissions:`、job 超时、shell pinning 以及可选的 SHA pinning。 - **`gen`** 脚手架可生成开箱即用、固定到 SHA 的强化版 workflow YAML(CI、CodeQL、release + attest-build-provenance、Dependabot)。 - **`audit`** 将固定的 action 与 GitHub Advisory Database 进行交叉检查,标记已归档的仓库,并捕获 404 的 action。 - **`doctor`** 为整个仓库提供 0-100 的安全态势评分。 一个二进制文件,零 Node runtime,可作为 composite Action 或 CLI 提供。 ## 安装 ``` go install github.com/kdairatchi/ghactor/cmd/ghactor@latest ``` `pin`、`update` 和 `audit` 需要 `gh` (GitHub CLI)。`trial` 需要 [`act`](https://github.com/nektos/act)。`trail` 直接使用 GitHub REST(读取 `GITHUB_TOKEN` / `GHACTOR_GITHUB_TOKEN`;未设置 token 时回退到 `gh`)。通过 `GITHUB_API_URL` 支持 GHES。 ## 它是如何协同工作的 ``` flowchart LR W["workflows/*.yml"] --> L["lint"] W --> F["fix"] W --> P["pin"] W --> T["trial"] W --> A["audit"] W --> SC["secrets"] L --> S{"SARIF?"} S -->|yes| GH["GitHub Security tab"] S -->|no| C["CLI output"] F -->|--pin| P P --> W T --> ACT["act local runner"] classDef cmd fill:#0d0d15,stroke:#f0c040,color:#f0c040 classDef sink fill:#0d0d15,stroke:#06b6d4,color:#06b6d4 classDef src fill:#111119,stroke:#9ca3af,color:#fff class L,F,P,T,A,SC cmd class GH,C,ACT sink class W,S src ``` ## 命令 | 命令 | 功能描述 | |-------------|---------------------------------------------------------------------------------| | `lint` | 运行 rhysd/actionlint **以及** ghactor 的安全规则 (GHA001–GHA032) | | `pin` | 通过 `gh api` 将 `uses: owner/repo@tag` 重写为 `@<40-char SHA> # tag` | | `fix` | 添加缺失的 `permissions:`,注入 `timeout-minutes:`,固定 shells/containers | | `update` | 显示哪些 action 有更新的版本 (通过 `gh api .../releases/latest`) | | `audit` | 将固定的 action 与 GHSA 进行交叉检查;标记已归档和 404 的仓库 | | `secrets` | Gitleaks 风格的 workflow YAML 扫描,查找泄露的 API key、token 和私钥 | | `baseline` | 为当前发现生成快照,以便 CI 仅在出现*新*问题时失败(遗留代码采纳) | | `trial` | Shell 到 `act` 以在本地运行 workflow | | `trail` | 美化打印最近的运行记录,包含成功/失败/平均持续时间 | | `doctor` | 生成带有 0–100 分数的全仓库健康报告 | | `rules` | 列出所有 ghactor 规则 | | `explain` | 打印单条规则的卡片(描述、修复方案、修复示例) | | `gen` | 生成强化版的 workflow(ci-go, ci-node, codeql, release-goreleaser, …) | | `watch` | 在 YAML 更改时重新进行 lint(基于 fsnotify,支持 debounce) | | `sarifdiff` | 对比两个 SARIF 报告 — 呈现 baseline 和 head 之间的新发现 | ## 规则 这 31 条规则分为四个类别——**注入**、**偏移 / 供应链**、**最小权限**和**发布完整性**。`lint` 默认运行所有这些规则。 ``` flowchart TB subgraph INJ["injection"] direction LR GHA003["GHA003
pwn-request
error"] GHA004["GHA004
expression injection
error"] GHA018["GHA018
env smuggling
error"] GHA026["GHA026
tj-actions taint
error"] GHA031["GHA031
obfuscated run
warn"] GHA032["GHA032
spoofable actor
warn"] end subgraph PIN["drift / supply-chain"] direction LR GHA001["GHA001
tag not SHA
warn"] GHA006["GHA006
floating ref
warn"] GHA008["GHA008
stale SHA
warn"] GHA009["GHA009
reusable wf ref
error / warn"] GHA023["GHA023
container tag
warn"] GHA024["GHA024
deprecated version
warn / error"] end subgraph LP["least-privilege"] direction LR GHA002["GHA002
no permissions
warn"] GHA011["GHA011
persist-creds prt
error"] GHA015["GHA015
prt artifact upload
error"] GHA019["GHA019
id-token scope
warn"] GHA020["GHA020
write-all
warn"] GHA029["GHA029
cross-org inherit
warn"] end subgraph REL["release integrity"] direction LR GHA017["GHA017
no concurrency
info"] GHA025["GHA025
deprecated runner
warn / error"] GHA027["GHA027
publish cache
warn"] GHA028["GHA028
no provenance
info"] end classDef err fill:#1a0d0d,stroke:#ef4444,color:#ef4444 classDef warn fill:#14110a,stroke:#f0c040,color:#f0c040 classDef info fill:#0a1419,stroke:#06b6d4,color:#06b6d4 class GHA003,GHA004,GHA009,GHA011,GHA015,GHA018,GHA026 err class GHA001,GHA002,GHA006,GHA008,GHA019,GHA020,GHA023,GHA024,GHA025,GHA027,GHA029,GHA031,GHA032 warn class GHA017,GHA028 info ``` 完整详情:`ghactor rules --verbose` 或 `ghactor explain GHA004`。 | ID | 严重性 | 检查内容 | |---------|------------|------------------------------------------------------------------------------------------------------| | GHA001 | warn | Action 使用标签而不是 40 字符的 SHA 进行固定 | | GHA002 | warn | 没有 `permissions:` 块 — 默认为 write-all | | GHA003 | error | `pull_request_target` + checkout 到 PR head ref (pwn-request 模式) | | GHA004 | error | 不受信任的 `${{ github.event.* }}` 被插入到 `run:` 中(注入) | | GHA005 | info | Job 没有 `timeout-minutes:`(默认 360) | | GHA006 | warn | Action 固定到浮动 ref (`@main`, `@master`, `@latest`, `@HEAD`) | | GHA007 | warn | `uses:` 完全没有 `@ref` | | GHA008 | warn | 固定的 SHA 已过期 — `# tag` 注释解析为不同的 SHA;在 opts 中需要 `Resolver` | | GHA009 | error/warn | 可复用 workflow 的 ref 是浮动分支 (error) 或 semver 标签而不是 SHA (warning) | | GHA010 | error | Action 匹配 `.ghactor.yml` 中的 `deny_actions` glob 模式 | | GHA011 | error | 在 `pull_request_target` 下使用带有 `persist-credentials: true` 的 `actions/checkout`(token 窃取) | | GHA012 | warn | `run:` 包含 `curl \| sh` / `wget \| bash` 模式(远程代码执行) | | GHA013 | warn | `actions/cache` key 派生自不受信任的 `github.event.*` / `inputs.*`(cache 投毒) | | GHA014 | error | 遗留的 `::set-env` / `::add-path` / `ACTIONS_ALLOW_UNSECURE_COMMANDS=true`(自 2020 年 11 月起已禁用) | | GHA015 | error | 在 `pull_request_target` 下的 `actions/upload-artifact`(artifact 提取机密) | | GHA016 | warn | 没有限制性标签的 `self-hosted` runner — fork PR 可以将其作为目标 | | GHA017 | info | 部署/发布 workflow 没有 `concurrency:` 块(重复部署竞争) | | GHA018 | error | 不受信任的表达式被写入 `$GITHUB_ENV` / `$GITHUB_OUTPUT`(env 走私) | | GHA019 | warn | Job 请求 `id-token: write` — 验证 OIDC 信任策略是否限制了 `sub`/`aud` | | GHA020 | warn | `permissions:` 授予 `write-all` / 广泛的 `write` 权限 — 缩小到最小权限 | | GHA021 | warn | 可复用 workflow 的 `workflow_call` 输入未指定类型 — 固定 `type:` 以防止类型强制转换 | | GHA022 | info | Step 的 `run:` 块没有 `shell:` — 默认设置在不同 runner 间会发生偏移 (bash vs. pwsh) | | GHA023 | warn | `container:` / `services.*.image:` 通过标签而不是 digest 固定 — 重写为 `image@sha256:…` | | GHA024 | warn/error | Action 固定到已弃用的主版本 (例如 `actions/upload-artifact@v3` — 自 2025-01-30 起强制失败) | | GHA025 | warn/error | Job 使用已弃用/已移除的 runner 镜像 (`ubuntu-20.04`, `macos-12`, `windows-2019`, …) | | GHA026 | error | `GHA004` 提升 — 来自已知受污染 action 的不可信 `steps..outputs.*` (CVE-2023-27529) | | GHA027 | warn | 发布/发布 workflow 恢复了构建缓存 — 特性分支缓存对 release 造成毒害 | | GHA028 | info | 发布/发布 workflow 没有 `actions/attest-build-provenance` step | | GHA029 | warn | 来自外部组织且带有 `secrets: inherit` 的可复用 workflow — 转发所有仓库机密 | | GHA030 | error | Action 不在 `.ghactor.yml` 的 `allow_actions:` 白名单中(可选择加入 — 仅在设置了列表时触发) | | GHA031 | warn | `run:` 块混淆 — base64 解码通过管道传输给 shell,`eval` 了 `${{ }}`,`curl \| bash` 链 | | GHA032 | warn | Job 的 `if:` 门控基于可伪造的 `github.actor` / `[bot]` 身份匹配 | ## 示例 ``` # Lint 所有内容 ghactor lint # 仅保留 ghactor 规则,跳过 timeouts 噪音 ghactor lint --only-ghactor --disable GHA005 # 通过管道传递给 jq 以用于 CI 仪表板 ghactor lint --json | jq '.[] | select(.severity=="error")' ghactor lint --baseline .ghactor/baseline.json # suppress known findings; report NEW only ghactor lint --resolve-drift # online: GHA008 stale-SHA check via gh api ghactor lint --junit ghactor.junit.xml # JUnit XML (Jenkins/GitLab CI) ghactor lint --github # GH Actions workflow annotations on stdout ghactor lint --actions . # also lint composite action.yml files ghactor lint --since origin/main # only lint files changed in this PR ghactor explain GHA004 # print full card for a rule ghactor explain GHA020 --json # machine-readable rule metadata ghactor gen # list available templates ghactor gen ci-go -o .github/workflows/ci.yml # scaffold a hardened Go CI workflow ghactor gen attest-release --force # SLSA build-provenance release ghactor gen dependabot -o .github/dependabot.yml ghactor watch # auto-re-lint on YAML change ghactor watch --clear --debounce 150ms # fast dev loop # 预览 / 应用 SHA pinning(缓存在 .ghactor/cache.json,30天 TTL) ghactor pin --dry-run ghactor pin # 一次性强化:perms + 15分钟 timeouts + pin 每个 action + shells + containers ghactor fix --all --timeout 15 ghactor audit # check pinned actions against GHSA ghactor audit --offline --json # static deny list only, machine-readable ghactor audit --no-archival --no-missing # skip archived/404 repo checks ghactor secrets --entropy # scan + high-entropy string detection ghactor baseline create # snapshot current findings ghactor baseline status --fail-on error # CI-mode: fail only on NEW findings ghactor baseline prune # drop fingerprints that no longer match ghactor doctor # scored health report ghactor trail -n 50 # last 50 runs ghactor trial -e pull_request # run locally via act ``` ## 退出代码 当发现的问题达到或超过 `--fail-on` 指定的级别(默认为 `warning`)时,`lint` 将以 `1` 退出。如果你只想在 CI 中拦截真正的安全问题,请使用 `--fail-on error`——其他所有问题都只作为信号保留,而不会成为阻塞。 ## 在 CI 中使用 ``` flowchart LR PR["Pull request"] --> CK["actions/checkout"] CK --> GA["kdairatchi/ghactor@v1"] GA --> SARIF["ghactor.sarif"] SARIF --> UP["upload-sarif"] UP --> TAB["Security tab"] GA -->|"fail-on: error"| FAIL{"Blocking finding?"} FAIL -->|yes| BLK["PR blocked"] FAIL -->|no| OK["Check passes"] classDef cmd fill:#0d0d15,stroke:#f0c040,color:#f0c040 classDef sink fill:#0d0d15,stroke:#06b6d4,color:#06b6d4 classDef node fill:#111119,stroke:#9ca3af,color:#fff classDef bad fill:#1a0d0d,stroke:#ef4444,color:#ef4444 classDef good fill:#0d1a10,stroke:#22c55e,color:#22c55e class GA cmd class TAB,UP,SARIF sink class PR,CK,FAIL node class BLK bad class OK good ``` **作为 composite action** — 发现的问题将通过 SARIF 显示在 Security 选项卡中: ``` name: ghactor on: [push, pull_request] permissions: contents: read security-events: write jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: kdairatchi/ghactor@v1 with: fail-on: error disable: GHA005 ``` **或者直接调用 CLI:** ``` - uses: actions/setup-go@11bd71901bbe5b1630ceea73d27597364c9af683 # v5 with: { go-version: stable } - run: go install github.com/kdairatchi/ghactor/cmd/ghactor@latest - run: ghactor lint --sarif ghactor.sarif --fail-on error - uses: github/codeql-action/upload-sarif@v3 if: always() with: sarif_file: ghactor.sarif category: ghactor ``` ## 配置 在仓库根目录下可选的 `.ghactor.yml`: ``` fail_on: warning # error | warning | info disable: # skip specific rules - GHA005 deny_actions: # glob patterns → GHA010 error - "some-untrusted-org/*" - "*/shady-action" allow_actions: # glob patterns → GHA030 error for anything else - "actions/*" - "myorg/*" severity: # per-rule severity override GHA028: off # disable an info rule entirely GHA005: warning # escalate an info rule ``` ## 安全 私下报告疑似漏洞 — 参见 [SECURITY.md](SECURITY.md)。
由 [@kdairatchi](https://github.com/kdairatchi) 构建 · [ProwlrBot](https://github.com/ProwlrBot) 生态系统的一部分 · 采用 MIT 许可
标签:CI/CD, DevSecOps, EVTX分析, GitHub Actions, StruQ, Web报告查看器, 上游代理, 供应链安全, 命令行工具, 密钥扫描, 文档安全, 自动笔记, 错误基检测, 静态代码分析