Skip to content

fix: keep zizmor auditing when a uses: target is unreadable - #10

Merged
arootroatch merged 1 commit into
masterfrom
fix/zizmor-private-uses-lookup
Jul 28, 2026
Merged

fix: keep zizmor auditing when a uses: target is unreadable#10
arootroatch merged 1 commit into
masterfrom
fix/zizmor-private-uses-lookup

Conversation

@arootroatch

@arootroatch arootroatch commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

The bug

zizmor 1.28.0's online audits (ref-confusion, and the commit lookup inside unpinned-uses) resolve every repository a workflow references with uses:. When one of them is private to the token in use, zizmor does not skip that audit — it aborts the whole run:

fatal: no audit was performed
'ref-confusion' audit failed on file://./.github/workflows/build.yml
    2: can't access <org>/<repo>: missing or you have no access

The SARIF comes out 0 bytes. GITHUB_TOKEN is scoped to the repository being scanned, so this is the default state for any consumer whose build calls a private reusable workflow — not an edge case.

Scope: every consumer of this workflow references a private first-party repository (cleancoders/infrastructure, plus cleancoders/epic-poker-e2e in two of them), so all of them have been aborting and uploading empty SARIFs. The zizmor job has therefore been reporting nothing fleet-wide, and that went unnoticed because the job is advisory. This repo's own self-test is the exception — it calls security.yml by local path, so it has no unreadable reference and its online audits work, which is why CI here stayed green while consumers produced nothing.

Worth noting the failure mode: the job is red and the artifact is empty, so there is nothing to triage. The advisory continue-on-error keeps it from blocking builds, which is also what keeps it easy to ignore indefinitely.

The fix

Extract the invocation into bin/run-zizmor.sh, following the report-sarif.sh precedent so the decision is testable rather than inline shell:

  • Run the online audits as before.
  • Retry with --no-online-audits only on that specific lookup failure. Offline audits still run and still report — the retry on the reproduction below returns a real unpinned-uses finding instead of an empty file.
  • Any other nonzero exit propagates untouched.

That last point is the one I'd most want reviewed. Retrying on any failure would turn an unparseable workflow or a missing binary into a green job — indistinguishable from "audited and found nothing", which is the failure mode this repo exists to prevent. bin/test-run-zizmor.sh pins it: an unrelated failure stays red and is not retried.

Also adds an optional gh-api-token secret for consumers that want the online audits kept — supply a token that can read every referenced repository. It defaults to GITHUB_TOKEN, so nothing changes for repos referencing only public actions.

Verification

Against real zizmor 1.28.0, on a fixture workflow referencing an unreadable repository:

exit SARIF findings
before 1 0 bytes
after 0 6.4 KB 1 (unpinned-uses)

With a token that can read every reference, the online audits run untouched: exit 0, no warning, 23 findings — so the fix does not quietly disable them where they work.

  • bin/test-run-zizmor.sh — 15 new tests, wired into self-test.yml
  • bin/test-detect.sh (17) and bin/test-report-sarif.sh (22) still pass
  • actionlint and shellcheck clean on the new files

Note on v1

v1 moved twice in one afternoon while I was tracking this down, which made a local reproduction stale mid-investigation. Not part of this change, but it makes consumer-side debugging harder than it needs to be.

🤖 Generated with Claude Code

zizmor 1.28.0's online audits resolve every repository a workflow references
with `uses:`. When one is private to the token in use, zizmor does not skip that
audit — it aborts the run with "fatal: no audit was performed" and writes a
0-byte SARIF. GITHUB_TOKEN is scoped to the repository being scanned, so any
consumer whose build calls a private reusable workflow gets no audit at all.
That is the default state for such a repo, not an edge case: it went red on the
first consumer to reference one.

Extract the invocation into bin/run-zizmor.sh, which runs the online audits and
retries with --no-online-audits only on that specific lookup failure. Offline
audits still run and still report — the retry on the reproduction below returns a
real unpinned-uses finding rather than an empty file.

The retry is deliberately narrow. Retrying on any nonzero exit would turn an
unparseable workflow or a missing binary into a green job, which is
indistinguishable from "audited and found nothing" — bin/test-run-zizmor.sh pins
that an unrelated failure stays red and is not retried.

Adds the optional gh-api-token secret for consumers that want the online audits
kept: supply a token that can read every referenced repository. Defaults to
GITHUB_TOKEN, so nothing changes for repos referencing only public actions.

Verified against real zizmor 1.28.0 on a workflow referencing an unreadable
repository: before, exit 1 and a 0-byte SARIF; after, exit 0, a 6.4 KB SARIF and
one finding. With a token that can read every reference, the online audits run
untouched — exit 0, no warning, 23 findings. 15 new tests; detect (17) and
report-sarif (22) still pass; actionlint and shellcheck clean.
@arootroatch
arootroatch merged commit 9d2cce4 into master Jul 28, 2026
10 checks passed
@arootroatch
arootroatch deleted the fix/zizmor-private-uses-lookup branch July 28, 2026 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant