Repository navigation
Lint
The lint workflows of ncmake-workflows as make targets. Each target collects the same files, runs the same tool in the same version and fails where its workflow fails, so a finding shows on your machine before it shows in a public CI log. make lint-actions-security asks whether a workflow of the repository can be attacked; make lint runs every lint target. The lint-actions-security workflow runs that very target on the runner.
Tip
TL;DR — make dev-init once per machine, then make lint-actions-security (seconds; the first run fetches zizmor once). Findings of medium and high severity are [!] and exit 1. ARGS=--details adds the files it checked, ARGS=--json the machine-readable form, SARIF=<file> the form GitHub code scanning reads, make help-lint-actions-security the full option list.
- What lint-actions-security checks
- Which files it reads
- Where zizmor comes from
- make lint
- Options
- FAQ
zizmor reads GitHub Actions workflows for what makes them attackable: pull_request_target or workflow_run running the code of a pull request, ${{ github.event.* }} expanded into run:, an action not pinned to a commit, permissions broader than the job needs, credentials that actions/checkout leaves behind, cache poisoning in a release workflow. It runs offline, so it needs no token and reads nothing but the checkout, and it reads the repository's own .github/zizmor.yml or zizmor.yml when there is one, as it does on the runner.
Findings of medium and high severity are [!] and exit 1, as they fail the workflow; low and informational ones are [i]. Each names the file and line, the severity and confidence zizmor gives it, and the page of the zizmor audit, which says how to fix the finding or how to ignore it. A finding the repository's configuration or an inline # zizmor: ignore[...] ignores is left out.
The tracked files only, so a file that is not committed is never checked:
- everything under
.github/workflowsand.ncmake/workflows - every
action.ymloraction.yamloutsidenode_modules/andvendor/ - every YAML file with a
.properties.jsonbeside it, the form of a workflow template
A repository without any of them passes. A directory that is not a git checkout is a failed run, exit 2.
make dev-init fetches mk/lint.mk along with the other developer modules. The zizmor version is pinned in that module and nowhere else.
zizmor is the Linux binary of its GitHub release for x86_64 or aarch64, checked against the sha256 digest the module pins, and kept in tools/ of the per-machine ncmake cache, once per version. A newer version replaces the older one once no ncmake container runs. The image ghcr.io/zizmorcore/zizmor is not used: on an older x86-64 CPU its binary stops with an illegal instruction, while the release binary runs.
The binary is mounted read-only into the throwaway Node container of the analysers, next to the analyser itself, so the host needs neither zizmor nor Node and nothing is written into the checkout. With RUNTIME=bare both run on the host directly. make cache-clean lists the fetched zizmor as an entry of its own.
make lint runs every lint target of the module, one after the other, each the way it runs alone. A target that fails does not keep the next one from running, and make lint fails when one failed. ARGS, JSON= and SARIF= belong to a single target, and make lint refuses them.
| Option | What it does |
|---|---|
ARGS=--details |
lists the files that were checked |
ARGS=--persona=pedantic |
zizmor's code smells as well; auditor shows everything, regular is the default |
ARGS=--json |
the full result as JSON, nothing else |
JSON=<file> |
the report as usual, and the JSON in <file> from the same run |
SARIF=<file> |
the report as usual, and the findings as SARIF in <file>, for GitHub code scanning |
Every finding in the JSON carries "security": true, because it says how a workflow can be attacked. The JSON and the SARIF file can lie outside the checkout; each is replaced only once it is complete, and a run that stops on an error writes neither.
Does my app need the workflow for this? No. The target checks whatever workflows the repository has, installed by ncmake or not.
Why the release binary and not a package manager? It is the build zizmor publishes, its digest is pinned in the module, and it needs nothing on the host. The same file runs on the runner.
How do I ignore a finding I accept? As zizmor documents it on the page each finding links: an inline # zizmor: ignore[<audit>] comment or an entry in .github/zizmor.yml. The target reads both.
© 2026 [ernolf] Raphael Gradenwitz · MIT-licensed · Report an issue
For app developers
- Getting started
- How ncmake understands your app
- Building and packaging
- Bundle analysis
- Build tools
- Checkout audit
- Lint
- Releasing
- Integrity signature
- Translations
- Per-app tuning
- Target reference
- AI assistant
CI workflows
App Store
For app users
Git
Background
Reference