Agentic workflow injection

Agentic workflow injection: fixtures and a coverage comparison

Reproducible vulnerable/fixed fixtures for the class of bug published as CVE-2026-44246 (nnU-Net, agentic workflow injection, CVSS 7.2, CWE-1427), plus the measured output of three detectors against them.

It exists because there was nothing to test a detector against. Writing a rule for this class means writing a fixture by hand and hoping it is faithful; the published revisions were only available by knowing which commit to look at, which turned out to be the interesting part.

The class

A GitHub Actions workflow hands untrusted text to an AI agent that has the repository’s credentials.

Four conditions, all necessary:

  1. An untrusted trigger. issues, issue_comment, pull_request_review — events whose text anyone with a GitHub account can author.
  2. A write scope on the job. issues: write, contents: write, and so on.
  3. An opt-out of the action’s own actor check. claude-code-action and codex-action refuse a run actor without write permission unless an input (allowed_non_write_users, allow-users) explicitly opts them in. Without that input an untrusted author never reaches the agent, and the workflow is the safe configuration rather than this one.
  4. The text reaching the agent. Either interpolated into the workflow ($ inside prompt:) or fetched at run time (gh issue view) with the token the job hands over.

It is not template injection. Nothing is evaluated as shell; the payload is prose, and the interpreter is the model. That is why the usual advice — quote your variables, do not eval — does not apply, and why the fix below is about what the agent can reach rather than about escaping input.

The part worth knowing: the fix commit is not the fix

nnU-Net hardened this workflow in two commits, and a detector that treats “before” and “after” as a binary gets the middle one wrong.

revision commit date agent’s --allowedTools verdict
vulnerable 94300b49e716 2026-04-13 gh issue comment, gh issue edit reachable write
“the fix” 4e4770b0b0e6 2026-04-24 gh issue comment only; labelling moved to a wrapper script still reachable write
later 11bd8746fc06 2026-04-27 neither; a later step posts from a file the agent writes not reachable

The commit that everyone would call the fix — its message is “hardened issue and PR agents” — removed gh issue edit and routed labels through .github/scripts/safe-label.sh, but left Bash(gh issue comment:*) with the agent. The agent could still be steered into commenting, and the allowlist pattern gh issue comment:* is not scoped to the triggering issue, so the target was the model’s to choose. Only the third commit closed it: the agent now writes /tmp/issue-comment.md and a later, non-agent step posts it with ISSUE: $ taken from the event.

Two things follow, and they are why this repository is not just two files:

“Fixed” is a claim about a revision, not a version. v2.4.1 is cited as the fixed release, but its .github/workflows/ contains only codespell.yml — the agent workflows are not in that tag at all. You cannot verify the fix from the tag; you have to pin the commit.

The permissions block never changes. issues: write is present and correct in all three revisions, including the last one, because a later step needs it. A detector that keys on permissions alone cannot separate revision 1 from revision 3. What separates them is what the agent may call.

Measured coverage

Three detectors, run over both fixtures and all three real revisions. Full raw output and tool versions in results.md.

revision agentbound 0.1.3 sisakulint v0.3.7 zizmor 1.30.1
1-vulnerable HIGH write-scope, HIGH untrusted-content ai-action-prompt-injection
2-fix-commit HIGH write-scope, CRITICAL author-association — (generic only)
3-later LOW write-scope, CRITICAL author-association ai-action-excessive-tools, ai-action-execution-order

No detector is simply “wrong” here — they answer different questions:

Every tool here has a finding it reports on a revision whose author had already reasoned about that exact condition. That is the normal state of a heuristic, and the reason a coverage table is more useful than a pass/fail column.

Using it

git clone https://github.com/sushant-me/agentic-workflow-injection
cd agentic-workflow-injection

bash scripts/fetch-revisions.sh   # the three real revisions, pinned by SHA
bash scripts/benchmark.sh         # runs every detector that is installed

benchmark.sh reports a detector that is missing as missing rather than skipping it silently, because a coverage table that quietly omits a tool proves nothing about it.

The fixtures are the reduced shape, written for this repository and MIT-licensed like the rest of it. They are mechanism-faithful rather than copies: fixtures/vulnerable.yml carries the four conditions and nothing else, and fixtures/fixed.yml is the same file with the six changes that matter, each annotated. If you are adding a rule, those two files are the smallest inputs it has to get right.

Two interface quirks, handled in the script but worth knowing:

Fixing it

Detection is the easy half. docs/mitigations.md covers the other half: how to bound an agent’s authority, with the layers ranked by one question — does this control depend on the model choosing to comply?

That ranking is the whole point. An instruction in a prompt is input, not a guard clause; a tool pattern that names its target, or a token the agent never holds, is a bound. The guide works down from “the model cannot do the wrong thing” to “the model was asked not to”, with a checklist and the nnU-Net evolution as the worked example.

fixtures/fixed.yml tags each of its six changes with the layer it implements and with whether it is load-bearing — two of them are not, and are labelled that way so a reader does not overestimate the file.

Provenance

Fetched at run time by commit SHA, never vendored:

file repository commit path
real/1-vulnerable.yml MIC-DKFZ/nnUNet 94300b49e716 .github/workflows/issue-triage.yml
real/2-fix-commit.yml MIC-DKFZ/nnUNet 4e4770b0b0e6 .github/workflows/issue-agent.yml
real/3-later.yml MIC-DKFZ/nnUNet 11bd8746fc06 .github/workflows/issue-agent.yml

No copy of nnU-Net’s configuration is redistributed here; re-run the fetch and compare the bytes yourself against the blobs above.

Scope

This is a detection-engineering artifact. It contains no exploit, and nothing here was tested against a live workflow — the vulnerable revisions are static files, already public in nnU-Net’s history and referenced by a published advisory. The fixtures are marked do not deploy because they exist to be detected, not copied.

If you maintain a detector for this class and want a row in the table, the benchmark script is the interface: add a section that runs your tool over targets and prints its findings, and the fixtures and pinned revisions do the rest.

MIT.