cpeoples/grackle

GitHub: cpeoples/grackle

一款静态安全扫描器,用于检测 GitHub Actions 和 GitLab CI 中 AI 编码代理因 fork 触发导致的提示词注入到远程代码执行的风险。

Stars: 1 | Forks: 0

Grackle

CI   OpenSSF Scorecard   CodeQL   cargo-audit   Rules   SLSA Build Level 3   CycloneDX SBOM   Sigstore verified

一个独立的 Rust 扫描器,用于检测 GitHub Actions 和 GitLab CI 工作流中**可由 fork 触发且具有 仓库写权限的 CI 编码代理**。当 AI 编码代理在一个可以写入仓库的作业中运行于不受信任的 fork 输入时, 如果没有任何机制检查触发者身份,提示词注入就会变成远程代码执行,并导致在 CI token 下接管仓库。 grackle 可以在代码合并之前,以静态方式准确地发现这种组合。 它是 [`ansible-security-scanner`](https://github.com/cpeoples/ansible-security-scanner) 中内置的 fork 触发代理检测的聚焦移植版, 保留了相同的锚点、家族证明、可达性后置过滤器以及控制框架元数据,因此检测结果可以一一对应。 **36 条规则**,横跨四个评估家族, 全部通过内置的正反示例进行了自我验证。 ### 构建技术
Rust   Cargo   GitHub Actions   GitLab CI   Hugo
## 检测目标 当不受信任的 fork 贡献者能够触达一个运行具有仓库写权限编码代理的作业时,该工作流就是存在漏洞的。 要触发检测结果,以下三个条件必须同时满足: 1. **可由 fork 触达的触发器** - `pull_request_target`、`issue_comment`、 `issues`、`pull_request`、`pull_request_review_comment`、 `pull_request_review`、`workflow_call`,或者是接收了 fork PR 运行数据但没有同仓库保护的 `workflow_run` 消费者, 并且代理的作业没有被自身的 `if:`(`github.event_name == 'push' | 'schedule' | 'workflow_dispatch'`,`pull_request.merged == true`, 受保护分支引用)限制在非 fork 事件中,也没有通过 `if: false` 被硬性禁用。 2. **无作者限制** - 没有 `author_association` / `getCollaboratorPermissionLevel` / 所有者相等性判断 (`github.actor == github.repository_owner`) / 白名单 (`contains(vars.ALLOWED_USERS, ...login)`) / fork 排除 / 维护者标签 检查来限制谁可以触发该代理。 3. **具备写权限的代理作业** - 代理作业声明了 `contents: write` / `permissions: write-all`,直接进行推送 (`git push`, `gh pr create|merge`),以自动批准模式 (`--dangerously-skip-permissions`, `--yolo`, `--permission-mode bypassPermissions`) 运行代理,或者继承了一个未收窄的工作流级默认写权限。 仅审查代理 (`contents: read`,评论范围) 和受限制的代理不会被标记。 **无仓库写入权限的机密窃取 (高危)。** 当满足条件 1 和 2,但作业*无法证明*具备写权限, 然而它仍然在 fork 检出的代码上向代理提供任意 shell (`--dangerously-skip-permissions`, `--allowedTools "...Bash..."`, `--yolo`),**并且此时作业环境中存在仓库机密时** (API key 或 `GITHUB_TOKEN`),会触发另一条规则,严重程度低一级。该 shell 会运行攻击者控制的内容, 即使没有写权限,也能读取并窃取该机密。此变体仅在包含机密的 fork 触发器 (`pull_request_target`、`issue_comment`、`issues`、`workflow_run` 权限提升) 上触发; 来自 fork 的普通 `pull_request` 不会接收到机密,并且会被与 CRITICAL 规则相同的作者、合并和传递性门控所抑制。 ### GitLab CI GitLab 的 fork 流水线模型不同于 GitHub Actions:fork 的合并请求流水线在 fork 中运行并使用 fork 的变量, 因此父项目的受保护机密永远不会被注入。当一个 `.gitlab-ci.yml` 代理作业运行在合并请求内容上, 被赋予写入或执行能力 (`--dangerously-skip-permissions` / `--permission-mode acceptEdits` / `--yolo` / `--full-auto`,包含 `Bash`/`Edit`/`Write` 的工具授权, `git push`,或真实的项目/个人访问令牌),并且没有自我门控时,它将被标记为高危。 它被评为高危而非 CRITICAL,是因为最终的漏洞可利用性还取决于文件中不可见的项目设置 (token 是否受保护、 分支保护、谁可以推送)。如果检测到 fork-ID 防护 (`$CI_MERGE_REQUEST_SOURCE_PROJECT_ID != $CI_PROJECT_ID`)、源分支名称门控、 基于项目内部变量的作业门控、只读工具、 没有补偿写入能力的 `--permission-mode plan`,或具有 fork 作用域的 `CI_JOB_TOKEN`,检测结果将被抑制。 ### 复合 / 本地 action 有时代理会隐藏在更深的一层:工作流步骤是 `uses: ./.github/actions/foo`,而真正的代理调用位于该 action 的 `action.yml` 中。 当 grackle 扫描**目录** (检出的仓库) 时,它会从磁盘解析每个 `uses: ./` 引用, 并在被引用的 action 定义中寻找代理。隐藏的代理会被归因于调用者工作流的 `uses:` 行, 并且只有在调用者作业是 fork 可达、无门控且具备写权限时才会触发, 因为这才是信任边界和 token 存在的地方。解析深度为一级。这仅适用于目录扫描; 单文件扫描无法解析磁盘上的本地路径。 ## 安装 macOS、Linux 和 Windows 的预编译二进制文件随每个 [发布版本](https://github.com/cpeoples/grackle/releases)提供。下载适用于您平台的归档文件, 解压缩,并将 `grackle` 放入您的 `PATH` 中。 要从源代码构建,您需要 Rust 工具链 (`rustup`): ``` # macOS / Linux curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source "$HOME/.cargo/env" ``` 在 Windows 上,使用 `winget install Rustlang.Rustup` 安装 `rustup` (或从 https://rustup.rs 运行 `rustup-init.exe`) 以及它提示安装的 MSVC C++ 构建工具。然后构建并将二进制文件安装到 `~/.cargo/bin` (已在 `PATH` 中): ``` cargo install --git https://github.com/cpeoples/grackle.git ``` 或者从本地检出代码构建: ``` git clone https://github.com/cpeoples/grackle.git cd grackle cargo install --path . # or: cargo build --release ``` `cargo build --release` 会将二进制文件留在 `target/release/grackle` (Windows 上为 `target\release\grackle.exe`)。 ## 用法 ``` grackle [PATH] # scan a file or directory (default: .) grackle -f json PATH # choose an output format grackle -o report.sarif PATH # write to a file (format inferred from extension) grackle --debug PATH # print per-file scan diagnostics to stderr grackle --list-rules # print the rule inventory grackle --self-test # validate every rule against its built-in examples grackle --version # print the version ``` 当报告任何检测结果时退出代码为 `1`,无问题时为 `0`。 ### GitHub Actions 在每次推送和 pull request 时运行 grackle,并在 Security 标签页和 PR diff 中内联显示检测结果: ``` permissions: contents: read security-events: write jobs: grackle: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: cpeoples/grackle@v0.1.0 ``` 默认开启 `upload-sarif`;设置 `fail-on-findings: false` 以仅报告而不导致构建失败, 或设置 `format`/`output` 以写入不同的报告。该步骤暴露了 `exit-code` 和 `report` 输出。 ### Pull request 和 merge request 评论 `--github-comment` 和 `--gitlab-comment` 将检测结果直接发布到 PR/MR 上: 一个置顶的摘要评论 (重新运行时会原地更新,包含已解决/新增的轨迹行) 以及针对有问题代码行的内联评论。 这不需要 GitHub Advanced Security,并且适用于私有仓库。 ``` on: pull_request permissions: contents: read pull-requests: write jobs: grackle: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: cargo install --git https://github.com/cpeoples/grackle.git - run: grackle --github-comment . env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` 仅从环境中读取 token (`GRACKLE_GITHUB_TOKEN` / `GITHUB_TOKEN` / `GH_TOKEN`,或 GitLab 的等效项),并在日志中对其进行脱敏。有关 GitLab,请参阅 [CI/CD 集成](docs/ci-cd.md)。 ### 输出格式 `-f/--format` 选择格式;`-o/--output` 写入文件并从文件扩展名推断格式。 `--json` 是 `-f json` 的简写。 | 格式 | 值 | 备注 | | ------------- | ------------------------- | ------------------------------------------------- | | Text | `text` (默认) | 人类可读,包含完整的修复建议。 | | JSON | `json` | 包含元数据、代码片段和修复建议的检测结果。 | | Markdown | `markdown` / `md` | 每个检测结果一节。 | | SARIF 2.1.0 | `sarif` | 规则目录 + `fixes[]`;GitHub 代码扫描。 | | GitLab SAST | `gitlab-sast` | 用于 `artifacts:reports:sast` 的 `gl-sast-report.json`。| | JUnit | `junit` | 每个检测结果一个失败的测试用例。 | | CSV | `csv` | 扁平化表格 (RFC 4180)。 | | XML | `xml` | 通用检测结果文档。 | | YAML | `yaml` / `yml` | 带有 block-scalar 片段/修复建议的检测结果。 | | HTML | `html` | 自包含的带样式报告。 | | CycloneDX 1.5 | `cyclonedx` | SBOM `vulnerabilities[]`;Dependency-Track, Snyk。 | 每个检测结果除了带有控制框架引用 (CWE、OWASP AppSec、OWASP LLM、OWASP ASVS、MITRE ATT&CK、MITRE ATLAS、CIS Controls、NIST 800-53、PCI-DSS、SOC 2)、有问题的 工作流块,以及根据该块动态生成的安全修复文章:漏洞代码、其危险的原因以及修正后的工作流之外,还带有一个 `high`、`medium` 或 `low` 的置信度。置信度反映了静态读取能够证明该检测结果的程度, 并且独立于严重程度;有关其推导方式,请参阅 [输出格式](docs/output-formats.md)。 ## 设计 | 模块 | 职责 | | -------------------- | -------------------------------------------------------------------------------------------------- | | `workflow.rs` | 结构原语:GitHub 触发器可达性、封闭作业块、工作流级默认写权限、作者门控、每作业写入能力;GitLab 合并请求可达性、fork/分支/变量门控以及写入能力检测。 | | `rules/mod.rs` | `RuleSpec` (纯数据)、`Family` 评估策略、`Finding`,以及负责评估、后置过滤器和重叠抑制的 `Engine`。 | | `rules/metadata.rs` | 三个共享的合规性配置文件 (`RCE_CRITICAL`、`REPO_MUTATION_HIGH`、`SECRET_EXFIL_HIGH`)。 | | `rules/installed.rs` | 20 条已安装代理规则加上 shell 执行机密窃取规则 (锚点 + 家族证明 + 示例)。 | | `rules/action.rs` | 14 条 action 配置规则以及 CRITICAL 与 HIGH 的重叠抑制。 | | `rules/gitlab.rs` | GitLab CI 代理规则 (GitLab 原生的可达性和门控模型)。 | | `rules/remediation.rs` | 每条规则的动态安全修复生成 (漏洞 / 解释 / 修正后的工作流)。 | | `localaction.rs` | 从磁盘解析 `uses: ./` 复合 action 引用,使得隐藏在 `action.yml` 中的代理被归因于其调用者。 | | `report.rs` | 输出格式 (text、json、markdown、sarif、gitlab-sast、junit、csv、xml、yaml、html、cyclonedx)。 | | `scanner.rs` | 工作流文件发现和扫描编排。 | | `main.rs` | CLI (`clap`)、格式选择和文件/标准输出输出。 | 规则集按**评估家族**划分,而不是按供应商划分:`Installed` 代理 (代理名称为锚点,通过整文件证明保持精确)、`Action` 配置 (代理通过 `allowed_non_write_users: "*"`、 写沙箱或 YOLO/自动批准向 fork 开放)、`ForkShellExec` (在 grackle 无法 证明其会写入仓库的包含机密作业中,代理在 fork 内容上获得了任意 shell,这是一种被评为 HIGH 的机密窃取风险) 和 `Gitlab` 代理作业 (GitLab 的 fork 流水线可达性、门控和写入模型,而不是 GitHub 的 `on:`/`jobs:`/`permissions:` 上下文)。 各家族具有真正不同的可达性语义,这才是代码拆分的真正轴线。 正则表达式使用 `fancy-regex` crate,因为移植的锚点依赖于 后向断言和前向断言,而默认的 `regex` crate 不支持这些。它们在构建 `Engine` 时被编译一次, 并且跨越式的可达性 / 门控 / 写入事实在每个文件中只计算一次,而不是每条规则计算一次。 ## 已检测的代理 **已安装代理 (CRITICAL):** Cursor、OpenCode、Amp、Goose、Factory Droid、 Aider、OpenHands、Qwen Code、Crush、GitHub Copilot CLI、Continue、gptme、 SWE-agent、Warp (CLI 和云代理 action)、Devin、Kilo Code、Claude CLI、Gemini CLI。 **Action 配置:** Claude Code action shell/write 工具,包括 自主 `claude_args: --permission-mode bypassPermissions` / `--dangerously-skip-permissions` 模式 (CRITICAL),Claude Code action 仓库修改 `gh`/MCP 工具 (HIGH),具有 shell 或写入 权限的 Gemini / Copilot action (CRITICAL),具有写入 / 完全访问沙箱的 OpenAI Codex action (CRITICAL),绕过其内置门控并带有自定义提示词的 JetBrains Junie (CRITICAL),具有可写 token 且没有 维护者/CODEOWNERS 门控的 Bonk (OpenCode 包装器) (CRITICAL),具有仓库写 权限的 Cogni AI action (CRITICAL),向具有 shell / 提交 访问权限的非写入用户开放的 Letta Code (CRITICAL),具有 仓库写权限的 `potproject/code-agent` Claude Code / Codex 包装器 (CRITICAL),具有仓库写 权限的 `cognitivecomputations` AI 重构 action (CRITICAL),具有仓库写 权限的 `a5c` 代理路由器 (CRITICAL),由不受信任的 提示词驱动的 iFlow CLI action (CRITICAL),具有 仓库写权限且无维护者标签门控的 Sweep AI 代理 (`sweepai/sweep`) (CRITICAL),以及 具有仓库写权限且无作者门控的 PR-Agent (`qodo-ai/pr-agent`) (HIGH)。 **GitLab CI (HIGH):** 运行 Claude、 Codex、Aider、Cursor、Qwen、OpenCode、Goose 或 Gemini 且具有写入/执行 能力但无 fork 门控的 `.gitlab-ci.yml` 合并请求作业。 每条规则都通过 `--self-test` 针对内置的正反示例进行了自我验证。 ## 安全 Grackle 的存在是为了标记不受信任的 fork 可以触达的具备写权限的 CI 代理, 因此该项目对自身也秉持相同的标准。每次推送和 pull request 都会运行: - **OpenSSF Scorecard** ([`.github/workflows/scorecard.yml`](.github/workflows/scorecard.yml)) - 每周供应链安全态势检查,发布到代码扫描仪表板。 - **CodeQL** ([`.github/workflows/codeql.yml`](.github/workflows/codeql.yml)) - 对 `src/` 中的 Rust 以及仓库自身的 GitHub Actions 工作流进行 SAST。 - **cargo-audit** ([`.github/workflows/cargo-audit.yml`](.github/workflows/cargo-audit.yml)) - 针对 RustSec 咨询数据库扫描依赖树,每周以及 每次 `Cargo.lock` 更改时运行。 - **Dependabot** ([`.github/dependabot.yml`](.github/dependabot.yml)) - 针对 `cargo` 和 `github-actions` 生态系统每周提交更新 PR。 每个工作流都在经过出口审计的 [`step-security/harden-runner`](https://github.com/step-security/harden-runner) runner 上运行,通过 commit SHA 固定第三方 action,并声明其所需的最低 `permissions`。任何工作流都不会向可由 fork 触达的触发器授予写权限。 ### 发布出处 发布归档文件受由可信 `slsa-github-generator` 可重用工作流生成的 [SLSA Build Level 3](https://slsa.dev/) 溯源保护。CycloneDX SBOM 是根据 Cargo 依赖图生成的,每个归档文件和 SBOM 都使用 [Sigstore](https://www.sigstore.dev/) 签名。使用 `slsa-verifier` 和 `cosign` 针对随附的 `.intoto.jsonl` 和 `.sigstore.json` 文件验证下载。 ### 隐私 Grackle 在本地运行,不会向外部发送数据。没有遥测,没有 分析,没有使用情况 ping,也没有远程规则获取。它读取磁盘上的工作流文件 并写入报告;它不会建立任何网络连接。 ### 报告漏洞 请通过 [Security 标签页](https://github.com/cpeoples/grackle/security/advisories/new) 提交私人安全建议,而不是公开 issue。grackle 本身的漏洞属于范围内事项; *grackle 扫描的工作流*中的漏洞则不属于,这些漏洞应由 被扫描仓库的维护者负责。
标签:AI代码助手, CI/CD安全, DevSecOps, Llama, Rust, 上游代理, 可视化界面, 网络流量审计, 通知系统, 静态代码扫描