Reproducing zizmor's flag on the Snowflake injection
I ran zizmor 1.29.0 against the exact Snowflake GitHub Actions workflow. A deterministic static rule flagged the injection at High confidence while AI review cleared it.
The public Snowflake incident is a useful test case for one question I keep coming back to: in a mixed workflow with both deterministic static analyzers and AI review, which one actually catches the injection?
I decided to find out empirically instead of arguing from vibes. I pulled the exact vulnerable workflow pattern and ran zizmor 1.29.0 against it in a sandbox.
Result: it flags the injection line. Rule template-injection, description “code injection via template expansion”, High confidence / High severity, pointing at jira_issue.yml:24:29 and naming github.event.issue.title as attacker-controllable input that can expand into a command. It reproduces with a plain zizmor --quiet run; the JSON output carries the same finding.
Here is the boring mechanics of the bug, because that is the part worth understanding and it is checkable by eye:
The workflow interpolates the issue title directly into a shell script:
run: |
TITLE=$(echo '${{ github.event.issue.title }}' | sed 's/"/\\"/g' | sed "s/'/\\'/g")
The sed escaping runs after GitHub’s ${{ }} template expansion, not before. A single quote in the issue title breaks out of echo '...' and reaches the shell. That is the escape-ordering bug: you cannot escape a value that has already been interpolated into an execution context.
Two structural bugs are worth separating because they fail at different layers:
- Escape-ordering. The sanitizer runs after the expansion that made the value dangerous, so it is pure theater. This is structural, not a model failure, and you do not need AI to explain it. A linter rule can and does catch it.
- The protective
if:guard does not match the actual invocation path. A guard that only makes sense for a handler it never fires for is dead weight. Again fully statically checkable.
The part I found notable: this is the same workflow that GH Advanced Security scanned and did not flag, and an AI autofix was associated with the same PR in a related file. So you get a clean A/B in the wild: a deterministic rule with an autofix caught the exact dangerous line, while the AI-assisted layer shipped a fix elsewhere in the same change.
That is not a claim that static analysis replaces code review. It is a claim about layering and about test selection. If you have a static analyzer that flags ${{ }} interpolation into run: with High confidence, that is reproducible evidence a given PR needs a human or an AI read on that specific line. The value of the deterministic layer is that it nominates the exact places where judgement is required.
The reproducibility point is the one I want to keep: every claim here was derived by running a pinned tool version against a known input, not by reading a vendor’s marketing page. That is the standard I would like the rest of the field held to. If a review tool claims it catches script injection, ask for the flag it produces on this workflow. It either names the line or it does not.
[ FAQ ]
What exact finding did zizmor produce on the Snowflake GitHub Actions workflow?
Running zizmor 1.29.0 against the vulnerable workflow produced rule template-injection, described as 'code injection via template expansion,' at High confidence and High severity, pointing at jira_issue.yml:24:29 and naming github.event.issue.title as attacker-controllable input.
What is the escape-ordering bug in the vulnerable workflow?
The workflow's sed escaping runs after GitHub's ${{ }} template expansion, not before. Because the sanitizer runs after the value has already been interpolated into an execution context, a single quote in the issue title can break out of echo '...' and reach the shell.
Did GitHub Advanced Security catch this same injection?
No. The same workflow was scanned by GH Advanced Security and not flagged, while an AI autofix was associated with the same pull request in a different, unrelated file — a clean A/B where the deterministic rule caught the dangerous line and the AI-assisted layer did not.
Were there other structural bugs in the workflow besides the injection itself?
Yes, two: the escape-ordering bug where sanitization runs too late to matter, and a protective if: guard that doesn't actually match the invocation path it's meant to protect. Both are fully statically checkable without needing a model to explain them.
Does this result mean static analysis should replace AI code review?
No. The piece frames it as a layering and test-selection question: a deterministic analyzer flagging ${{ }} interpolation into run: at High confidence is reproducible evidence that a specific line needs a human or AI read, not proof that static tools alone are sufficient.