mcpField
Z
zanadir

zanadir

本地服务
mustachecase.github.io安全180今天更新

关于 zanadir

zanadir 扫描 CI 配置,找出缺失的安全与质量控制,并检查现有控制能否阻止构建。它面向仓库维护者,支持六种 CI 平台并提供发现结果和配置建议。

主要功能

  1. 01扫描六种 CI 平台
  2. 02检查控制的执行效果
  3. 03为缺项推荐工具
  4. 04生成 CI 修复配置
  5. 05导出表格、JSON、SARIF 或 Markdown 报告

适用场景

  1. 01审查仓库的 CI 安全覆盖
  2. 02检查扫描器能否阻止构建
  3. 03为缺失控制生成配置

MCP 相关

zanadir 作为 MCP 服务提供只读的 `scan` 和 `fix` 工具。它向 MCP 客户端返回 CI 检查结果和建议配置,不会写入文件。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"mustachecase-zanadir": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITHUB_TOKEN",
"docker.io/mustachecase/zanadir:0.24.2"
],
"env": {
"GITHUB_TOKEN": "<GITHUB_TOKEN>"
}
}
}
}
启动命令docker run -i --rm -e GITHUB_TOKEN docker.io/mustachecase/zanadir:0.24.2
需要配置的环境变量
GITHUB_TOKEN密钥Optional: a token for the repo argument, used only when the server is started with --repo-token

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
ocidocker.io/mustachecase/zanadir:0.24.2

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

zanadir

zanadir 覆盖率 Go 参考文档 测试覆盖率

zanadir 会读取代码仓库的 CI 配置,并报告缺少哪些安全和质量控制措施,或哪些措施已接入却无法导致构建失败。

它能回答哪些问题

  • 我的流水线缺少什么? 涵盖依赖扫描、密钥检测、SBOM 和工作流加固等 12 个类别;针对每个类别检查相应的工具,并提供适合代码仓库所用语言的建议。
  • 我的 CI 真的会因安全问题而阻止合并吗? 配置了 continue-on-error、--exit-code 0、|| true、仅在 push 时触发或设置了 allow_failure 的扫描器虽然存在,却无法阻止合并。zanadir 会将每项控制措施评定为强制执行、部分执行、仅提供提示或未知,而不只是判断是否覆盖;请参阅有效性。
  • 合并时必须通过这项检查吗? 使用 --required-checks 后,只有分支保护或规则集要求通过的控制措施才计为强制执行。
  • 我该添加什么? zanadir fix 会打印可直接粘贴的 CI 配置来填补缺失项,也可以使用 --write 写入工作流。

它可以读取 GitHub Actions、GitLab CI、CircleCI、Bitbucket Pipelines、Azure Pipelines 和 Jenkins 配置,既支持从本地检出目录(--dir)读取,也支持直接从 GitHub 读取(--repo),并以表格、JSON、SARIF 或 Markdown 格式报告。

这些缺口有多常见?2026 年 CI 安全状况扫描了 GitHub 上星标数最高的 1,000 个代码仓库:其中 511 个完全没有声明依赖、密钥或代码扫描;而在 CI 中运行扫描器的代码仓库里,每 6 个就有 1 个使用的扫描器无法导致构建失败。

zanadir scan --dir .
zanadir scan --repo github.com/owner/name --output json
zanadir fix --dir .

安装

无需安装,即可在任何公开代码仓库上试用:

docker run --rm mustachecase/zanadir scan --repo github.com/owner/name

要安装,请从最新版本下载适用于 Linux、macOS 或 Windows(amd64 或 arm64)的压缩包。checksums.txt 列出了每个压缩包的 SHA-256 校验和,且每个压缩包都有构建来源证明:

gh attestation verify zanadir_<version>_linux_amd64.tar.gz --repo MustacheCase/zanadir

也可以使用 Homebrew(此代码仓库就是 tap)或 Go 安装:

brew tap --custom-remote MustacheCase/zanadir https://github.com/MustacheCase/zanadir.git
brew install mustachecase/zanadir/zanadir
go install github.com/MustacheCase/zanadir@latest

在 GitHub Actions 中,请使用 zanadir-action。此工作流会在每个拉取请求上生成报告,但不会使其失败:

# .github/workflows/zanadir.yml
name: zanadir
on: [pull_request]
permissions:
  contents: read
jobs:
  zanadir:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: mustachecase/[email protected]
        with:
          dir: .

准备好设置门禁后,添加 enforce: true(任何未覆盖的类别都会导致失败)、fail-on(仅针对指定的类别),或 fail-on-weakened。若要将报告作为拉取请求评论发布,请参阅将报告发布到拉取请求。

zanadir 与 OpenSSF Scorecard

OpenSSF Scorecard 和 zanadir 回答的是不同的问题,并且可以很好地配合使用。

OpenSSF Scorecardzanadir
问题该项目的安全实践状况如何?缺少哪些 CI 控制措施,已有的控制措施能否导致构建失败?
范围覆盖项目的 20 项检查:分支保护、代码审查、维护、锁定依赖项、令牌权限、签名发布、已知漏洞、模糊测试等涵盖 12 类 CI 控制措施,并逐一匹配提供这些控制措施的各类工具
读取内容代码托管平台的 API:GitHub 和 GitLab 仓库、近期拉取请求和检查运行记录CI 配置:六种 CI 平台、Makefile、Taskfile、tox、复合操作,以及任务调用的模板
例如,SAST查找 CodeQL 或 SonarCloud;其文档指出,其他工具的得分可能较低可识别 Semgrep、CodeQL、gosec、Bandit、Brakeman、SonarQube 等工具,并评估每种工具是否能导致构建失败
结果每项检查及总体的 0-10 分按类别显示是否覆盖,并评为强制执行、部分执行、建议执行或未知,同时提供修复方案

使用 Scorecard 评估项目整体安全状况;使用 zanadir 查明现有 CI 是否能真正阻止不良变更,以及应添加哪些内容。

在 AI 编码 Agent 中使用 zanadir

若要让编码 Agent 在更改 CI 前先使用 zanadir 检查,请将以下内容添加到仓库的 AGENTS.md、CLAUDE.md 或同等文件中:

## CI changes

Before adding or changing CI configuration, run `zanadir scan --dir . --output json`
(install: https://github.com/MustacheCase/zanadir#installation). It lists the
missing CI controls and grades each present one: `enforcing` can fail a merge,
`partial`, `advisory` and `unknown` cannot or may not. Fix advisory controls before
adding tools, and use `zanadir fix --dir .` for configuration that fits this
repository. Re-run the scan to confirm the change.
作为 MCP 服务

zanadir mcp 通过模型上下文协议提供两个只读工具,因此任何支持 MCP 的 Agent 都可以在更改 CI 前后对其进行评估:

  • scan 返回 JSON 报告:得分、未覆盖类别及建议工具,以及附有判定结果的弱化控制措施;
  • fix 返回可直接粘贴的配置,用于填补缺漏,且不会写入任何内容。

两者均接受 dir(本地检出目录,默认为当前目录)或 repo(github.com/owner/name[@ref],无需克隆即可读取),以及 excludedCategories。

claude mcp add zanadir -- zanadir mcp

它已列入 MCP Registry,名称为 io.github.MustacheCase/zanadir,通过 Docker 镜像运行,并以只读方式挂载你的仓库。对于 Cursor、VS Code 或 Windsurf 等其他客户端:

{
  "mcpServers": {
    "zanadir": { "command": "zanadir", "args": ["mcp"] }
  }
}
作为 Claude Code 插件

在 Claude Code 中,zanadir 插件包含两项功能:MCP 服务,以及一项技能。该技能会扫描并读取你的 CI 配置,按优先级顺序提出修复建议,并且只会在你选择后编辑文件。服务会运行已安装的 zanadir(前提是版本为 0.18.0 或更高),否则会使用 Docker 镜像。

/plugin marketplace add MustacheCase/zanadir
/plugin install zanadir@mustachecase

然后让 Claude Code 审查或强化你的 CI,或运行 /zanadir:improve-ci。

llms.txt 为会读取仓库的 Agent 概述了 zanadir;对于会查看网站根目录的 Agent,该文件也托管在 mustachecase.github.io/llms.txt。

功能

  • 📂 扫描:分析仓库,提供 CI/CD 改进建议,包括安全服务和最佳实践。
  • 🔍 CI 分析:评估每项控制措施是否确实能导致构建失败,而不只是检查其是否存在。
  • 🛠️ 修复:打印扫描报告中缺失类别对应的 CI 配置,或将其写入工作流。
  • 🚀 开源:欢迎通过贡献来增强 Zanadir 的功能!

支持的 CI 操作

Zanadir 目前支持:

  • GitHub Actions
  • CircleCI
  • GitLab
  • Bitbucket Pipelines
  • Azure Pipelines
  • Jenkins

未来计划支持:

  • Tekton

我们建议的类别

Zanadir 会分析以下类别的仓库。粗体名称是传递给 --excluded-categories 的 确切值(匹配时不区分大小写):

  • 🛡️ SCA:软件成分分析
  • 🔐 Secrets Detection:密钥管理
  • 📜 License Compliance:许可证合规
  • 🛠️ End Of Life:生命周期终止的软件包
  • 📊 Coverage:测试覆盖率
  • 📊 Performance Testing:测试性能和可靠性
  • 🧑‍💻 Linter:代码检查
  • 🧪 Unit Tests:自动化单元测试
  • 🔎 SAST:静态应用程序安全测试
  • 🏗️ IaC Security:基础设施即代码错误配置扫描
  • 🔗 Supply Chain:SBOM 生成、签名和溯源
  • 🧱 Workflow Security:强化流水线定义本身的安全性

使用示例

基本用法

扫描仓库,获取 CI/CD 改进建议:

zanadir scan --dir /path/to/your/repo

也可以扫描 GitHub 仓库而不克隆它:

zanadir scan --repo github.com/owner/name
zanadir scan --repo owner/[email protected]     # a branch, tag or commit
zanadir fix --repo owner/name

--repo 会将仓库归档下载到临时目录,像检出后的仓库一样进行扫描, 然后将其删除。只有 zanadir 读取的文件会保留内容。GitHub 的归档会排除 .gitattributes 中标记为 export-ignore 的文件,其中通常包括 .github, 因此 zanadir 会单独获取这些文件,避免将没有 CI 的仓库误报出来。公开 仓库无需令牌;设置 GITHUB_TOKEN(或 GH_TOKEN)即可访问私有仓库 或提高 GitHub 的速率限制。GitLab 和 Bitbucket 仓库仍需克隆。

输出格式

Zanadir 支持三种输出格式:表格(默认)、JSON 和 SARIF。

表格输出(默认)
zanadir scan --dir . --output table

示例输出:

Coverage 10/11 - 1 category needs attention:

|--------------------------------|--------------------------------|-------------------|
|            CATEGORY            |          DESCRIPTION           |  SUGGESTED TOOLS  |
|--------------------------------|--------------------------------|-------------------|
| Performance and Reliability    | Tools for load, stress and     | k6, JMeter,       |
| Testing Tools                  | reliability testing, to verify | Gatling, Apache   |
|                                | a system holds up under        | Bench, Artillery, |
|                                | expected and peak traffic.     | BlazeMeter        |
|--------------------------------|--------------------------------|-------------------|

如果没有缺失项,扫描会直接说明,而不是打印空表格:

Coverage 11/11 - all categories are covered.

在交互式终端中,标题会以粗体显示。如果输出通过管道传递或使用 --output-file 写入文件,或者设置了 NO_COLOR,则不会显示颜色。

JSON 输出
zanadir scan --dir . --output json

一个对象,包含评分、检测到配置的 CI 系统、未覆盖的类别及覆盖这些类别的 工具、工具无法完全保护仓库的已覆盖类别,以及未计入评分的类别。每个列表 都是数组;没有内容时为空数组,而不是 null。

{
  "score": { "covered": 4, "total": 11 },
  "platforms": ["github"],
  "uncovered": [
    {
      "id": "Secrets Detection",
      "name": "Data Leakage & Secrets Detection",
      "description": "Tools that help detect sensitive information ...",
      "suggestions": [
        {
          "name": "Gitleaks",
          "repository": "https://github.com/gitleaks/gitleaks",
          "description": "..."
        }
      ]
    }
  ],
  "weakened": [
    { "category": "SCA", "verdict": "advisory", "location": ".github/dependabot.yml" }
  ],
  "notApplicable": ["IaC Security"]
}

只有当建议的工具仅适用于一种语言时,才会显示 language;只有当弱化的 类别指向仓库内的位置时,才会显示 location。在 0.10.0 之前,--output json 只会打印现在称为 uncovered 的纯数组,且键名首字母大写。

SARIF 输出
zanadir scan --dir . --output sarif

SARIF 是静态分析结果的标准交换格式。生成 SARIF 后,未覆盖的类别会显示在 GitHub 的 Security 选项卡中,也会显示在任何其他使用 SARIF 的平台中,并与 其他扫描工具的结果并列。

每个未覆盖的类别都会成为一条 SARIF 规则和一个 warning 级别的结果,建议 工具会列在结果的帮助文本中。结果会锚定到一个 CI 配置文件,也就是应添加 缺失工具的位置。缺失的控制措施并非特定行上的缺陷,但每个 SARIF 使用方都 要求提供位置,而且 GitHub 代码扫描会拒绝没有位置的报告。

Markdown 输出
zanadir scan --dir . --output markdown

Markdown 是一种方便将扫描结果展示给他人、而非保存到文件中的格式。工具会 链接到各自的仓库;报告开头会带有一个隐藏标记,使工作流可以更新上次发布的 评论,而不是每次推送都新增一条评论。

示例输出:


### zanadir

Coverage 10/11 - 1 category needs attention:

| Category | Description | Suggested tools |
| --- | --- | --- |
| Performance and Reliability Testing Tools | Tools for load, stress and reliability testing. | [k6](https://github.com/grafana/k6), [JMeter](https://github.com/apache/jmeter) |

如果没有缺失项,则会省略表格,只保留一行。

将报告写入文件

使用 --output-file 将报告写入指定路径,而不是写到标准输出。它适用于所有格式,也是生成机器可读报告的可靠方式:重定向标准输出时也会捕获调试日志,因此 --output sarif --debug > report.sarif 生成的文件不是有效的 JSON。

zanadir scan --dir . --output sarif --output-file zanadir.sarif

要从 GitHub Actions 工作流发布报告:

- name: Run zanadir
  run: zanadir scan --dir . --output sarif --output-file zanadir.sarif

- name: Upload SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: zanadir.sarif

未覆盖的类别随后会与其他扫描器的结果一同显示在仓库的 Security 选项卡中。

在拉取请求中发布报告

SARIF 会将未覆盖的类别放到 Security 选项卡中,但人们只是偶尔会去那里查看。拉取请求评论则是他们本来就会查看的地方。

permissions:
  contents: read
  pull-requests: write

steps:
  - name: Run zanadir
    run: zanadir scan --dir . --output markdown --output-file zanadir.md

  - name: Post the report
    run: gh pr comment "$NUMBER" --body-file zanadir.md --edit-last --create-if-none
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      NUMBER: ${{ github.event.pull_request.number }}

--edit-last 会持续更新同一条评论,而不是每次推送都新增一条。它会匹配同一用户发布的最后一条评论,因此,如果也有其他内容以 github-actions 身份发表评论,请改为匹配标记:

  - name: Find the previous report
    uses: peter-evans/find-comment@v4
    id: previous
    with:
      issue-number: ${{ github.event.pull_request.number }}
      body-includes: ""

  - name: Post the report
    uses: peter-evans/create-or-update-comment@v5
    with:
      comment-id: ${{ steps.previous.outputs.comment-id }}
      issue-number: ${{ github.event.pull_request.number }}
      body-path: zanadir.md
      edit-mode: replace

来自分叉仓库的拉取请求会获得只读令牌,因此评论步骤无法发布评论。请使用 if: github.event.pull_request.head.repo.full_name == github.repository 为其添加条件,而不是使用 pull_request_target,因为后者会让工作流以写入权限运行分叉仓库中的代码。

覆盖率评分

每次扫描都会汇总为一个覆盖率评分:适用于该仓库的类别数,减去仍未覆盖的类别数。

以下三条规则可确保评分真实可靠:

  • 排除的类别不计入分母。 如果仓库通过 --excluded-categories 声明某个类别与其无关,就不会因此被扣分。

  • 没有可扫描内容的类别也不计入。 如果仓库没有 Terraform、Helm chart、Kubernetes 清单或 Dockerfile,就不会被要求添加 Checkov,评分基数也会从 11 降为 10。扫描会说明原因:

    IaC Security is not applicable and is out of the score: nothing in this
    repository needs it.
    

    有两类会以这种方式被排除。IaC 安全需要可扫描的基础设施,而检测范围刻意设得很广——只要有名为 infra、k8s 或 charts 的目录,就会将其保留在评分中。 工作流安全需要 GitHub Actions,因为其中的每个工具都会读取这种格式,所以不会要求仅使用 GitLab 的仓库添加 actionlint。

    其余类别适用于任何代码库;而那些可以说并非适用于所有代码库的类别——例如对库进行负载测试,或为没有任何发布内容的项目生成 SBOM——无法与确实适用的情况区分开来,因此如果靠猜测排除,可能会排除仓库实际需要的类别。

  • 基线接受的缺口仍计为未覆盖。 基线记录的是某个缺口可以接受,而非该缺口已关闭。如果只因有人提交了一个文件,评分就上升,那这个评分便毫无意义。

README 徽章

--badge 会将评分写入一个 shields.io endpoint 文件:

zanadir scan --dir . --badge zanadir-badge.json
{
  "schemaVersion": 1,
  "label": "zanadir",
  "message": "10/11",
  "color": "green"
}

在 CI 中发布该文件,并让徽章指向它。请将文件放到专用分支,而不要提交到默认分支:受保护的默认分支只接受通过拉取请求提交的更改,因此,尝试将徽章推送到该分支的工作流会被拒绝。

![zanadir](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/OWNER/REPO/badges/zanadir-badge.json)

以下作业会在每次推送到默认分支时更新徽章,并在首次运行时创建 badges 分支进行发布:

badge:
  if: github.event_name == 'push'
  runs-on: ubuntu-latest
  permissions:
    contents: write
  steps:
    - uses: actions/checkout@v7

    - name: Run zanadir
      run: zanadir scan --dir . --badge zanadir-badge.json

    - name: Publish the badge when it moved
      run: |
        set -euo pipefail
        cp zanadir-badge.json "$RUNNER_TEMP/zanadir-badge.json"
        rm -f zanadir-badge.json
        git config user.name github-actions
        git config user.email [email protected]
        if git fetch --depth=1 origin badges 2>/dev/null; then
          git switch --discard-changes -C badges FETCH_HEAD
        else
          git switch --orphan badges
        fi
        cp "$RUNNER_TEMP/zanadir-badge.json" zanadir-badge.json
        git add zanadir-badge.json
        if git diff --cached --quiet; then exit 0; fi
        git commit -m "chore: update zanadir badge"
        git push origin badges

badges 分支仅包含这一个文件。如果某个规则集涵盖所有分支,而不仅是默认分支,请将 badges 排除在外。

颜色随评分变化:达到或超过 80% 时为绿色,达到 60% 时为黄色,达到 40% 时为橙色,低于该值时为红色,只有覆盖率达到 100% 时才为亮绿色。

有效性

存在不等于保护。配置成永远不会让构建失败的扫描器,会让仓库的处境比完全没有扫描器更糟,因为大家都以为有这项防护。以下这些过去都报告为 covered:

- uses: gitleaks/gitleaks-action@v2
  continue-on-error: true          # the job stays green whatever it finds
on:
  schedule: [{cron: "0 3 * * *"}]  # nothing is blocked at merge time
on:
  push: {branches: [main]}         # pull requests are never checked

现在,扫描会评估找到的每项控制,并报告那些实际保护力度不如其存在所暗示的控制:

3 covered categories are weakened:
  Linter is partial (.github/workflows/pushonly.yml)
  SCA is advisory (.github/workflows/nightly.yml)
  Secrets Detection is advisory (.github/workflows/ci.yml)
判定含义
enforcing在对变更进行门禁检查的路径上,该控制可以让构建失败
partial可以让构建失败,但不在该路径上——例如,只在推送时运行
advisory会运行,但无法阻止任何操作
unknown无法充分读取配置,因而无法判定

只有当 pull_request 触发器会在变更本身上运行,且会在针对默认分支的拉取请求上运行时,它才起到门禁作用。types: [closed] 会在合并后运行,因此其判定与推送相同。branches: 或 branches-ignore: 筛选条件若未包含默认分支,则该控制会判定为 unknown。默认分支在仓库之外设置:zanadir 会从检出内容的 origin/HEAD 读取,git clone 会设置此值;也可以通过 GitHub 配合 --repo 或 --required-checks 获取。如果这些方式都无法提供默认分支(大多数 CI 检出都是如此),则以 main、master、trunk 和 develop 作为默认分支,或以筛选条件中指定的其中一个分支为准。

Supply Chain 按另一种路径进行评估。SBOM、签名或来源证明描述的是实际发布的内容,而不是查找需要阻止的内容,因此它应在发布制品的环节运行:在发布、推送、标签、拉取请求中运行,或在这些事件之后通过工作流链式运行,判定均为 enforcing。手动运行可能就是发布本身,文件无法判断这一点,因此判定为 unknown。只有与任何发布活动脱离的定时任务才判定为 advisory;continue-on-error 在这里和其他地方一样,也是 advisory:签名步骤若能静默失败,就会发布未经签名的制品。

除非明确要求,否则弱化的控制不会导致扫描失败。--enforce 和 --fail-on 仅作用于没有工具支持的类别;要让弱化的控制导致失败,需使用 --fail-on-weakened:

zanadir scan --dir . --fail-on-weakened
Enforcement failed: controls that cannot fail a build: Secrets Detection (advisory)

两者互不影响。单独使用 --enforce 仍会忽略弱化的控制,因此升级不会让此前通过的构建开始失败。--fail-on 会同时缩小两者的范围。

基线会接受接受时所处判定等级的弱化情况:

version: 1
categories:
  - Performance Testing
weakened:
    Secrets Detection: partial

因此,接受 partial 并不意味着日后降为 advisory 也会被接受。如果控制的情况比记录的判定更糟,扫描就会再次失败。--write-baseline 会为你记录判定结果。

其中有些规则是通用的。continue-on-error 会使任何控制失效,以下 shell 写法也一样:gitleaks detect || true、|| echo failed,以及将工具作为 if 或 ! 的条件运行。|| exit 1 若仍会导致失败,则没有问题。只在定时任务中运行的工作流也一样。其余规则针对特定工具,规则可以说明这一点:

  - id: "tfsec-rule"
    regex: "(?i)\\btfsec\\b"
    weakenedWhen:
      - runMatches: "--soft-fail\\b"
        verdict: "advisory"
      - input: "soft_fail"
        equals: "true"
        verdict: "advisory"

一项检查要么是针对步骤 shell 命令的正则表达式,要么是将一个 action 输入与某个值进行比较——刻意不采用表达式语言。目前已覆盖:trivy --exit-code 0、gitleaks --exit-code 0、tfsec 和 checkov 的 --soft-fail / soft_fail: true,以及 anchore/scan-action 的 fail-build: false。

有两处是有意不予判定的。if: 条件绝不进行解析,因为它可以引用任意上下文;错误解读的后果比承认无法读取更糟——因此该控制会报告为 unknown。此外,类别判定取其最强控制,因此只要有一个扫描器配置得当,就能让该类别保持无提示,无论旁边还有多少个较弱的扫描器。

必需检查

扫描器可以在每个拉取请求上运行,却仍然无法阻止任何内容,因为它的检查并非合并所必需。这项设置位于仓库的规则集和分支保护中,而不在文件里,因此需要选择启用:

zanadir scan --dir . --required-checks
zanadir scan --repo owner/name --required-checks

zanadir 会从 GitHub 读取默认分支的必需检查(公共仓库无需令牌;如果设置了 GITHUB_TOKEN 或 GH_TOKEN,则会使用)。只有当其作业的检查为必需项时,GitHub Actions 控件才会保持 enforcing;这也包括矩阵作业(build (ubuntu-latest))和可复用工作流(codeql / Analyze)的名称;否则为 partial:

2 covered categories are weakened:
  SAST is partial (.github/workflows/codeql.yml): its check is not required to merge
  Secrets Detection is partial (.github/workflows/secrets.yml): its check is not required to merge

Merge settings for main:
  Required to merge: build (ubuntu-latest), build (macos-latest).
  Problem: ruleset "checks must pass" requires "Run tests" but applies to no branch.

它还会指出那些看起来像保护措施、实际却不是的设置:要求检查但不针对任何分支或仅进行评估的规则集,以及没有任何工作流作业报告的必需 GitHub Actions 检查。不会评估其他应用报告的检查,例如 DCO。如果无法读取这些设置,扫描会发出警告,并在不考虑这些设置的情况下评级。它只读取 GitHub;在其他平台上,zanadir 无法查看合并设置。

在流水线之外声明的控件

Dependabot 和 Renovate 是根据其配置检测的——.github/dependabot.yml、renovate.json 以及 Renovate 接受的其他名称——而不是根据 CI 作业检测,因为它们并不位于 CI 作业中。

它们的评级为 advisory。两者都按代码托管平台自己的计划运行并创建拉取请求;两者都无法使构建失败。因此,如果仓库唯一的依赖项控件是 Dependabot,报告会同时将其标记为已覆盖 和 已弱化,这比回答“你没有 SCA”或“你已受到覆盖”中的任何一种都更有用:

1 covered category is weakened:
  SCA is advisory (.github/dependabot.yml)
平台覆盖范围

GitHub Actions 会进行完整评级。GitLab CI 会针对两个明确的信号进行评级:allow_failure: true,也就是换了种写法的 continue-on-error,以及 when: manual,即只有在有人请求时才会运行的作业。

系统会跟踪 GitLab 的 include:。本地文件(local: 或裸路径,可使用通配符)会作为同一配置的一部分读取。无法从仓库读取 GitLab 自带的模板和 CI/CD 组件,因此会按名称识别:Jobs/SAST.gitlab-ci.yml 或 components/sast/sast@... 会计为 SAST;Secret Detection、Dependency and Container Scanning、SAST-IaC、License Scanning、Code Quality 和性能测试模板也同样如此。project: 或 remote: 引用会被跳过。zanadir 无法读取的作业会被跳过,而不是导致扫描失败。

GitLab 的 rules:、only: 和 except: 会有意不作解读。分支流水线是否会阻止合并,取决于仓库中未包含的项目设置,因此解读这些内容就等于猜测。没有这两个弱化字段的 GitLab 作业会报告为 unknown,表示无法判断,而不是存在问题。CircleCI 没有等效信号,因此不进行评级。

Bitbucket Pipelines 会按触发方式拆分文件,因此大部分内容都会进行评级:

工具运行的位置评定结果
pull-requests:enforcing
针对某个指定分支(例如 main)的 branches:partial - 在合并后运行
tags:、custom:,或包含 trigger: manual 的步骤或阶段advisory
after-script:advisory - 失败的 after-script 不会导致步骤失败
default: 或通配符分支模式unknown

default: 也会在拉取请求的源分支上运行,而该构建是否会阻止合并取决于仓库外配置的合并检查,因此出于与 GitLab 分支流水线相同的原因,会将其标记为 unknown。Pipes 的读取方式与 GitHub Actions 类似:pipe: snyk/snyk-scan 会计为 Snyk,而 DONT_BREAK_BUILD: "true" 会将其评级为 advisory。 Jenkinsfile(及其旁边的变体,例如 Jenkinsfile.release)会作为 Groovy 词法单元读取,而不会运行。sh、bat 和 powershell 步骤会像 run: 一样读取,其他步骤(例如 snykSecurity、dependencyCheck、withSonarQubeEnv 或 junit)则会像操作一样读取,并将其命名参数作为输入。哪些分支和拉取请求会触发构建,由 Jenkins 作业配置,而不是由文件配置,因此除非文件本身降低了控制强度,否则控制项为 unknown:

Jenkinsfile 中的内容判定
位于 catchError(buildResult: 'SUCCESS') 或 buildResult: null 中advisory——构建仍会显示为绿色
步骤选项,例如 snykSecurity(failOnIssues: false) 或 waitForQualityGate abortPipeline: falseadvisory
位于 when { }、if、warnError 或 catchError(buildResult: 'UNSTABLE') 中,或使用 sh(returnStatus: true)unknown

仅通过赋值配置的共享库步骤(例如 mavenBuild { dependencyCheck = 'enabled' })无法读取。

Azure Pipelines 会从仓库根目录的 azure-pipelines.yml(或 .yaml)读取,并跟踪 template: 和 extends: 引用的同一仓库中的文件。无法读取其他仓库中的模板(path@alias)或由表达式指定的模板,因此会跳过。

工具运行的位置判定
pr: 指定了分支的管道enforcing
步骤或其作业中设置了 continueOnError: trueadvisory
设置了 trigger: none、pr: none,且只有 schedules:advisory
任何不是 succeeded() 的 condition:unknown
没有 pr: 的管道unknown

如果没有 pr:,Azure Repos 拉取请求由仓库之外的分支策略进行验证,因此文件无法说明该管道是否会阻止其通过。忽略 succeeded(),因为它是每个步骤已有的默认值。设置了 enabled: false 的步骤永远不会运行,因此不计入判定。任务会像操作一样读取,并将 inputs: 作为输入,因此 SnykSecurityScan 配置了 failOnIssues: false 时,判定为 advisory。

在 SARIF 中,降低强度的控制项会标记为 note,并指向产生该判定的文件;缺失的类别仍标记为 warning。

按语言提供建议

Zanadir 会检测仓库使用的语言,并仅建议适用的工具——不会向 Go 项目推荐 ESLint,也不会向 JavaScript 项目推荐 Pylint。无论生态系统如何都能使用的工具(Trivy、Gitleaks、Codecov 等)始终会被推荐。

检测依据是仓库根目录及其直接子目录中的依赖清单,因此常见的单体仓库布局也能正常工作:

语言检测依据
Gogo.mod
Pythonrequirements.txt、pyproject.toml、setup.py、Pipfile
JavaScriptpackage.json
RubyGemfile、*.gemspec
Javapom.xml、build.gradle、build.gradle.kts
RustCargo.toml
PHPcomposer.json
C#*.csproj、*.sln

node_modules、vendor 和其他依赖目录会被跳过,因为其中的清单描述的是项目未编写的代码。如果未识别出任何语言,则会像之前一样推荐所有工具。

高级用法
排除特定类别

在分析期间跳过某些类别:

zanadir scan --dir . --excluded-categories "SCA,Secrets Detection"

类别名称必须与上方列表中的名称一致。遇到无法识别的名称时会报错,而不会静默忽略。

强制模式

Zanadir 提供 --enforce 标志,确保所有 CI/CD 建议都得到落实。如果有任何建议未落实,CI 流水线就会失败。这有助于在自动化工作流中落实安全最佳实践和合规要求。

zanadir scan --dir . --enforce

构建失败的类别会打印到 stderr:

Enforcement failed: uncovered categories: SCA, Secrets Detection

退出代码:扫描通过时为 0;强制检查失败或扫描无法运行(目录或配置无法读取)时为 1;遇到 zanadir 无法识别的命令行参数时为 2,例如拼错标志。因此,拼写错误会导致构建失败,而不是在未强制执行任何检查的情况下通过。

针对特定类别失败

--enforce 会对所有类别一视同仁,这使得在现有仓库中采用它变得困难:只要有一个类别未覆盖,每次构建都会失败。使用 --fail-on 仅强制检查你关注的类别,其余类别仍只提供信息:

zanadir scan --dir . --fail-on "SCA,Secrets Detection"

--fail-on 会启用强制检查,因此无需同时指定 --enforce。类别名称匹配时不区分大小写。

基线

基线会记录仓库有意接受的缺口。这些类别仍会报告,但不再导致扫描失败。因此,可以在所有问题修复之前启用强制检查,只有新增缺口才会导致构建失败。

根据当前状态生成基线:

zanadir scan --dir . --write-baseline

这会写入 .zanadir-baseline.yaml(可通过 --baseline <path> 覆盖),并成功退出:

# zanadir baseline: categories that are uncovered but accepted.
# These are still reported; they just do not fail a scan.
# Regenerate with: zanadir scan --dir . --write-baseline
version: 1
categories:
    - Coverage
    - Performance Testing

然后依据基线进行强制检查。当仓库中的缺口与基线一致时,扫描会通过;一旦出现新缺口,扫描就会失败:

zanadir scan --dir . --enforce --baseline .zanadir-baseline.yaml

提交基线,以便审核已接受的缺口,并随着时间推移逐步缩减它。

调试模式

获取详细日志信息:

zanadir scan --dir . --debug
配置文件

--config(-c)会从 YAML 文件读取标志值,这样团队就不必在每次调用时重复输入这些值。每个键都是它所设置的标志名称:

# .zanadir.yml
excluded-categories: [Performance Testing]
enforce: true
fail-on-weakened: true
required-checks: true
baseline: .zanadir-baseline.yaml
badge: zanadir-badge.json
output: markdown
zanadir scan --dir . -c .zanadir.yml
zanadir scan --dir . -c .zanadir.yml --output json   # the command line wins
  • 在命令行中指定的标志会覆盖文件中的同名键。
  • 未知键会导致错误,因此拼错 exclude-categories 会使扫描失败,而不会在未应用排除项的情况下静默扫描。
  • 相对路径(baseline、badge、output-file)会相对于配置文件所在目录解析,而不是相对于运行命令的目录。
  • zanadir fix -c 会读取同一文件,但只使用 excluded-categories 和 debug。
  • dir 和 write-baseline 仍需在命令行中指定:前者用于指定扫描目标,后者是一次性操作,而不是设置项。
自定义规则

zanadir 只识别其目录中的工具,因此自研扫描器会被视为缺失类别。可通过配置文件中的 rules: 添加规则,让它识别该工具;规则格式与内置规则相同:

rules:
  - id: acme-scan
    applyOn: [Job.Run]          # Artifact.Name, Job.Name, Job.Package, Job.Run
    categories: [SAST]
    regex: '\bacme-scan\b'
    unless: '--list-rules'      # optional: a match that does not count, e.g. a command that does not scan
    weakenedWhen:               # optional: configurations that cannot fail a build
      - runMatches: '--report-only'
        verdict: advisory       # advisory or partial

如果规则还会匹配 Artifact.Name,则可以使用 unlessPackage,它是一个针对工作流操作的正则表达式:当有操作匹配时,由工作流中的步骤决定是否覆盖,该操作名称不计入(unlessPackage: '^acme/scan-action')。

自定义规则只会扩充目录,不会替换内置规则,因此如果 id 已被占用,就会报错;无法编译的正则表达式或未知类别也会报错。匹配规则即视为已覆盖,其评级方式与其他规则相同,因此 --fail-on-weakened 也会捕获 acme-scan --report-only。zanadir fix 也会使用相同的规则来判断哪些类别未覆盖。

完整示例
# Scan with all options
zanadir scan \
  --dir /path/to/repo \
  --output json \
  --excluded-categories "Linter" \
  --enforce \
  --debug

zanadir fix

扫描会告诉你缺少什么。fix 会打印需要添加的配置:

zanadir fix --dir .
Infrastructure as Code Security is not covered. Add to .github/workflows/security.yml:

  - name: Scan infrastructure as code
    uses: bridgecrewio/checkov-action@v12
    with:
      directory: .

  https://github.com/bridgecrewio/checkov

它只会打印,不会修改任何文件。它会根据仓库检测 CI 平台,并相应地生成 GitHub Actions、GitLab CI、Bitbucket Pipelines、Azure Pipelines 或 Jenkins 的代码片段。

并非所有建议的工具都有模板。如果某个类别中的工具都没有模板,则会跳过该类别,而不是生成不完全准确的内容,因此 fix 覆盖的类别少于 scan 报告的类别。模板位于 fixer/templates.yaml,并将 action 固定到主版本标签;如果 action 没有发布主版本标签,则固定到某个发布版本,绝不会固定到分支。添加模板是一项独立的贡献。

生成工作流

--write 会生成独立的 .github/workflows/zanadir-suggested.yml,而不是打印配置:

zanadir fix --dir . --write
Wrote .github/workflows/zanadir-suggested.yml (9 categories).
It runs as its own job, so it repeats checkout and adds a check to pull requests.

现有工作流绝不会被修改,因此你编写的内容不会受损;最坏的情况也只是多出一个可以删除的文件。重新运行会覆盖生成的文件并产生相同结果;如果该路径下的文件不是由 zanadir 生成的,zanadir 会拒绝覆盖。

这种独立作业需要有所取舍:它会重复执行 checkout,并在检查列表中新增一项,比起将步骤添加到已有作业中会显得更繁琐。你可以随时将这些步骤移入自己的工作流。

目前,--write 仅支持生成 GitHub Actions。在其他平台上,请不带此选项运行并粘贴代码片段。

--excluded-categories 的用法与 scan 相同。

贡献者

欢迎反馈、提交问题和拉取请求;请参阅 CONTRIBUTING.md。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。