
Fixture GitHub Actions riproducibili, vulnerabili e corrette, per l'iniezione di workflow agentici (CVE-2026-44246), con copertura del rilevatore misurata e indicazioni di mitigazione.
Fixture riproducibili vulnerabili/corrette per la classe di bug pubblicata come CVE-2026-44246 (nnU-Net, iniezione di workflow agentico, CVSS 7.2, CWE-1427), più l'output misurato di tre rilevatori su di esse.
Esiste perché non c'era nulla con cui testare un rilevatore. Scrivere una regola per questa classe significa scrivere una fixture a mano e sperare che sia fedele; le revisioni pubblicate erano disponibili solo sapendo quale commit guardare, il che si è rivelato la parte interessante.
Un workflow di GitHub Actions consegna testo non attendibile a un agente AI che possiede le credenziali del repository.
Quattro condizioni, tutte necessarie:
issues, issue_comment, pull_request_review —
eventi il cui testo chiunque abbia un account GitHub può scrivere.issues: write, contents: write, e così via.claude-code-action e
codex-action rifiutano un attore di esecuzione senza permesso di scrittura a meno che un input
(allowed_non_write_users, allow-users) non lo abiliti esplicitamente. Senza
quell'input un autore non attendibile non raggiunge mai l'agente, e il workflow è
la configurazione sicura anziché questa.${{ github.event.issue.body }} dentro prompt:) sia recuperato a runtime
(gh issue view) con il token che il job consegna.Non è template injection. Nulla viene valutato come shell; il payload è prosa,
e l'interprete è il modello. Ecco perché il consiglio abituale — metti tra
virgolette le tue variabili, non usare eval — non si applica, e perché la correzione
sotto riguarda ciò che l'agente può raggiungere piuttosto che l'escaping dell'input.
nnU-Net ha rafforzato questo workflow in due commit, e un rilevatore che tratta "prima" e "dopo" come binari sbaglia quello intermedio.
| revisione | commit | data | --allowedTools dell'agente | verdetto |
|---|---|---|---|---|
| vulnerabile | 94300b49e716 | 2026-04-13 | gh issue comment, gh issue edit | scrittura raggiungibile |
| "la correzione" | 4e4770b0b0e6 | 2026-04-24 | solo gh issue comment; l'etichettatura spostata in uno script wrapper | scrittura ancora raggiungibile |
| successiva | 11bd8746fc06 | 2026-04-27 | nessuno dei due; uno step successivo pubblica da un file che l'agente scrive | non raggiungibile |
Il commit che tutti chiamerebbero la correzione — il suo messaggio è "hardened issue
and PR agents" — ha rimosso gh issue edit e instradato le etichette attraverso
.github/scripts/safe-label.sh, ma ha lasciato Bash(gh issue comment:*) con
l'agente. L'agente poteva ancora essere indirizzato a commentare, e il pattern
dell'allowlist gh issue comment:* non è limitato all'issue che ha attivato il
workflow, quindi l'obiettivo era a scelta del modello. Solo il terzo commit l'ha chiuso: l'agente ora
scrive /tmp/issue-comment.md e uno step successivo, non-agentico, lo pubblica con
ISSUE: ${{ github.event.issue.number }} preso dall'evento.
Ne seguono due cose, ed è per questo che questo repository non è solo due file:
"Corretto" è un'affermazione su una revisione, non su una versione. v2.4.1 è citato come
la release corretta, ma il suo .github/workflows/ contiene solo codespell.yml — i
workflow dell'agente non sono affatto in quel tag. Non puoi verificare la correzione dal
tag; devi fissare il commit.
Il blocco dei permessi non cambia mai. issues: write è presente e corretto
in tutte e tre le revisioni, inclusa l'ultima, perché uno step successivo ne ha bisogno. Un
rilevatore che si basa solo su permissions non può separare la revisione 1 dalla
revisione 3. Ciò che le separa è cosa l'agente può chiamare.
Tre rilevatori, eseguiti su entrambe le fixture e su tutte e tre le revisioni reali. Output grezzo completo e versioni degli strumenti in results.md.
| revisione | 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 | — (solo generico) | — |
3-later | LOW write-scope, CRITICAL author-association | ai-action-excessive-tools, ai-action-execution-order | — |
Nessun rilevatore è semplicemente "sbagliato" qui — rispondono a domande diverse:
ai-action-prompt-injection scatta sulla revisione
vulnerabile, dove il corpo dell'issue è interpolato in prompt:, e si ferma una volta
che l'interpolazione è sparita. Corretto. I suoi risultati sulla revisione successiva sono
da leggere prima di agire su di essi — ai-action-excessive-tools segnala
Write, che questo workflow usa di proposito per scrivere il file che uno step
successivo pubblica; e ai-action-execution-order vuole l'agente per ultimo, che è
esattamente ciò che questo design evita, dato che gli step privilegiati vengono dopo
che il turno del modello è terminato.permissions del job. Il suo
risultato ci-agent-missing-author-association è riportato come critical sulla
revisione successiva e il suo stesso README lo descrive come eccessivamente severo anziché
falso: auto-triage è pensato per essere raggiungibile da chiunque.Ogni strumento qui ha un risultato che riporta su una revisione il cui autore aveva già ragionato su quella esatta condizione. Questo è lo stato normale di un'euristica, e il motivo per cui una tabella di copertura è più utile di una colonna pass/fail.
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 segnala un rilevatore mancante come mancante anziché
saltarlo silenziosamente, perché una tabella di copertura che omette tranquillamente uno strumento non
prova nulla al riguardo.
Le fixture sono la forma ridotta, scritte per questo repository e con licenza MIT
come il resto. Sono fedeli al meccanismo anziché copie:
fixtures/vulnerable.yml porta le quattro condizioni e nulla più, e
fixtures/fixed.yml è lo stesso file con le sei modifiche che contano, ciascuna
annotata. Se stai aggiungendo una regola, quei due file sono gli input più piccoli che
deve gestire correttamente.
Due stranezze dell'interfaccia, gestite nello script ma che vale la pena conoscere:
not found ed esce con 3. Non osservato, sembra identico a una scansione pulita.path JSON di agentbound è un basename, quindi una scansione di directory su file con
lo stesso nome è ambigua.