zanadir

io.github.MustacheCase由 GitHub 账号 MustacheCase 发布v0.31.0更新于 Nov 7, 2026

Find what a repository's CI is missing, and which security checks cannot fail the build.

已验证STDIO仅桌面开发者工具安全与监控

概览

AI 生成的概览

通过 MCP 提供 zanadir 的只读 CI/CD 安全检查:评估仓库流水线控制项,并给出可直接粘贴的修复配置。

可以做什么
  • 扫描本地检出目录或公开 GitHub 仓库,返回 JSON 报告:覆盖评分、缺失类别及建议工具、被削弱控制项及其评级
  • 针对缺口输出可直接粘贴的 CI 配置,不写入任何文件
  • 列出仓库中发现的 agent skill 及其问题
  • 通过排除指定类别来缩小扫描或修复范围
适用场景
适合让助手审查、加固或修改仓库的 CI 配置,或在新增工具前判断现有安全扫描器是否真的能让构建失败。
运行要求
作为本地进程运行(zanadir mcp),需已安装 zanadir 可执行文件或使用 Docker 镜像;MCP 注册表中的条目以 Docker 运行并只读挂载仓库。扫描范围限于服务器 --dir 指定的目录内。匿名 GitHub 抓取仅限公开仓库;访问私有仓库或提高速率限制需以 --repo-token 启动并设置 GITHUB_TOKEN 或 GH_TOKEN。
安装前请注意
工具被描述为只读,fix 也不写入文件,但服务器可读取仓库文件,且在启用 --repo-token 时会用 GITHUB_TOKEN 访问私有仓库,应将该令牌权限限制到最小。按仓库参数扫描时,仓库内容会发送给 GitHub。README 指出 agent 可能被其读取的内容引导,因此服务器把扫描限制在启动目录内。

安装

在 SourceWeft 中

  1. 打开 控制台中的 zanadir,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

[zanadir]

[zanadir coverage] [Go Reference] [Test coverage] [Latest release] [License: MIT]

zanadir is an open-source CI/CD security tool. It reads a repository's pipeline, from GitHub Actions to GitLab CI, and reports which security and quality checks are missing, or wired so they cannot fail a build.

What it answers

  • What is my pipeline missing? Thirteen categories, from dependency scanning and secrets detection to SBOMs, workflow hardening and agent skill scanning, each checked against the tools that cover it, with suggestions that fit the repository's languages.
  • Does my CI actually block on security findings? A scanner with continue-on-error, || true, a push-only trigger or allow_failure, or one that only reports unless told to fail, like trivy without --exit-code 1, is present but cannot stop a merge. zanadir grades each control enforcing, partial, advisory or unknown rather than just covered; see Effectiveness.
  • Is that check required to merge? With --required-checks, a control only counts as enforcing when branch protection or a ruleset requires it.
  • What do I add? zanadir fix prints ready-to-paste CI configuration for the gaps, or writes a workflow with --write.

It reads GitHub Actions, GitLab CI, CircleCI, Bitbucket Pipelines, Azure Pipelines and Jenkins, from a checkout (--dir) or straight from GitHub (--repo), and reports as a table, JSON, SARIF or Markdown. To see it on a public repository first, scan it in your browser. For how it maps to the OWASP Top 10 CI/CD Security Risks, and how it differs from OpenSSF Scorecard, see the mapping and the comparison.

How common are these gaps? The State of CI security 2026 scanned 1,000 of the most-starred GitHub repositories: 511 declare no dependency, secret or code scanning at all, and 1 in 6 of those that run a scanner in CI have one that cannot fail the build.

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

Installation

Try it on any public repository without installing anything, in your browser at mustachecase.github.io/scan (zanadir runs there as WebAssembly, and the repository's files never leave your browser), or with Docker:

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

To install it, download the archive for Linux, macOS or Windows (amd64 or arm64) from the latest release. checksums.txt lists each archive's SHA-256, and every archive has a build provenance attestation:

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

Or with Homebrew (the tap is this repository) or Go:

sh
brew tap --custom-remote MustacheCase/zanadir https://github.com/MustacheCase/zanadir.gitbrew install mustachecase/zanadir/zanadirgo install github.com/MustacheCase/zanadir@latest

In GitHub Actions, use zanadir-action. This workflow reports on every pull request without failing it:

yaml
# .github/workflows/zanadir.ymlname: zanadiron: [pull_request]permissions:  contents: readjobs:  zanadir:    runs-on: ubuntu-latest    steps:      - uses: actions/checkout@v4        with:          fetch-depth: 0      - uses: mustachecase/[email protected]        with:          dir: .

When you are ready to gate, add enforce: true (fail on any uncovered category), fail-on (only the categories you name) or fail-on-weakened. To post the report as a pull request comment, see Posting the report to a pull request.

zanadir and OpenSSF Scorecard

OpenSSF Scorecard and zanadir answer different questions, and work well together.

OpenSSF Scorecardzanadir
QuestionHow healthy are this project's security practices?Which CI controls are missing, and can the ones present fail a build?
Scope20 checks across the project: branch protection, code review, maintenance, pinned dependencies, token permissions, signed releases, known vulnerabilities, fuzzing and more13 categories of CI controls, each matched against the many tools that provide it
ReadsThe forge's API: GitHub and GitLab repositories, recent pull requests and check runsThe CI configuration: six CI platforms, the Makefiles, Taskfiles, tox, composite actions and templates its jobs call
SAST, for exampleLooks for CodeQL or SonarCloud; its docs note other tools may score lowRecognises Semgrep, CodeQL, gosec, Bandit, Brakeman, SonarQube and others, and grades whether each can fail the build
ResultA 0-10 score per check and overallCovered or not per category, graded enforcing, partial, advisory or unknown, with fixes

Use Scorecard for a project-wide security posture, and zanadir to find out whether the CI you have would actually stop a bad change, and what to add.

Using zanadir with AI coding agents

To have a coding agent check CI with zanadir before changing it, add this to the repository's AGENTS.md, CLAUDE.md or equivalent:

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

As an MCP server

zanadir mcp serves three read-only tools over the Model Context Protocol, so any agent that speaks MCP can grade CI, and check agent skills, before and after changing them:

  • scan returns the JSON report: the score, uncovered categories with suggested tools, and weakened controls with their verdicts;
  • fix returns ready-to-paste configuration for the gaps, and writes nothing;
  • skills returns zanadir skills' JSON: the skills found and their problems.

Each takes dir (a local checkout, by default the current directory) or repo (github.com/owner/name[@ref], read without cloning); scan and fix also take excludedCategories.

An agent can be steered by what it reads, so the server keeps its tools to what you started it with. A dir must be inside --dir (by default the directory the server starts in); a relative one is read from there. repo fetches are anonymous, so they reach public repositories only, unless you start the server with --repo-token to let them use GITHUB_TOKEN or GH_TOKEN.

sh
claude mcp add zanadir -- zanadir mcp

It is listed in the MCP Registry as io.github.MustacheCase/zanadir, where it runs from the Docker image with your repository mounted read-only. For other clients, such as Cursor, VS Code or Windsurf:

json
{  "mcpServers": {    "zanadir": { "command": "zanadir", "args": ["mcp"] }  }}

As a Claude Code plugin

In Claude Code, the zanadir plugin brings both: the MCP server, and a skill that scans, reads your CI configuration, recommends fixes in priority order, and edits files only after you choose. The server runs the installed zanadir if it is 0.18.0 or later, and otherwise the Docker image.

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

Then ask Claude Code to review or harden your CI, or run /zanadir:improve-ci.

llms.txt summarises zanadir for agents that read the repository, and is served at mustachecase.github.io/llms.txt for those that look at a site's root.

Features

  • 📂 Scan: Analyze the repository for CI/CD enhancement suggestions, including security services and best practices.
  • 🔍 CI Analysis: Grades whether each control can actually fail a build, not only whether it is present.
  • 🛠️ Fix: Prints, or writes as a workflow, CI configuration for the categories a scan reports as missing.
  • 🚀 Open Source: Contributions are welcome to enhance Zanadir's capabilities!

Supported CI Actions

Zanadir currently supports:

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

Future work will include support for:

  • Tekton

Categories We Suggest

Zanadir analyzes repositories in the following categories. The name in bold is the exact value to pass to --excluded-categories (matching is case-insensitive):

  • 🛡️ SCA: Software Composition Analysis
  • 🔐 Secrets Detection: Secrets Management
  • 📜 License Compliance: License Compliance
  • 🛠️ End Of Life: End-of-Life Software Packages
  • 📊 Coverage: Test Coverage
  • 📊 Performance Testing: Test Performance and Reliability
  • 🧑‍💻 Linter: Code Linting
  • 🧪 Unit Tests: Automated Unit Testing
  • 🔎 SAST: Static Application Security Testing
  • 🏗️ IaC Security: Infrastructure-as-Code Misconfiguration Scanning
  • 🔗 Supply Chain: SBOM Generation, Signing and Provenance
  • 🧱 Workflow Security: Hardening of the pipeline definition itself
  • 🤖 Agent Skill Security: Scanning agent skills (SKILL.md) for prompt injection and malicious code

Usage Examples

Basic Usage

Scan a repository for CI/CD improvement suggestions:

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

Or scan a GitHub repository without cloning it:

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

--repo lists the repository's files at one commit and downloads only the ones zanadir reads (CI and build files, plus the start of JSON files for infrastructure detection) into a temporary directory. Every other file is an empty placeholder, since only its name matters. It then scans the directory exactly as a checkout and removes it. That is three GitHub API requests per scan, however large the repository. A repository too large to list in one request is downloaded as an archive instead. Public repositories need no token; set GITHUB_TOKEN (or GH_TOKEN) to reach private ones or raise GitHub's rate limit of 60 requests an hour without one. GitLab and Bitbucket repositories still need a clone.

Output Formats

Zanadir supports three output formats: table (default), JSON and SARIF.

Table Output (Default)
sh
zanadir scan --dir . --output table

Sample Output:

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        ||--------------------------------|--------------------------------|-------------------|
10 categories are covered by:  SCA                 Dependabot, govulncheck  Secrets Detection   Gitleaks (advisory)  Linter              golangci-lint  Unit Tests          go test  ...

The covered categories come last, each with the tools found for it. A tool whose run cannot block a merge shows its grade (see Effectiveness). A rule that covers several tools, such as the test command one, names the one it matched: pytest, go test or mvn.

When nothing is missing, the scan says so rather than printing an empty table, and lists what covers each category:

Coverage 11/11 - all categories are covered.
11 categories are covered by:  ...

The headline is bold on an interactive terminal. Colour is omitted when the output is piped or written with --output-file, and when NO_COLOR is set.

JSON Output
sh
zanadir scan --dir . --output json

One object with the score, the CI systems whose configuration was found, the uncovered categories and the tools that cover them, the covered categories whose tooling cannot fully protect the repository, the categories left out of the score, and every covered category with the tools found for it. Every list is an array, empty rather than null when there is nothing in it.

json
{  "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"],  "covered": [    {      "category": "SCA",      "verdict": "advisory",      "tools": [        { "name": "Dependabot", "verdict": "advisory", "locations": [".github/dependabot.yml"] }      ]    }  ]}

In covered, a category's verdict is its strongest tool's, and each tool has its own with the files it runs in. Tools are listed strongest first.

language appears on a suggested tool only when it is specific to one language, and location on a weakened category only when it points inside the repository. Before 0.10.0, --output json printed only the bare array that is now uncovered, with capitalised keys.

SARIF Output
sh
zanadir scan --dir . --output sarif

SARIF is the standard interchange format for static analysis results. Emitting it lets uncovered categories show up in GitHub's Security tab, and in any other SARIF-consuming platform, alongside your other scanners.

Each uncovered category becomes one SARIF rule and one result at warning level, with the suggested tools listed in the result's help text. Results are anchored at a CI configuration file — the place the missing tool would be added. A missing control is not a defect on a particular line, but every SARIF consumer expects a location, and GitHub code scanning rejects a report whose results have none.

Markdown Output
sh
zanadir scan --dir . --output markdown

Markdown is the format for putting a scan in front of people rather than in a file. Tools link to their repositories, and the report opens with a hidden marker so a workflow can update the comment it posted last time instead of adding a new one on every push.

Sample Output:

markdown
<!-- zanadir-report -->### 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) |

When nothing is missing the table is omitted, leaving a single line.

Writing the report to a file

Use --output-file to write the report to a path instead of stdout. This works with every format, and is the reliable way to produce a machine-readable report: redirecting stdout captures the debug log as well, so --output sarif --debug > report.sarif yields a file that is not valid JSON.

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

To publish the report from a GitHub Actions workflow:

yaml
- 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

Uncovered categories then appear in the repository's Security tab alongside your other scanners.

Posting the report to a pull request

SARIF puts uncovered categories in the Security tab, which people visit occasionally. A pull request comment is where they already are.

yaml
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 keeps one comment up to date instead of adding one per push. It matches on the last comment by the same user, so if something else comments as github-actions too, match the marker instead:

yaml
  - name: Find the previous report    uses: peter-evans/find-comment@v4    id: previous    with:      issue-number: ${{ github.event.pull_request.number }}      body-includes: "<!-- zanadir-report -->"
  - 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

A pull request from a fork gets a read-only token, so the comment step cannot post. Guard it with if: github.event.pull_request.head.repo.full_name == github.repository rather than reaching for pull_request_target, which would run your workflow with write access to a fork's code.

Coverage Score

Every scan is summarised as a coverage score: the categories that apply to the repository, minus the ones still uncovered.

Three rules keep the number honest:

  • Excluded categories leave the denominator. A repository is not marked down for a category it has declared irrelevant with --excluded-categories.

  • So do categories with nothing to scan. A repository with no Terraform, Helm chart, Kubernetes manifest or Dockerfile is not told to add Checkov, and is scored out of ten rather than eleven. The scan says which:

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

    Three categories are ruled out this way. IaC Security needs infrastructure to scan, and the detection is deliberately broad — a directory called infra, k8s or charts is enough to keep it. Workflow Security needs GitHub Actions or GitLab CI, the formats its tools read. A GitLab project is pointed at glsec or poutine, a GitHub one at zizmor, actionlint, Scorecard or poutine, and a CircleCI-only repository is not scored on it. Agent Skill Security needs a SKILL.md outside dependency and test directories; it is credited for Cisco Skill Scanner, SkillSpector or Snyk Agent Scan run in CI. To check the skills themselves, see zanadir skills.

    The rest apply to any codebase, and the ones that arguably do not — load testing a library, an SBOM for something that ships nothing — cannot be told apart from the ones that do, so guessing would rule out categories a repository does need.

  • Baseline-accepted gaps still count as uncovered. A baseline records that a gap is tolerated, not that it was closed. A score that rose because someone committed a file would measure nothing.

README Badge

--badge writes the score as a shields.io endpoint file:

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

Publish that file from CI and point a badge at it. Put it on a branch of its own rather than committing it to the default branch: a protected default branch only takes changes through a pull request, so a workflow that pushes the badge there is rejected.

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

A job that keeps it current on every push to the default branch, publishing to a badges branch it creates on the first run:

yaml
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

The badges branch holds that one file and nothing else. If a ruleset covers every branch rather than only the default one, exclude badges from it.

The colour tracks the score: green at 80% or above, yellow at 60%, orange at 40%, red below that, and bright green only at full coverage.

Effectiveness

Presence is not protection. A scanner wired up so it can never fail a build leaves a repository worse off than one with no scanner at all, because everybody believes the control is there. All of these used to report covered:

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

A scan now grades each control it finds and reports the ones that protect less than their presence suggests:

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)
VerdictMeaning
enforcingthe control can fail the build on the path that gates a change
partialit can fail a build, but not on that path - pushes only, say
advisoryit runs, and cannot block anything
unknownthe configuration cannot be read well enough to say

A pull_request trigger gates only if it runs on the change itself and on pull requests to the default branch. types: [closed] runs after the merge, so it grades like a push. A branches: or branches-ignore: filter that leaves out the default branch makes the control unknown. The default branch is set outside the repository: zanadir reads it from the checkout's origin/HEAD, which git clone sets, from GitHub with --repo or --required-checks. Where none of those has it, as in most CI checkouts, main, master, trunk and develop stand in for it, or whichever of them the filter names.

Supply Chain is graded against a different path. An SBOM, a signature or a provenance attestation describes what ships rather than looking for something to block, so it belongs where the artifact is published: running on a release, a push, a tag, a pull request or a workflow chained after one is enforcing. A manual run may be the release itself, which the file cannot tell, so it is unknown. Only a schedule, detached from anything that ships, is advisory, and continue-on-error is advisory here as everywhere: a signing step that can fail silently publishes unsigned artifacts.

SCA is also held to what it can see. A dependency scanner looks packages up by name and version, so one that uv.lock installs from a git repository or a direct URL is invisible to every scanner, and an enforcing SCA control in a repository with such a package is partial, with the packages named:

  SCA is partial (.github/workflows/ci.yml): uv.lock installs en-core-web-sm from a git or URL source, which no dependency scanner can look up

A registry other than PyPI does not count, since a mirror or proxy serves PyPI's packages under the same names, and path, directory and editable sources are the repository's own code.

A weakened control does not fail a scan unless you ask it to. --enforce and --fail-on act only on categories with no tooling; --fail-on-weakened is what turns a weak control into a failure:

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

The two are independent. --enforce on its own still ignores weakened controls, so upgrading does not start failing builds that passed before. --fail-on narrows both.

A baseline accepts a weakness at the verdict it had when accepted:

yaml
version: 1categories:  - Performance Testingweakened:    Secrets Detection: partial

So accepting partial does not also accept it becoming advisory later. If the control gets worse than its recorded verdict, the scan fails again. --write-baseline records the verdicts for you.

Some of this is generic. continue-on-error defeats any control, and so do its shell spellings: gitleaks detect || true, || echo failed, and running the tool as the condition of an if or a !. A || exit 1 that fails again is fine. So does a workflow that only runs on a schedule. The rest is specific to one tool, and a rule can say so:

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

A check is either a regex over the step's shell command or one action input compared to a value - deliberately not an expression language. Covered today: gitleaks --exit-code 0, tfsec and checkov --soft-fail / soft_fail: true, and anchore/scan-action with fail-build: false.

Some tools are the other way round: they exit 0 on findings unless told to fail. trivy without --exit-code 1 (including trivy-action without exit-code), grype without --fail-on, kubescape without a severity or compliance threshold (its old --fail-threshold is ignored by current releases, so it is unknown), semgrep scan without --error and trufflehog without --fail only report, so they are advisory. When the repository has a config file the tool reads by default, such as trivy.yaml or .grype.yaml, or the step names one with --config, the file may set it, and the control is unknown:

yaml
    exitsZeroUnless:      runMatches: "--fail-on\\b"          # what makes it fail      unknownWhen: "--config\\b"          # what may, out of sight      configFiles: [".grype.yaml"]        # read from the repository by default

For actions that take flags through an input, exitsZeroUnless can name it. The flawfinder action, for example, uses:

yaml
    exitsZeroUnless:      input: "arguments"      runMatches: "--error-level\\b"

Matched shell commands are checked first. If none match and the step matches by its action, the named input is checked instead. Flawfinder with missing or empty arguments, or with only --sarif ./, is advisory; --error-level=4 lets the normal triggers decide its grade. An input set by an expression such as ${{ inputs.args }} is unknown, even if the expression mentions the flag.

zanadir cannot see an exit code set in a workflow's env:, such as TRIVY_EXIT_CODE, so such a step reads as advisory.

Two deliberate silences. An if: condition is never interpreted, because it can reference arbitrary context and reading one wrong is worse than admitting it cannot be read - the control is reported unknown instead. And a category is judged by its strongest control, so one properly wired scanner keeps the category quiet however many weak ones sit beside it.

Required checks

A scanner can run on every pull request and still block nothing, because its check is not required to merge. That lives in the repository's rulesets and branch protection, not its files, so it is opt-in:

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

zanadir reads the default branch's required checks from GitHub (public repositories need no token; GITHUB_TOKEN or GH_TOKEN is used if set). A GitHub Actions control that would be enforcing stays so only when its job's check is required, matrix (build (ubuntu-latest)) and reusable-workflow (codeql / Analyze) names included; otherwise it is partial. A ruleset that blocks merges on a tool's code scanning alerts counts too: a scanner that runs on every pull request and uploads to code scanning is enforcing even when its own step only reports, such as trivy-action writing SARIF without exit-code:

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.

It also names settings that look like protection and are not: a ruleset that requires checks but targets no branch or only evaluates, and a required GitHub Actions check that no workflow job reports. Checks other apps report, such as DCO, are never judged. If the settings cannot be read, the scan warns and grades without them. Only GitHub is read; on other platforms the merge settings are still outside what zanadir can see.

Controls declared outside the pipeline

Dependabot and Renovate are detected from their configuration - .github/dependabot.yml, renovate.json and the other names Renovate accepts - not from a CI job, because that is not where they live.

They are graded advisory. Both run on the forge's own schedule and open pull requests; neither can fail a build. So a repository whose only dependency control is Dependabot is reported as covered and weakened, which is a more useful answer than either "you have no SCA" or "you are covered":

1 covered category is weakened:  SCA is advisory (.github/dependabot.yml)

Platform coverage

GitHub Actions is graded in full. GitLab CI is graded for the two signals that are unambiguous there - allow_failure: true, which is continue-on-error under another name, and when: manual, a job that only runs when somebody asks.

GitLab's include: is followed. A local file (local: or a bare path, wildcards allowed) is read as more of the same configuration. GitLab's own templates and CI/CD components cannot be read from the repository, so they are recognised by name: Jobs/SAST.gitlab-ci.yml or components/sast/sast@... counts as SAST, and likewise for Secret Detection, Dependency and Container Scanning, SAST-IaC, License Scanning, Code Quality and the performance-testing templates. A project: or remote: include is skipped. A job zanadir cannot read is skipped rather than failing the scan.

GitLab's rules:, only: and except: are deliberately not interpreted. Whether a branch pipeline gates a merge depends on a project setting that is not in the repository, so reading them would be guessing. A GitLab job with neither weakening field is reported unknown, which says nothing rather than something wrong. CircleCI has no equivalent signal and is not graded.

Bitbucket Pipelines splits its file by trigger, so most of it is graded:

Where the tool runsVerdict
pull-requests:enforcing
branches: for one named branch, such as mainpartial - it runs after the merge
tags:, custom:, or a step or stage with trigger: manualadvisory
after-script:advisory - a failing after-script does not fail the step
default:, or a wildcard branch patternunknown

default: also runs on a pull request's source branch, and whether that build blocks the merge is a merge check configured outside the repository, so it is left unknown for the same reason as GitLab's branch pipelines. Pipes are read like GitHub actions: pipe: snyk/snyk-scan counts as Snyk, and DONT_BREAK_BUILD: "true" grades it advisory.

A Jenkinsfile (and variants beside it such as Jenkinsfile.release) is read as Groovy tokens, not run. sh, bat and powershell steps are read like run:, and other steps such as snykSecurity, dependencyCheck, withSonarQubeEnv or junit like actions, with their named arguments as inputs. Which branches and pull requests build is configured on the Jenkins job, not in the file, so a control is unknown unless the file itself weakens it:

In the JenkinsfileVerdict
inside catchError(buildResult: 'SUCCESS') or buildResult: nulladvisory - the build stays green
a step option such as snykSecurity(failOnIssues: false) or waitForQualityGate abortPipeline: falseadvisory
inside when { }, if, warnError or catchError(buildResult: 'UNSTABLE'), or sh(returnStatus: true)unknown

A shared-library step configured only by assignments, such as mavenBuild { dependencyCheck = 'enabled' }, cannot be read.

Azure Pipelines is read from azure-pipelines.yml (or .yaml) at the repository root, following template: and extends: references to files in the same repository. A template in another repository (path@alias) or one named by an expression cannot be read and is skipped.

Where the tool runsVerdict
a pipeline whose pr: names branchesenforcing
continueOnError: true on the step or its jobadvisory
trigger: none, pr: none and only schedules:advisory
any condition: other than succeeded()unknown
a pipeline with no pr:unknown

With no pr:, an Azure Repos pull request is validated by a branch policy outside the repository, so the file cannot say whether the pipeline gates it. succeeded() is ignored because it is the default every step already has. A step with enabled: false never runs and counts for nothing. Tasks are read like actions, with inputs: as their inputs, so SnykSecurityScan with failOnIssues: false grades advisory.

In SARIF a weakened control is a note, pointing at the file that produced the verdict, while a missing category stays a warning.

Language-Aware Suggestions

Zanadir detects which languages a repository uses and suggests only tools that apply to it — a Go project is not offered ESLint, and a JavaScript project is not offered Pylint. Tools that work regardless of ecosystem (Trivy, Gitleaks, Codecov, and so on) are always suggested.

Detection is based on dependency manifests in the repository root and its immediate subdirectories, so common monorepo layouts work:

LanguageDetected from
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 and other dependency directories are skipped, since their manifests describe code the project did not write. If no language is recognised, every tool is suggested, exactly as before.

Advanced Usage

Exclude Specific Categories

Skip certain categories during analysis:

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

Category names must match the list above. An unrecognized name is rejected with an error rather than being silently ignored.

Enforce Mode

Zanadir provides an --enforce flag to ensure that all CI/CD suggestions are fulfilled. If any suggestion is not met, the CI pipeline will fail. This helps enforce security best practices and compliance in automated workflows.

sh
zanadir scan --dir . --enforce

The category that failed the build is printed to stderr:

Enforcement failed: uncovered categories: SCA, Secrets Detection

Exit codes: 0 when the scan passes, 1 when enforcement fails or the scan cannot run (an unreadable directory or configuration), and 2 for a command line zanadir does not understand, such as a mistyped flag. A typo therefore fails the build instead of passing it with nothing enforced.

Failing on Specific Categories

--enforce is all-or-nothing, which makes it hard to adopt on an existing repository: a single uncovered category fails every build. Use --fail-on to enforce only the categories you care about, while the rest stay informational:

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

--fail-on implies enforcement, so --enforce is not needed alongside it. Category names are matched case-insensitively.

Baseline

A baseline records the gaps a repository knowingly accepts. Those categories are still reported, but they no longer fail a scan — so enforcement can be switched on before everything is fixed, and only new gaps break the build.

Generate one from the current state:

sh
zanadir scan --dir . --write-baseline

This writes .zanadir-baseline.yaml (override with --baseline <path>) and exits successfully:

yaml
# 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-baselineversion: 1categories:    - Coverage    - Performance Testing

Then enforce against it. The scan passes while the repository's gaps match the baseline, and fails as soon as a new one appears:

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

Commit the baseline so the accepted gaps are reviewable, and shrink it over time.

Debug Mode

Get detailed logging information:

sh
zanadir scan --dir . --debug
Config File

--config (-c) reads flag values from a YAML file, so a team stops repeating them on every invocation. Each key is the name of the flag it sets:

yaml
# .zanadir.ymlexcluded-categories: [Performance Testing]enforce: truefail-on-weakened: truerequired-checks: truebaseline: .zanadir-baseline.yamlbadge: zanadir-badge.jsonoutput: markdown
sh
zanadir scan --dir . -c .zanadir.ymlzanadir scan --dir . -c .zanadir.yml --output json   # the command line wins
  • A flag given on the command line overrides the same key in the file.
  • An unknown key is an error, so a misspelt exclude-categories fails the scan instead of silently scanning without the exclusion.
  • Relative paths (baseline, badge, output-file) are resolved against the directory holding the config file, not the directory you run from.
  • zanadir fix -c reads the same file and uses only excluded-categories and debug.
  • dir and write-baseline stay on the command line: one says what to scan, the other is a one-off action rather than a setting.
Custom Rules

zanadir only recognises the tools in its catalogue, so an in-house scanner reads as a missing category. Teach it with rules: in the config file, in the same shape as the built-in rules:

yaml
rules:  - id: acme-scan    name: Acme Scan             # optional: the tool the report names; without it, the matched text    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    exitsZeroUnless:            # optional, for a tool that only reports by default      runMatches: '--fail'      # the flag that makes it fail

A rule that also matches on Artifact.Name can take unlessPackage, a regex over the workflow's actions: when one matches, the workflow's steps decide and its name does not count (unlessPackage: '^acme/scan-action').

An exitsZeroUnless with input requires Job.Package in applyOn; it may also include Job.Run to grade direct commands. Without input, it requires Job.Run. The runMatches regex is required in either case.

A command's executable is matched by its unquoted basename: "$RUNNER_TEMP/grant" check is recognised as grant check. Arguments keep their quoting, and the original command is retained as evidence. Custom command rules use the same normalization.

Custom rules add to the catalogue and never replace a built-in rule, so an id that is already taken is an error, as is a regex that does not compile or an unknown category. A rule that matches counts as coverage and is graded like any other, so --fail-on-weakened catches acme-scan --report-only too. zanadir fix uses the same rules to decide what is uncovered.

Complete Example
sh
# Scan with all optionszanadir scan \  --dir /path/to/repo \  --output json \  --excluded-categories "Linter" \  --enforce \  --debug

zanadir fix

A scan tells you what is missing. fix prints the configuration to add:

sh
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

It prints only; no file is touched. The CI platform is detected from the repository, and snippets are emitted for GitHub Actions, GitLab CI, Bitbucket Pipelines, Azure Pipelines or Jenkins accordingly.

Not every suggested tool has a template, and some templates need a file the repository may not have: the end-of-life check builds an image from a Dockerfile, and the load test runs a load-test.js written for the service. fix emits nothing half-right. A category it cannot fill is listed at the end with the reason, and with the exclusion to use when the category does not apply:

Not covered, with no snippet that fits:  End-of-Life Package Checkers: Trivy checks a container image, and there is no Dockerfile at the repository root to build one. If it does not apply here, exclude it with -e "End Of Life".

Coverage snippets set a floor the test run enforces itself (pytest --cov-fail-under, go-test-coverage), with no account or token; set it to today's coverage. Templates live in fixer/templates.yaml and pin every action to a commit with its release in a comment, so the generated workflow passes zizmor; images, packages and binaries are pinned to a release. Adding one is a self-contained contribution.

Generating a workflow

--write generates a standalone .github/workflows/zanadir-suggested.yml instead of printing:

sh
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.

The categories it could not fill are listed after that, and in a comment at the top of the file.

Existing workflows are never edited, so nothing you wrote can be damaged; the worst case is a file you delete. Re-running overwrites the generated file and produces the same result, and zanadir refuses to overwrite a file at that path that it did not generate.

That standalone job is the trade-off: it repeats checkout and adds its own entry to the checks list, which is noisier than adding the steps to a job you already have. Move them into your own workflow whenever you prefer.

--write currently generates GitHub Actions only. On other platforms, run without it and paste the snippets.

--excluded-categories works as it does for scan.

zanadir skills

Agent skills (SKILL.md files for Claude Code, Codex and other agents) fail quietly: a vague description never triggers, a linked script was deleted, two skills compete for the same requests. skills finds every SKILL.md in a checkout, or in a GitHub repository without cloning it, and reports them:

sh
zanadir skills --dir .zanadir skills --repo github.com/owner/namezanadir skills --home   # your own skills, which agents load in every project
Found 3 skills: .claude/skills/deploy, .claude/skills/review, plugins/acme/skills/db-migrate
Quality  ! .claude/skills/deploy/SKILL.md:3  description says what the skill does but not when to use it; add a "Use when ..." sentence  [description-no-trigger]  ✗ plugins/acme/skills/db-migrate/SKILL.md:41  links scripts/rollback.sh, which does not exist  [missing-reference]
Safety  ! .claude/skills/review/scripts/setup.sh:4  downloads a script and pipes it straight into a shell  [pipe-to-shell]
1 error, 2 warnings
RuleSeverityFinds
frontmatter-invaliderrorfrontmatter that is not valid YAML
missing-referenceerrora link to a file in the repository that does not exist
hidden-unicodeerrorbidirectional overrides or tag characters, which can hide text from a reviewer; a warning for zero-width characters in SKILL.md
frontmatter-missing, name-missing, description-missingwarningno frontmatter, name or description
name-format, name-mismatchwarninga name that is not lowercase-hyphenated, or does not match its directory
description-short, description-longwarningunder 40 characters, or over the 1,024 the Agent Skills specification allows
description-no-triggerwarninga description that says what, never when ("Use when ...")
description-overlapwarningtwo skills in one skills directory whose descriptions share most of their words
body-longwarninga SKILL.md over 500 lines, which loads whole every time
bash-unrestrictedwarningallowed-tools granting every shell command
pipe-to-shell, skip-approvalswarningcurl | sh, or running the agent without approvals or its sandbox, in SKILL.md or a script

The safety rules are shallow on purpose and read only what the agent follows or runs, not reference documents that describe a risky pattern. For a security audit of a skill, use SkillSpector.

--output json gives the findings to other tools, --output sarif to GitHub code scanning, and --output markdown to a pull request comment; --enforce exits non-zero when any error is found. The SARIF has its own code scanning category, zanadir/skills/, so uploading it does not replace a scan's results. The Markdown lists errors first, at most 50 findings, and starts with <!-- zanadir-skills-report --> so a workflow can update its last comment.

--home checks the skills agents load for you in every project: ~/.claude/skills (or $CLAUDE_CONFIG_DIR/skills) for Claude Code, ~/.agents/skills and ~/.codex/skills for Codex, and ~/.cursor/skills for Cursor, which reads the others too. A skill folder that is a symlink is read where it points, as the agents do, and a skill linked from two places is checked once. Skills inside installed plugins are not read yet. Dependency, test and .git directories are skipped. --repo fetches only the skill directories' files, as scan --repo fetches only CI files, and needs GitHub's file list to find them: a repository too large for one listing has to be cloned and checked with --dir.

Contributors

Feedback, issues and pull requests are welcome; see CONTRIBUTING.md.

来源:README.md,提交 f3c137d

工具

0
SourceWeft 尚未在沙箱中启动过该软件包,因此其工具未知。

版本历史

11
  1. v0.31.0最新Nov 7, 2026
  2. v0.29.0Nov 5, 2026
  3. v0.28.0Nov 3, 2026
  4. v0.27.0Nov 1, 2026
  5. v0.26.1Oct 31, 2026
  6. v0.25.0Oct 31, 2026
  7. v0.24.2Oct 29, 2026
  8. v0.23.0Oct 26, 2026
  9. v0.22.0Oct 26, 2026
  10. v0.21.0Oct 25, 2026
  11. v0.20.0Oct 23, 2026

评分

已满足适用检查项的 50%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分

    请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。

  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具15 分

    请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。

  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分