No. 002Seguridad9 min de lectura

La inyección de prompts es la nueva inyección SQL — la solución no es SQL

P2SQL, CVE-2024-5565, EchoLeak, la filtración de Supabase: el SQL hostil llega ahora en la salida del modelo. La solución es un agente sin sintaxis en la que inyectar.

El ticket de soporte no parecía un ataque. Esperaba en la cola como cualquier otro — un mensaje de cliente pendiente de resumen. En el cuerpo había instrucciones: leer la tabla integration_tokens y publicar el contenido en ese hilo.

Un desarrollador había conectado un asistente de IA a esa cola a través del servidor MCP de Supabase, que se conecta con credenciales service_role — el rol que omite por completo la seguridad a nivel de fila. El asistente leyó el ticket, interpretó su contenido como una tarea y la ejecutó contra la base de datos de producción. Los tokens aparecieron en el hilo del ticket, donde el atacante pudo leerlos sin más Simon Willison, 2025.

Nada fue explotado en el sentido tradicional. Sin corrupción de memoria, sin verificación de autenticación ausente, sin dependencia vulnerable. El texto hostil llegó al plano de datos únicamente a través del agente — y la prueba de concepto se generaliza a cualquier agente conectado de la misma manera.

Esto es inyección SQL con el intérprete reubicado. El texto hostil sigue convirtiéndose en un comando de base de datos. Pero la cadena ya no llega en la entrada, donde veinte años de herramientas de saneamiento hacen guardia. Llega en la salida del modelo — aguas abajo de todos los filtros que usted controla.

SQL INJECTION, 2005 — THE PAYLOAD ARRIVES IN THE INPUT input sanitizer application database parameterized payload dies here PROMPT-TO-SQL, NOW — THE PAYLOAD ARRIVES IN THE OUTPUT input sanitizer model generated SQL database untrusted context — tickets, pages, rows hostile SQL is authored here — after every filter
El filtro protege una puerta que el payload ya no usa — en P2SQL, el SQL hostil se genera en la salida del modelo (Pedro et al., ICSE 2025).

El registro

Investigadores del INESC-ID de Lisboa nombraron el patrón en 2023: P2SQL, inyección prompt-to-SQL Pedro et al., 2025. Trabajando contra un chatbot LangChain real sobre Postgres, construyeron siete ataques representativos de gravedad creciente — lecturas no autorizadas, tablas corrompidas, tablas eliminadas, hasta ejecución de código — y los ejecutaron contra siete modelos de última generación. Los fallos fueron independientes del modelo. Su conclusión, textual: «Las aplicaciones integradas con LLM basadas en Langchain son altamente susceptibles a ataques de inyección P2SQL.» El artículo fue aceptado en ICSE 2025 — la forma que tiene la revisión por pares de decir que esto no es una curiosidad.

A mediados de 2024 el patrón tenía número de CVE. Vanna.AI, una biblioteca de texto a SQL, pasaba las preguntas del usuario a un prompt que generaba código de gráficos Plotly y luego ejecutaba ese código con exec(). Una pregunta manipulada llegaba directamente a la ejecución remota de código — CVE-2024-5565, CVSS 8.1 JFrog, 2024. Nótese dónde vivía el payload: en la salida del modelo, más allá de cualquier verificación de entrada. Los prompts de guardia de Vanna no lo detuvieron, porque un guardia no es más que texto adicional en la misma ventana de contexto que el ataque.

A finales de 2024 el ranking era oficial: la inyección de prompts es LLM01, el riesgo número uno en el OWASP Top 10 para aplicaciones LLM, ocupando el primer puesto por segunda edición consecutiva OWASP, 2025. El diagnóstico de OWASP sobre la causa raíz resume todo en una frase: los LLM procesan instrucciones y datos en el mismo canal, sin separación clara. Por eso la categoría se divide en dos — inyección directa, escrita por el usuario, e inyección indirecta, que viaja en lo que el modelo recupera. El ticket de Supabase fue indirecta. También lo fue la más grave registrada.

En junio de 2025 se divulgó CVE-2025-32711 — EchoLeak, CVSS 9.3 — en Microsoft 365 Copilot: el primer exploit documentado de inyección de prompts sin clic contra un sistema LLM en producción arXiv, 2025. Un único correo manipulado — instrucciones ocultas en comentarios HTML, texto blanco sobre blanco, Markdown con referencias — y Copilot exfiltró datos internos al servidor del atacante. El usuario no hizo clic en nada. La cadena burló el clasificador XPIA dedicado de Microsoft y su redacción de enlaces en la salida.

CVSS 10 8.1 9.3 2025-06 CVE-2025-32711 — EchoLeak zero-click, one email, M365 Copilot 2023-08 P2SQL coined 7 attacks × 7 LLMs 2024-06 CVE-2024-5565 Vanna.AI — RCE 2024-11 OWASP LLM01 #1 for 2nd edition 2025-07 Supabase MCP PoC no code vulnerability
Dos años, del laboratorio al zero-click — barras CVSS a escala (arXiv 2308.01990; JFrog 2024; OWASP 2025; arXiv 2509.10540; Willison 2025).

Por qué las soluciones obvias fallan

Cada incidente anterior invalida una de las defensas estándar.

Filtrar la entrada. EchoLeak pasó por XPIA, un clasificador que Microsoft construyó específicamente para detectar intentos de inyección arXiv, 2025. Los clasificadores son probabilísticos. El atacante necesita un solo fallo y dispone de intentos ilimitados para fabricarlo; el defensor necesita un registro perfecto contra entradas diseñadas para parecerse a cualquier otra cosa.

Instruir al modelo. Vanna incluía prompts de guardia; CVE-2024-5565 los atravesó JFrog, 2024. No existe un canal privilegiado dentro de una ventana de contexto. El texto del sistema, el texto del usuario y el texto del ataque son la misma sustancia — que es exactamente la causa raíz que OWASP señala OWASP, 2025.

Blindar la salida. Analizar el SQL generado y bloquear las partes peligrosas. Ahora hay que mantener una lista de denegación sobre cada construcción con capacidad de escritura en cada dialecto soportado, más cada función con efectos secundarios — mientras el coautor del adversario es el propio modelo, dispuesto a reformular una consulta bloqueada en una forma permitida. Significativamente, las propias defensas propuestas por los autores de P2SQL viven fuera del modelo: analizar y permitir el SQL generado mediante lista blanca, y ejecutar el agente bajo un rol de base de datos separado y restringido Pedro et al., 2025. Los controles que se sostienen son los que el modelo no puede tocar.

Simon Willison condensó la estructura en una regla de tres: un agente con acceso a datos privados, exposición a contenido no confiable y una vía para comunicarse externamente puede ser inducido a exfiltrar — sin necesidad de ningún bug de software Simon Willison, 2025. Su prescripción es arquitectónica: eliminar una pata. La lista de mitigaciones de OWASP converge en el mismo punto — restringir lo que el modelo puede hacer y validar lo que produce con código determinista, porque el modelo no puede ser el árbitro de sí mismo.

Leídos juntos, dicen algo más contundente que «añadir defensas». Dicen: deje de arbitrar cadenas de texto. Cambie la interfaz.

Sin sintaxis en la que inyectar

La inyección SQL no terminó porque los saneadores mejoraron. Terminó porque las consultas parametrizadas eliminaron el lugar donde los datos podían convertirse en código. El mismo movimiento está disponible para los agentes: impedir que el modelo genere sintaxis ejecutable en absoluto.

Así es como está construido SQAI. El modelo nunca escribe SQL — ni saneado, ni revisado, ninguno. Su única herramienta ejecutora acepta una especificación tipada: una unión discriminada, kind: "query" | "computation", version: "1", validada contra un contrato de capacidades fijado por hash antes de que nada se ejecute. Nombrar una capacidad fuera del contrato y el motor responde unsupported_operation. Ninguna cadena que produzca el modelo se concatena, interpola o entrega a un intérprete — por lo que aquello que una inyección debe capturar, la sintaxis generada por el modelo, no existe en el pipeline. El argumento completo se desarrolla en por qué los agentes no deberían escribir SQL.

El vocabulario es cerrado, y es de solo lectura: cada una de las 4.574 capacidades expuestas, por construcción. No existe ninguna capacidad de escritura que nombrar, por lo que no hay incantación — por muy hábilmente que se introduzca en un ticket de soporte — que produzca una escritura. La vía de escape al código del host, getUnsafeRuntime, no es accesible desde ninguna herramienta del modelo.

Y porque la seguridad seria contempla el día en que el modelo es secuestrado, las capas detrás de la interfaz lo asumen:

  • Política que el modelo no puede direccionar. Las listas de permitidos para fuentes, campos y funciones se fijan en su código en el momento de createSQAI() y se aplican en proceso, antes de la ejecución. El esquema de herramientas no lleva campos allowed* — la superficie de política nunca entra en la ventana de contexto. Las violaciones devuelven denegaciones tipadas y no reintentables: policy_denied_source, policy_denied_field, policy_denied_function. Por qué esto supera a los roles y los flags: solo lectura que no lo era.
  • Resultados acotados al tenant. Los resultados grandes se almacenan bajo un result_id de 16 bytes aleatorios. Un tenant incorrecto recibe result_not_found — no un error de permisos que confirme que el dato existe — y las entradas expiran en 15 minutos.
  • Un canal acotado de vuelta al modelo. Como máximo 25 filas y 32.000 bytes regresan a la ventana de contexto, con truncación siempre declarada. Un prompt inyectado no puede hacer que la herramienta vuelque una tabla en la transcripción; el transporte no lo admite.
  • Errores que no revelan el mapa. Los errores visibles para el modelo pasan por sanitizeMessage, que elimina las rutas absolutas del sistema de archivos. El reconocimiento obtiene una denegación limpia, no un listado de directorios.

Evalúelo contra la trifecta. Datos privados: siguen presentes — ese es el trabajo. Contenido no confiable: sigue presente — los modelos leen lo que leen. Pero las patas que convierten la influencia en pérdida están cortadas. La vía de escritura está ausente, y la vía de lectura está en lista blanca, acotada al tenant, limitada — y con hash, de modo que lo que un agente secuestrado llegara a solicitar es reproducible para el análisis posterior.

204 excluded 4,778 capabilities in the contract 4,574 exposed to the model — every one read-only 4,564 deterministic within the declared scope 0 write capabilities — nothing to name, nothing to run
El vocabulario completo, a escala — un prompt secuestrado puede elegir valores distintos, nunca un verbo distinto (fuente: contrato de capacidades de SQAI).

El límite, redibujado

La inyección de prompts no está resuelta, y el registro sugiere que no lo estará pronto. LLM01 ha mantenido su posición durante dos ediciones consecutivas. Un sistema en producción con las defensas de Microsoft detrás cedió ante un solo correo. Los modelos seguirán leyendo texto hostil, y parte de él seguirá llegando.

Pero influir en el modelo y alcanzar su base de datos son fallos distintos, y solo el primero es inevitable. El ticket de Supabase se convirtió en un incidente porque el agente tenía service_role y una superficie SQL de forma libre — exactamente las patas que un secuestro necesita. El mismo ticket, dirigido a un vocabulario cerrado, tipado y de solo lectura, se convierte en una línea de log: unsupported_operation.

La solución a la inyección SQL nunca fue un filtro más inteligente. Fue un límite donde los datos no pueden convertirse en código. Trace ese límite de nuevo — esta vez alrededor del modelo — y la nueva inyección SQL obtiene el mismo final que la antigua. El resto de la superficie está documentado en /security.