Audit GitHub Actions workflows, composite actions, Dependabot configs, and pre-commit configs for security findings using zizmor — template injection, credential persistence, unpinned uses, over-broad permissions, impostor commits. Covers local CLI, auto-fix, zizmor.yml policy, severity-based CI gates, SARIF upload, and pre-commit. Use when asked to "audit my workflows", "run zizmor", "is this workflow safe", "check for template injection", "pin my actions", or "set up a zizmor CI gate". Workflow syntax and shell errors → /platform-skills:github-actions (actionlint). IaC misconfig → /platform-skills:checkov. Image and dependency CVEs → /platform-skills:trivy. Keeping SHA pins fresh → /platform-skills:renovate.
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
The canonical home for this skill is zizmor in nitinjain999/platform-skills
Audit GitHub Actions workflows for security findings — before an attacker reads them for you.
Read references/zizmor.md before responding. It contains bootstrap steps, the full 41-audit catalogue with persona and online gating, zizmor.yml schema and discovery rules, exit-code semantics, auto-fix blast radius, and the CI and pre-commit templates.
This command is interactive by default. With no arguments, run the three-layer wizard below. Never dump a raw zizmor invocation and stop — the useful output is a triaged verdict, not a finding list.
| Question | Authoritative command |
|---|---|
| "Is this workflow valid?" (syntax, schema, shell bugs) | /platform-skills:github-actions — actionlint |
| "Is this workflow safe?" (injection, permissions, pinning) | this command |
"Is my action.yml well designed?" | /platform-skills:composite-actions |
| "How do I keep these SHA pins current?" | /platform-skills:renovate |
| "Is there a misconfig in my Terraform / Helm / Dockerfile?" | /platform-skills:checkov |
| "Does this image or dependency have CVEs?" | /platform-skills:trivy |
| "How do I sign images / generate an SBOM?" | /platform-skills:supply-chain |
Zizmor and actionlint are complements. A workflow can be perfectly valid and still hand an attacker a shell. When the user asks for "workflow checks" without qualifying, run both and say which tool produced which finding.
Parse the first word of $ARGUMENTS as the mode. When $ARGUMENTS is empty, run the three-layer interactive wizard.
| Mode | What it does |
|---|---|
scan | Default-persona audit of the current repo — the everyday run |
audit | Deep sweep: --persona=auditor --min-confidence=low, all collectors, online |
fix | --fix=safe (or all on request), show the diff, re-audit what remains |
triage | Walk existing findings one at a time → fix / inline-ignore / config-ignore / remap / disable |
config | Generate or repair .github/zizmor.yml — pinning policy, allowlists, suppressions |
ci | Emit the GitHub Actions workflow — SARIF alerts, hard-fail gate, or both |
precommit | Add the zizmor-pre-commit hook and explain the offline caveat |
explain | Explain one audit id or one finding, and how to fix it properly |
| (empty) | Three-layer interactive wizard |
What are you trying to find out?
1. "Are my workflows safe to merge?" → scan (default persona, online)
2. "Give me everything, I'm doing a review." → audit (auditor persona, low confidence)
3. "Just fix what can be fixed." → fix (--fix=safe, diff-reviewed)
4. "I have findings — help me decide." → triage
5. "Set up a policy file for this repo." → config
6. "Make this a check on every PR." → ci
7. "Catch it before I even commit." → precommit
8. "What does this finding actually mean?" → explain
Workflow syntax / shell errors? → /platform-skills:github-actions
action.yml design review? → /platform-skills:composite-actions
Terraform or Helm misconfig? → /platform-skills:checkov
Image or dependency CVEs? → /platform-skills:trivy
Enter 1–8 or a mode name:Ask only what the chosen mode needs.
What should I audit? (press Enter for the recommended value)
• Path [.]: current repo, or a specific workflow / action / directory.
Remote repos work too — enter `owner/repo` or `owner/repo@ref`.
• Online mode [on]: four audits only work online — impostor-commit,
known-vulnerable-actions, ref-confusion, stale-action-refs.
zizmor uses a GitHub token if one is set in your environment
(GH_TOKEN, GITHUB_TOKEN or ZIZMOR_GITHUB_TOKEN); I never read or create
one. Enter to accept, or type 'offline' to skip those four.
• Collectors [default]: workflows + actions + dependabot + pre-commit,
respecting .gitignore. Type 'all' to include gitignored paths (slower),
or name specific ones (e.g. 'workflows,actions').Detect and resolve silently where possible:
zizmor --version. Not installed → offer uvx zizmor@1.29.0 (no install) or brew install zizmor. Do not ask permission to check the version.GH_TOKEN, GITHUB_TOKEN or ZIZMOR_GITHUB_TOKEN from the environment itself. When its output says it is running in offline mode, state plainly that the four online audits were skipped, and that they run once the user exports one of those variables in the shell that starts Claude Code..github/zizmor.yml or zizmor.y{a,}ml at the repo root. Found → say which file will apply. Not found → note that defaults apply, including blanket SHA-pinning for all actions.zizmor.yml in a subdirectory is never read.A few decisions worth making explicitly (Enter accepts the recommendation):
• Pinning policy [hash-pin]: how should `uses:` be pinned?
1. hash-pin — full 40-char commit SHA + `# vX.Y.Z` comment (RECOMMENDED)
2. ref-pin — semver tag only, e.g. `@v4.2.1`
3. mixed — hash-pin third parties, ref-pin namespaces you own
SHA is recommended because a tag is a mutable pointer: the author can
move `v4` — or `v4.2.1` — to different code after you reviewed it, and
your workflow silently executes it on the next run. A SHA cannot be
moved. Choosing 2 or 3 is a deliberate trust decision, not a default.
• Persona [regular]: pedantic adds code smells; auditor adds likely false
positives. Recommended regular for gates, pedantic for a cleanup pass.
Never gate CI on auditor.
• Strict collection [on]: without it, a workflow that fails to parse is
warned about and skipped — "0 findings" can mean "0 files audited".
Enter to keep on.
• Severity floor for failing [medium]: informational and low still print,
they just don't block. Enter to accept, or name a different floor.Ask the pinning question before running anything in scan, fix, config, or ci mode, because it determines whether unpinned-uses findings are real work or expected noise. Zizmor's own default since v1.20.0 is a blanket hash-pin for every action, including actions/* — so a repo that ref-pins actions/checkout@v5 gets flagged unless the policy says otherwise. Do not silently write a ref-pin policy to make findings disappear; that is a security downgrade disguised as a config change.
If the answer is 2 or 3, say plainly what is being accepted: "ref-pin means you trust <namespace> not to move a tag under you, and you lose the ability to prove which code ran in a past build." Then record the reason as a comment in .github/zizmor.yml, so the next reviewer sees the decision rather than re-litigating it.
Whichever policy is chosen, pair it with /platform-skills:renovate. A SHA pin with no update automation rots into stale-action-refs and unpatched advisories, which is a worse position than a ref pin that at least tracks patches.
Ask these only when the situation triggers them:
| Trigger | Question |
|---|---|
fix mode with a dirty working tree | "Working tree has uncommitted changes and --fix edits files in place. Commit or stash first? (recommended) / proceed anyway" |
fix mode, user asked for unsafe fixes | "--fix=all applies unsafe fixes that can change workflow behaviour. Every hunk needs manual review. Continue? (y/N)" |
unpinned-uses findings exceed 20 | "This repo predates zizmor's blanket hash-pin default. Pin everything now (recommended), or set a ref-pin policy for namespaces you own and hash-pin the rest?" |
| Repo already mixes SHA pins and tag pins | "Some uses: are SHA-pinned and some are tag-pinned. Standardise on SHA (recommended), or encode the split as an explicit unpinned-uses policy so the intent is reviewable?" |
config mode about to write a file | "Write .github/zizmor.yml? (y/N)" — show the full proposed file first |
ci mode, repo already has a zizmor workflow | ".github/workflows/<name>.yml already runs zizmor. Update it in place, or add a second workflow?" |
| Findings suppressed with no justification found | "N existing suppressions have no reason string. Add justifications now?" |
Never ask about: output format (default plain, sarif only in CI), whether to install zizmor (just do it via uvx), or whether to re-audit after fix (always re-audit). Never check for saved GitHub logins or token variables (no gh auth status, no inspecting GH_TOKEN): zizmor's own output says whether it ran online or offline.
The everyday run. Default persona, online if a token is available, strict collection.
# Recommended local invocation. Online audits run when GH_TOKEN, GITHUB_TOKEN or
# ZIZMOR_GITHUB_TOKEN is set in your environment; zizmor reads it itself.
zizmor --strict-collection .
# Explicitly offline — the four online audits are skipped
zizmor --strict-collection --offline .
# Without a local install
uvx zizmor@1.29.0 --strict-collection .Report in this order:
file:line, the audit id, and one sentence on why it matters in this repo.--fix=safe would resolve, with the count.Then offer: fix for the auto-fixable set, triage for the rest.
Deep sweep for a human security review. Not a gate.
zizmor --strict-collection \
--persona=auditor \
--min-confidence=low \
--collect=all \
--no-ignores \
.--no-ignores (v1.25.0+) re-surfaces everything currently suppressed by zizmor.yml and inline comments. That is the point of an audit: suppressions rot, and a review that trusts them is not a review.
--collect=all here means "ignore .gitignore while collecting", not "collect more input kinds" — default collection already finds workflows, action.yml, Dependabot and pre-commit configs. It belongs in an audit and nowhere else: it is materially slower on any repo with a large ignored tree, and a gate does not need it.
State up front that the auditor persona is documented to include likely false positives, so a raw count from this mode is not a security score. Present findings in three buckets: confirmed, needs repo context to judge, likely false positive.
# Refuse to proceed on a dirty tree — the diff is the review artifact
git status --porcelain | grep -q . && { echo "commit or stash first"; exit 1; }
zizmor --fix=safe .
git --no-pager diff
zizmor --strict-collection . # what remains needs a humanBlast radius to state before running:
unpinned-uses detects offline but needs a token to resolve the SHA it pins to. Fixing offline silently fixes less.--fix masks exit codes. If everything is fixed, zizmor exits 0. Never put --fix in a gate.--fix=all includes unsafe fixes that can change behaviour and require hunk-by-hunk review.Rollback: git checkout -- . (or git restore .) before committing.
If unpinned-uses is among the findings, confirm the pinning policy first (Layer 3). --fix pins to a SHA — the recommended outcome — but that is a repo-wide convention change, so it should be a stated decision rather than a side effect of running a fixer. Fixing unpinned-uses also needs a token to resolve each ref to its SHA; offline it silently fixes fewer findings.
After fixing, hand off to /platform-skills:commit for the commit message, and to /platform-skills:renovate so the freshly pinned SHAs do not rot into stale-action-refs.
Walk findings one at a time. For each, apply the decision tree in references/zizmor.md → "Triage decision tree" and recommend exactly one outcome:
| Outcome | When | Mechanism |
|---|---|---|
| Fix | True positive, actionable | --fix=safe, or hand-edit |
| Inline ignore | Accepted, location-specific, or in a composite action | # zizmor: ignore[id] <reason> |
| Config ignore | Accepted, many findings or whole files | zizmor.yml rules.<id>.ignore |
| Remap down | Audit is noisy but should stay counted | rules.<id>.remap.severity |
| Disable | Audit genuinely inapplicable — last resort | rules.<id>.disable: true |
| Report upstream | Genuine false positive | File it with zizmor; do not suppress silently |
Two rules with no exceptions:
zizmor.yml. They must use inline comments. If the user asks for a config ignore on an action.yml finding, correct them.Prefer remap.severity over disable. Disabled rules do not appear in suppressed counts, so future findings from that audit are invisible forever.
Generate or repair .github/zizmor.yml. Show the complete proposed file and get approval before writing.
Decisions to walk through:
Pinning policy — the Layer 3 question, written down. Offer these three shapes and recommend the first:
# 1. hash-pin everything (RECOMMENDED) — this is also zizmor's default,
# so the block is only worth writing to make the intent explicit.
rules:
unpinned-uses:
config:
policies:
"*": hash-pin# 2. ref-pin only the namespaces this org controls; hash-pin all third parties.
# Most specific pattern wins regardless of order.
rules:
unpinned-uses:
config:
policies:
# We own this org and enforce protected tags on every release repo.
"my-org/*": ref-pin
"*": hash-pin# 3. ref-pin broadly. A deliberate downgrade — only with a stated reason.
rules:
unpinned-uses:
config:
policies:
"*": ref-pinAsk which namespaces are trusted and why, and put the answer in the comment. "We own it" is a reason; "it's a big org" is not — actions/* is a big org and zizmor's default still hash-pins it. Pattern specificity order and the any policy are in references/zizmor.md → "Repository patterns".
Existing suppressions — migrate scattered inline ignores into config where they are file-wide, keep them inline where they are location-specific.
Severity promotions — which audits are release-blocking for this org.
Configurable audits — only five accept config:: unpinned-uses, forbidden-uses, known-vulnerable-actions, secrets-outside-env, dependabot-cooldown. Do not invent config: blocks for any other audit.
Always place the file at .github/zizmor.yml and include the SchemaStore comment so editors validate it. See references/zizmor.md → "Worked baseline config".
State the discovery trap explicitly: in a Git repo, discovery starts and ends at the repo root. A zizmor.yml anywhere else is dead config.
Ask which shape is wanted before generating:
How should this gate behave?
a. Security-tab alerts (SARIF) → findings become code-scanning alerts;
the job stays GREEN, blocking is a ruleset decision
b. Hard-fail check → the job goes RED on medium+ findings
c. Both → alerts for visibility, plus a failing check
Enter a, b, or c:This choice matters more than it looks. --format=sarif suppresses exit codes 11–14, so a SARIF-only workflow passes even with high-severity findings. If the user says "fail the build", (a) alone will not do it — say so rather than shipping a gate that cannot fail.
Copy the matching template from references/zizmor.md → "CI integration": Option A (zizmorcore/zizmor-action), Option B (uvx hard-fail gate), Option C (both). All three ship with permissions: {} at the top, job-scoped security-events: write only where SARIF is uploaded, persist-credentials: false on checkout, and SHA-pinned action refs with version comments.
Two action-specific traps to state when using Option A:
annotations: true is mutually exclusive with advanced-security: true. Enabling annotations requires setting advanced-security: false explicitly.version: input rather than leaving latest, or the check's behaviour changes without a commit.# .pre-commit-config.yaml
repos:
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.29.0
hooks:
- id: zizmorAlways state the caveat: the hook runs in whatever mode the local environment implies, which without a token is offline. Pre-commit passing is therefore not equivalent to CI passing — the four online audits do not run locally. That trade is usually right for hook latency, but it must be said, not discovered.
files: is unnecessary — the hook scans the whole repo by default. Works with prek as well as pre-commit. Hand off to /platform-skills:renovate to keep rev: current.
Given an audit id (template-injection) or a pasted finding, produce:
pedantic/auditor, or online access. Several audits are invisible by default; that is often the real question behind "why didn't zizmor catch this?".Pull the facts from the audit catalogue in references/zizmor.md. Never invent an audit id, a config: key, or a persona requirement — if it is not in the catalogue, say so and point at https://docs.zizmor.sh/audits/.
Before asking anything, classify the free-text request:
| Intent signals | Mode |
|---|---|
| "audit workflows", "run zizmor", "is this safe", "check my actions" | scan |
| "security review", "everything", "full sweep", "auditor", "pentest my CI" | audit |
| "fix", "pin my actions", "hash-pin", "remediate", "auto-fix" | fix |
| "too noisy", "false positive", "suppress", "ignore this", "which do I care about" | triage |
| "zizmor.yml", "policy", "allowlist", "config", "set a pinning rule" | config |
| "CI", "PR check", "GitHub Actions gate", "SARIF", "security tab", "block merges" | ci |
| "pre-commit", "before commit", "local hook", "prek" | precommit |
| "what does X mean", "why is this flagged", "CKV-style id", any bare audit id | explain |
"audit my dependabot.yml", "is my Dependabot config safe", dependabot-execution, dependabot-cooldown, "cooldown" | scan with --collect=dependabot |
| "syntax error", "actionlint", "shellcheck", "invalid workflow" | → /platform-skills:github-actions |
| "action.yml inputs", "composite action design" | → /platform-skills:composite-actions |
| "terraform", "helm", "kubernetes manifest", "dockerfile misconfig" | → /platform-skills:checkov |
| "CVE", "image scan", "vulnerable dependency" | → /platform-skills:trivy |
| "keep pins updated", "renovate", "bump my pins", "update schedule", "grouping rules" | → /platform-skills:renovate |
A bare mention of Dependabot is not a handoff signal. Split on intent, not on the tool name: update management (schedules, grouping, ignore rules, which ecosystems to track) belongs to /platform-skills:renovate; security auditing of the config itself stays here, because zizmor owns dependabot-execution (insecure-external-code-execution) and dependabot-cooldown. "Set up Dependabot for npm" → renovate. "Is this dependabot.yml safe" → scan.
--persona=auditor — documented to include likely false positives; the team stops trusting the check.--fix in a gate — it repairs its own input and always passes.config: key, persona requirement, or CLI flag. The catalogue in references/zizmor.md is verified against zizmor v1.29.0; anything absent from it needs https://docs.zizmor.sh/audits/ checked first.disable: when ignore, a lower persona, or remap.severity would work.ref-pin policy just to clear unpinned-uses findings. Relaxing the policy is a security decision the developer makes with the trade-off stated, not a way to reach a green run.unpinned-uses findings or writing a config. Never infer it from what the repo happens to do today.--strict-collection to every CLI invocation, so "0 findings" cannot mean "0 files audited". It has no equivalent in zizmorcore/zizmor-action — no input, no env var, and rules is the only top-level key the config file accepts. So when generating an action-based workflow (CI Options A and D), never claim strict parse coverage: state that a file with a syntax or schema error is skipped with a warning while the rest are audited, that fail-on-no-inputs: true only catches an empty collection, and offer Option B if the distinction matters.stale-action-refs and unpatched advisories./platform-skills:github-actions — workflow and job-graph design, actionlint syntax validation, OIDC, token scoping, failing-workflow debug. Run alongside zizmor, not instead of it./platform-skills:composite-actions — composite action scaffolding, action.yml review, SHA pinning and env isolation inside actions/platform-skills:renovate — keep the SHA pins zizmor demands from going stale/platform-skills:checkov — Terraform, Kubernetes, Helm, and Dockerfile IaC misconfiguration/platform-skills:trivy — container image, filesystem, and repo CVE and secret scanning/platform-skills:supply-chain — Cosign signing, SBOM generation and attestation, SLSA provenance/platform-skills:preflight — production-readiness sweep across mixed file types, including workflows/platform-skills:pr-review — PR-level review: ownership, blast radius, rollback feasibility/platform-skills:commit — conventional commit message for the resulting fix/platform-skills:self-improve — after completing a task here, log errors and learnings with /platform-skills:self-improve lognitinjain999/platform-skills
c2e75b7
Canonical home
since Oct 12, 2026
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.