We hardened zizmor's GitHub Actions static analyzer

woodruffw2 pts0 comments

We hardened zizmor's GitHub Actions static analyzer - The Trail of Bits BlogPage content

In March 2026, attackers exploited a pull_request_target misconfiguration in<br>the aquasecurity/trivy-action GitHub Action to exfiltrate organization and<br>repository secrets, then used those credentials to backdoor LiteLLM on PyPI (see<br>Trivy&rsquo;s post-mortem for the full timeline). zizmor is a static analyzer<br>that GitHub Actions users run to catch exactly these misconfigurations before they ship.<br>When GitHub Actions added support for YAML anchors in September 2025, a small but<br>high-value slice of the ecosystem started writing workflows that zizmor could only<br>analyze on a best-effort basis.<br>Over the past three months, Trail of Bits collaborated with the zizmor maintainers<br>to bring zizmor&rsquo;s anchor support up to full coverage. First, we fixed parsing bugs<br>that caused crashes, produced wrong-location findings, and silently mishandled aliased values.<br>Second, we surfaced deserialization edge cases that broke zizmor on otherwise valid workflows.<br>Finally, we helped align zizmor’s expression evaluator with GitHub’s own<br>Known Answer Tests. We validated all of this against a new corpus of 41,253 workflows<br>from 6,612 high-value open-source repositories. The result: 20 filed issues, 15 merged pull<br>requests.<br>Building the test corpus<br>To understand how anchors are used in CI today and to stress-test zizmor<br>against the full variety of YAML it encounters in the wild, we built a corpus<br>of real workflows. We used BigQuery&rsquo;s GitHub dataset to identify the 10,000<br>most-starred repositories created between 2022 and 2025, filtered to the 6,612<br>that use GitHub Actions, and downloaded every workflow file. That gave us<br>41,253 YAML files.<br>Figure 1: Building a testing corpus<br>When we ran zizmor against the corpus, it crashed on 45 of the 41,253<br>workflows. That&rsquo;s a low rate, but each crash means a bug in zizmor.<br>How anchors are used in the wild<br>zizmor&rsquo;s anchor support was deliberately limited, and for good reason.<br>YAML anchors make workflows non-local: an alias defined in one place changes<br>behavior elsewhere in the file. This complicated zizmor&rsquo;s parsing model, and<br>adoption was rare enough that the zizmor maintainers reasonably discouraged<br>anchor use. In our corpus, only 43 of the 41,253 workflows use YAML anchors (roughly 0.1%), but those 43 include some of the most foundational projects in open source:<br>Bitcoin Core<br>PHP<br>OpenSSL<br>However, anchors are a supported feature, and their use will likely grow over time.<br>We found two common patterns. The first is reusing steps across jobs , as<br>Bitcoin Core&rsquo;s CI does:<br>jobs:<br>runners:<br>steps:<br>- &ANNOTATION_PR_NUMBER<br>name: Annotate with pull request number<br>run: |<br>if [ "${{ github.event_name }}" = "pull_request" ]; then<br>echo "::notice ..."<br>fi

test-each-commit:<br>steps:<br>- *ANNOTATION_PR_NUMBER<br>- uses: actions/checkout@v6Figure 2: Reuse step definitionThe second pattern is pinning action versions once . For instance,<br>Home Assistant&rsquo;s CI defines the action reference (with its<br>SHA hash) using an anchor, then reuses it wherever the same action appears:<br>jobs:<br>lint:<br>steps:<br>- uses: &actions-setup-python actions/setup-python@a309ff8b42...<br># later in the same workflow:<br>- uses: *actions-setup-pythonFigure 3: Reuse action definitionFour anchor handling bugs found and fixed<br>When we started, four anchor patterns from these workflows broke zizmor.<br>Aliases in sequences were incorrectly flattened. When a YAML alias appeared<br>inside a sequence (like a list of steps), zizmor&rsquo;s internal path representation<br>spread the alias contents rather than treating it as a single element. This<br>caused zizmor to crash or produce findings pointing at the wrong location<br>in the file. (Fixed in #1557)<br>Anchor prefixes leaked into values.

foo: [&name v, *x]Figure 4: Anchor prefix leakIn YAML flow sequences, anchor prefixes like &name weren&rsquo;t stripped from<br>resolved values. Given the snippet in Figure 4, looking up the first element of<br>foo would return &name v instead of v, causing any step that consumed the<br>node value to fail. (Fixed in #1562)<br>Duplicate anchors caused a crash. The YAML spec allows redefining an anchor<br>name (the last definition wins). zizmor&rsquo;s YAML layer assumed anchor names were<br>unique and panicked on duplicates. (Fixed in #1575)<br>The template-injection audit crashed on aliased run values. When a<br>YAML alias was used as a scalar run: value, the audit didn&rsquo;t expect the<br>indirection and failed. (Fixed in #1732)<br>To prevent future regressions, we also added integration tests covering anchor<br>patterns found in real workflows (#1682) and updated the anchor documentation<br>(#1788).<br>What else the corpus surfaced<br>Running zizmor against the full test corpus also surfaced bugs that had nothing to<br>do with anchors.<br>Deserialization edge cases. GitHub Actions accepts YAML constructs that<br>zizmor&rsquo;s workflow model didn&rsquo;t anticipate: if: 0 (an integer where a string<br>is expected),...

zizmor rsquo anchor yaml github actions

Related Articles