Skip to content

Back to the catalog

Bringing full YAML anchor support to zizmor
blog.trailofbits.comarticle

Bringing full YAML anchor support to zizmor

Trail of Bits worked with the zizmor maintainers to add full YAML anchor support to zizmor, the GitHub Actions static analyzer that catches misconfigurations like the one exploited in the March 2026 aquasecurity/trivy-action pull_request_target attack that led to a LiteLLM PyPI backdoor. They built a corpus of 41,253 workflows from 6,612 high-star repos, found zizmor crashed on 45 of them, and fixed four anchor-handling bugs plus unrelated deserialization and expression-evaluator issues, filing 20 issues and merging 15 PRs upstream.

TL;DR Trail of Bits hardened zizmor's YAML anchor parsing using a 41k-workflow corpus, fixing crash/misparse bugs that let malicious or malformed GitHub Actions workflows slip past the analyzer.
Document

What it is

An engineering report from Trail of Bits (funded by the Sovereign Tech Agency) on hardening zizmor, the static analyzer that catches risky GitHub Actions configurations like the pull_request_target misconfiguration that led to the March 2026 trivy-action → LiteLLM PyPI supply-chain compromise. GitHub Actions added YAML anchor support in September 2025; zizmor had only best-effort anchor handling until this work brought it to full coverage.

How it works — the fixes

Four anchor-handling bugs, each with its upstream PR:

  • Aliases in sequences were flattened — an alias inside a step list was spread rather than treated as one element, causing crashes or findings pointing at the wrong file location. (#1557)
  • Anchor prefixes leaked into values — in flow sequences, &name wasn't stripped, so a lookup returned &name v instead of v, failing any step consuming the node. (#1562)
  • Duplicate anchors crashed — the YAML spec allows redefining an anchor (last wins); zizmor assumed uniqueness and panicked. (#1575)
  • Template-injection audit crashed on aliased run: values — the audit didn't expect a YAML alias used as a scalar run: value. (#1732)

Regression coverage was added via integration tests (#1682) and doc updates (#1788).

Non-anchor issues the corpus surfaced:

  • Deserialization edge cases that made zizmor reject entire valid workflows: if: 0 (int where string expected), timeout-minutes: 0.5 (float where int expected), secrets: inherit (string where mapping expected). (#1670, #1672, #1674)
  • Expression evaluator misalignment, fixed by validating against GitHub's own Known Answer Tests. (#1694)
  • Upstream tree-sitter-yaml crashes filed as tree-sitter-yaml#39 and #43.

Affected

Anchor use is rare — only 43 of 41,253 corpus workflows (~0.1%) — but concentrated in foundational projects (Bitcoin Core reuses steps across jobs; Home Assistant pins an action SHA via an anchor). If your workflows use YAML anchors, integer/float/string type coercions, or secrets: inherit, an older zizmor may have crashed or emitted wrong-location findings on them.

What to do

Update zizmor to a version including the merged PRs above so anchor-using and edge-case workflows analyze cleanly rather than crashing or being silently skipped. If you maintain a static analysis tool, the stated methodology is directly reusable: download real inputs, run the tool, triage the failures — here, the 10,000 most-starred repos created 2022–2025 via BigQuery, filtered to the 6,612 using Actions.

Caveats

The crash rate was low (45 of 41,253) and the page states not even the YAML 1.2 test suite covers every spec-permitted edge case, so corpus-driven testing catches what real inputs exercise, not the full spec.

Organisations named here

  • pull_request_target
  • Sovereign Tech Agency
  • woodruffw

Technologies named here

  • aquasecurity/trivy-action GitHub Action
  • zizmor
  • tree-sitter-yaml
  • LiteLLM on PyPI
  • BigQuery GitHub dataset
  • GitHub Actions
  • Trivy
  • aquasecurity/trivy-action
  • YAML

Techniques named here

  • pull_request_target misconfiguration
  • Template injection via aliased run values