
La premisa: tu app tiene una frontera humano-IA y tu crees que esta sellada
Un desarrollador llamado Daniel Nwaneri construyo GeoMart para el WebMCP Challenge de OpenAI. La idea: un storefront donde un humano llena un brief en vivo, y un agente de IA usa herramientas WebMCP para leerlo, evaluar equipos contra restricciones fisicas reales, y redactar un presupuesto que el humano tiene que aprobar. La regla del hackathon era clara: cada accion central tiene que ser alcanzable solo a traves de una herramienta WebMCP, sin ruta REST que la duplique.
Daniel penso que lo habia cubierto. Entonces le entrego el codigo fuente completo a Google Antigravity — otra IA— y le dijo: intenta romperlo. En diez minutos encontro tres problemas.
Falla 1: innerHTML sin escape
El texto que el agente suministraba iba directo al DOM via innerHTML sin escapar. XSS clasico, de manual. Daniel lo arreglo: HTML-escapando cada string que viene del agente antes de tocar el DOM.
Falla 2: el endpoint POST no tenia proteccion ninguna
El endpoint POST /api/quotes tenia cero proteccion. Un script cualquiera podia llamarlo directamente y enviar un presupuesto sin ningun humano involucrado. Toda la frontera de aprobacion humana era decorativa. Daniel agrego un chequeo isTrusted en el boton de submit: solo un click de hardware real pasaba.
Falla 3: el bypass que lo dejo helado
Despues de arreglar las dos primeras, pidio un retest. Antigravity replico el ataque anterior: 403. Bien. Pero entonces hizo algo que Daniel no habia pensado probar: seteo el header Origin en una llamada fetch() de Node.js para que coinciciera con el dominio del sitio. Obtuvo 201. El presupuesto se envio. Sin navegador, sin humano, sin nada.
El punto es brutal: Origin no es un secreto. Su repo es open source, exigido por las reglas del hackathon. Cualquiera que lea worker.ts ve que valor espera el chequeo. Un navegador real no puede forzar ese header, pero fetch() de Node no es un navegador. Su fix solo verificaba copiaste el nombre del dominio, no eres realmente un navegador.
Como dice Daniel: si tu chequeo de seguridad sigue pasando despues de que le explicaste al atacante como funciona, nunca fue un chequeo real. Era un filtro para gente que no habia leido tu codigo todavia.
La solucion que si funciono
Considere cookies de sesion y se auto-despidio en dos minutos: un script con acceso HTTP completo puede fetchar la pagina, robar la cookie, y replay. Mismo hueco, diferente forma.
Lo que funciono fue Cloudflare Turnstile, verificado server-side contra un secreto que solo vive en un Worker secret, nunca commiteado, nunca en el bundle del cliente. Origin correcto sin token: 403. Origin correcto con token falso: 403. Esa es la diferencia entre un valor que puedes leer y un valor que no puedes.
El bonus: la tabla nunca existio
Mientras perseguia el bypass de Origin, el retest descubrio que la tabla de la base de datos para guardar los presupuestos nunca se creo. Escribio la migracion y nunca la corrio. El endpoint entero estaba apuntando al vacio.
Mi opinion
Como estudiante de ciberseguridad y alguien que trabaja con agentes de IA todos los dias, esta historia me calo hondo. La cantidad de veces que he visto chequeos de seguridad que cualquier atacante con acceso al codigo puede bypassar es alarmante. Y el punto no es que Daniel fuera malo programando —no lo era—. El punto es que el testing adversarial con IA no es opcional si estas claimando una frontera de aprobacion humana.
Si estas construyendo algo que interactua con agentes de IA —y cada vez mas de nosotros lo estamos— pidele a un agente con acceso al codigo completo que intente derrotar tus propias claims antes de que un atacante lo haga por ti. Diez minutos de Antigravity encontraron lo que horas de testing manual no vieron.
Y la leccion de fondo aplica fuera del mundo IA tambien: tu modelo de amenazas tiene que asumir que el atacante leyo tu codigo. Si tu seguridad se cae cuando alguien lee el repo, no era seguridad. Era oscuridad.
Fuente de inspiración: I Found 3 Security Vulnerabilities in My Own AI Agent’s Tool Access
