
Fixtures reproducibles de GitHub Actions vulnerables y corregidas para la inyección de flujos de trabajo agénticos (CVE-2026-44246), con cobertura de detección medida y guía de mitigación.
Fixtures vulnerables/corregidos reproducibles para la clase de bug publicada como CVE-2026-44246 (nnU-Net, inyección de flujo de trabajo agéntico, CVSS 7.2, CWE-1427), más la salida medida de tres detectores contra ellos.
Existe porque no había nada con lo que probar un detector. Escribir una regla para esta clase significa escribir un fixture a mano y esperar que sea fiel; las revisiones publicadas solo estaban disponibles sabiendo qué commit mirar, lo cual resultó ser la parte interesante.
Un flujo de trabajo de GitHub Actions entrega texto no confiable a un agente de IA que tiene las credenciales del repositorio.
Cuatro condiciones, todas necesarias:
issues, issue_comment, pull_request_review —
eventos cuyo texto cualquiera con una cuenta de GitHub puede redactar.issues: write, contents: write, y así sucesivamente.claude-code-action y
codex-action rechazan a un actor de ejecución sin permiso de escritura a menos que una entrada
(allowed_non_write_users, allow-users) lo habilite explícitamente. Sin
esa entrada, un autor no confiable nunca llega al agente, y el flujo de trabajo es
la configuración segura en lugar de esta.${{ github.event.issue.body }} dentro de prompt:) o recuperado en tiempo de ejecución
(gh issue view) con el token que el job entrega.No es inyección de plantillas. Nada se evalúa como shell; el payload es
prosa, y el intérprete es el modelo. Por eso el consejo habitual — cita
tus variables, no uses eval — no aplica, y por eso la corrección a continuación trata sobre
lo que el agente puede alcanzar en lugar de sobre escapar la entrada.
nnU-Net endureció este flujo de trabajo en dos commits, y un detector que trata "antes" y "después" como binario se equivoca con el del medio.
| revisión | commit | fecha | --allowedTools del agente | veredicto |
|---|---|---|---|---|
| vulnerable | 94300b49e716 | 2026-04-13 | gh issue comment, gh issue edit | escritura alcanzable |
| "la corrección" | 4e4770b0b0e6 | 2026-04-24 | solo gh issue comment; el etiquetado se movió a un script envoltorio | escritura aún alcanzable |
| posterior | 11bd8746fc06 | 2026-04-27 | ninguno; un paso posterior publica desde un archivo que el agente escribe | no alcanzable |
El commit que todo el mundo llamaría la corrección — su mensaje es "hardened issue
and PR agents" — eliminó gh issue edit y enrutó las etiquetas a través de
.github/scripts/safe-label.sh, pero dejó Bash(gh issue comment:*) con el
agente. El agente aún podía ser dirigido a comentar, y el patrón de allowlist
gh issue comment:* no está limitado al issue que dispara, por lo que el
objetivo era elección del modelo. Solo el tercer commit lo cerró: el agente ahora
escribe /tmp/issue-comment.md y un paso posterior, no-agente, lo publica con
ISSUE: ${{ github.event.issue.number }} tomado del evento.
De esto se siguen dos cosas, y son la razón por la que este repositorio no son solo dos archivos:
"Corregido" es una afirmación sobre una revisión, no sobre una versión. v2.4.1 se cita como la
versión corregida, pero su .github/workflows/ contiene solo codespell.yml — los
flujos de trabajo del agente no están en esa etiqueta en absoluto. No puedes verificar la corrección desde
la etiqueta; tienes que fijar el commit.
El bloque de permisos nunca cambia. issues: write está presente y es correcto
en las tres revisiones, incluida la última, porque un paso posterior lo necesita. Un
detector que se basa solo en permissions no puede separar la revisión 1 de la
revisión 3. Lo que las separa es lo que el agente puede invocar.
Tres detectores, ejecutados sobre ambos fixtures y las tres revisiones reales. Salida cruda completa y versiones de herramientas en results.md.
| revisión | 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 genérico) | — |
3-later | LOW write-scope, CRITICAL author-association | ai-action-excessive-tools, ai-action-execution-order | — |
Ningún detector está simplemente "equivocado" aquí — responden a preguntas diferentes:
ai-action-prompt-injection se dispara en la revisión vulnerable,
donde el cuerpo del issue se interpola en prompt:, y se detiene una vez
que la interpolación desaparece. Correcto. Sus hallazgos en la revisión posterior vale
la pena leerlos antes de actuar sobre ellos — ai-action-excessive-tools marca
Write, que este flujo de trabajo usa a propósito para escribir el archivo que un paso
posterior publica; y ai-action-execution-order quiere al agente al final, que es
exactamente lo que este diseño evita, ya que los pasos privilegiados vienen después de que
termina el turno del modelo.permissions del job. Su
hallazgo ci-agent-missing-author-association se reporta como critical en la
revisión posterior y su propio README describe eso como demasiado severo en lugar de
falso: se supone que auto-triage es alcanzable por cualquiera.Cada herramienta aquí tiene un hallazgo que reporta en una revisión cuyo autor ya había razonado sobre esa condición exacta. Ese es el estado normal de una heurística, y la razón por la que una tabla de cobertura es más útil que una columna de aprobado/fallido.
git clone https://github.com/sushant-me/agentic-workflow-injection
cd agentic-workflow-injection
bash scripts/fetch-revisions.sh # las tres revisiones reales, fijadas por SHA
bash scripts/benchmark.sh # ejecuta cada detector que esté instalado
benchmark.sh reporta un detector que falta como faltante en lugar de
omitirlo silenciosamente, porque una tabla de cobertura que omite tranquilamente una herramienta no
prueba nada sobre ella.
Los fixtures son la forma reducida, escritos para este repositorio y con licencia MIT
como el resto. Son fieles al mecanismo en lugar de copias:
fixtures/vulnerable.yml lleva las cuatro condiciones y nada más, y
fixtures/fixed.yml es el mismo archivo con los seis cambios que importan, cada uno
anotado. Si estás añadiendo una regla, esos dos archivos son las entradas más pequeñas que
tiene que acertar.
Dos peculiaridades de interfaz, manejadas en el script pero que vale la pena conocer:
not found y sale con 3. Sin vigilancia, eso se ve idéntico a un escaneo
limpio.path JSON de agentbound es un basename, por lo que un escaneo de directorio sobre archivos con
el mismo nombre es ambiguo.La detección es la mitad fácil. docs/mitigations.md cubre la otra mitad: cómo acotar la autoridad de un agente, con las capas clasificadas por una pregunta — ¿este control depende de que el modelo elija cumplir?
Esa clasificación es el punto entero. Una instrucción en un prompt es entrada, no una cláusula de guarda; un patrón de herramienta que nombra su objetivo, o un token que el agente nunca posee, es una cota. La guía va descendiendo desde "el modelo no puede hacer lo incorrecto" hasta "se le pidió al modelo que no lo hiciera", con una lista de verificación y la evolución de nnU-Net como ejemplo desarrollado.
fixtures/fixed.yml etiqueta cada uno de sus seis cambios con la capa que implementa
y con si es portante — dos de ellos no lo son, y están etiquetados
así para que un lector no sobreestime el archivo.
Obtenidos en tiempo de ejecución por SHA de commit, nunca vendorizados:
| archivo | repositorio | commit | ruta |
|---|---|---|---|
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 se redistribuye aquí ninguna copia de la configuración de nnU-Net; vuelve a ejecutar la obtención y compara los bytes tú mismo contra los blobs anteriores.
Este es un artefacto de ingeniería de detección. No contiene ningún exploit, y nada
aquí fue probado contra un flujo de trabajo en vivo — las revisiones vulnerables son archivos
estáticos, ya públicos en el historial de nnU-Net y referenciados por un aviso
publicado. Los fixtures están marcados do not deploy porque existen para ser
detectados, no copiados.
Si mantienes un detector para esta clase y quieres una fila en la tabla, el
script de benchmark es la interfaz: añade una sección que ejecute tu herramienta sobre
targets e imprima sus hallazgos, y los fixtures y las revisiones fijadas hacen el
resto.
MIT.