关于 zanadir
zanadir 扫描 CI 配置,找出缺失的安全与质量控制,并检查现有控制能否阻止构建。它面向仓库维护者,支持六种 CI 平台并提供发现结果和配置建议。
主要功能
- 01扫描六种 CI 平台
- 02检查控制的执行效果
- 03为缺项推荐工具
- 04生成 CI 修复配置
- 05导出表格、JSON、SARIF 或 Markdown 报告
适用场景
- 01审查仓库的 CI 安全覆盖
- 02检查扫描器能否阻止构建
- 03为缺失控制生成配置
MCP 相关
zanadir 作为 MCP 服务提供只读的 `scan` 和 `fix` 工具。它向 MCP 客户端返回 CI 检查结果和建议配置,不会写入文件。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
docker run -i --rm -e GITHUB_TOKEN docker.io/mustachecase/zanadir:0.24.2GITHUB_TOKEN密钥Optional: a token for the repo argument, used only when the server is started with --repo-token本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
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 Scorecard | zanadir | |
|---|---|---|
| 问题 | 该项目的安全实践状况如何? | 缺少哪些 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 中发布该文件,并让徽章指向它。请将文件放到专用分支,而不要提交到默认分支:受保护的默认分支只接受通过拉取请求提交的更改,因此,尝试将徽章推送到该分支的工作流会被拒绝。

以下作业会在每次推送到默认分支时更新徽章,并在首次运行时创建 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: false | advisory |
位于 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: true | advisory |
设置了 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 等)始终会被推荐。
检测依据是仓库根目录及其直接子目录中的依赖清单,因此常见的单体仓库布局也能正常工作:
| 语言 | 检测依据 |
|---|---|
| Go | go.mod |
| Python | requirements.txt、pyproject.toml、setup.py、Pipfile |
| JavaScript | package.json |
| Ruby | Gemfile、*.gemspec |
| Java | pom.xml、build.gradle、build.gradle.kts |
| Rust | Cargo.toml |
| PHP | composer.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。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。