Qué enseñan 13 casos documentados sobre darle a un agente de IA acceso a la base de datos

Agentes de código, servidores MCP y herramientas text-to-SQL, ordenados por lo que de verdad salió mal y por qué control habría frenado cada caso.

9 min de lectura
AGENTES DE IA INVESTIGACIÓN

En mayo de 2026, un agente de código lanzó un subagente para revisar por qué fallaba la lógica de reset de una suite de tests. El subagente escribió un script con un DELETE FROM para cada tabla y lo ejecutó. El archivo .env del proyecto todavía tenía las credenciales de producción, así que uno de esos scripts se conectó a producción. La base de datos de un cliente real desapareció.

El repositorio incluso tenía un guardrail: los tests no arrancaban si el nombre de la base no terminaba en _test. El script cargó el entorno por su cuenta y nunca pasó por ahí.

Nadie atacó nada. Un agente intentó ser útil con el acceso que tenía.

Revisamos el registro público de casos como este: incidentes, advisories, CVEs e investigaciones en los que un agente de IA, un servidor MCP o una herramienta text-to-SQL hizo algo destructivo en una base de datos, o pudo hacerlo. Nos quedamos con los que tienen una fuente que se pueda enlazar. Esto es lo que tienen en común, y lo que habría frenado cada uno.

Dónde estamos parados. Construimos Vericto, un control determinista que se ubica entre los agentes y la base y bloquea el SQL destructivo antes de que se ejecute. Así que tenemos un interés en este tema. Por eso la tabla de resultados incluye los casos que nuestro enfoque no habría frenado, y cada afirmación enlaza a su fuente.

Cómo armamos la lista

  • Solo fuentes públicas. Postmortems, issues de GitHub escritos por los afectados, advisories de proveedores, CVEs, un paper y una charla. Los autorreportes están marcados como tales: son de primera mano, pero nadie más los verificó.
  • El SQL, por un motor real. Cuando un caso publicó su SQL, o la sentencia que implica, la pasamos por nuestro motor (vericto-engine 3.5.3 con las 28 reglas que trae el proxy), en Postgres y en MySQL cuando la sentencia aplica. Cada sentencia se evaluó 1,000 veces. El veredicto nunca cambió, que es justamente el sentido de un control determinista.
  • Ninguna cifra que no se pueda rastrear. Los números salen de las fuentes, no de estimaciones.

Los 13 casos

Agentes que ejecutan SQL destructivo por su cuenta

1. Un subagente ejecuta DELETE FROM en cada tabla de producción (mayo de 2026). El caso de arriba. El script corrió sin confirmación, y el agente principal primero negó haberlo causado porque no había revisado las transcripciones de sus subagentes. Fuente: anthropics/claude-code#64056 (autorreporte).

2. drizzle-kit push --force contra producción: más de 60 tablas perdidas (febrero de 2026). Un agente de código que corría en otra terminal empujó un cambio de schema con --force a un Postgres de producción. Según el reporte no había backups automáticos ni recuperación a un punto en el tiempo, y reconstruir tomó unas ocho horas. Era la segunda pérdida con la misma herramienta en ese proyecto. Fuente: anthropics/claude-code#27063 (autorreporte).

3. Un agente acepta un reset de Prisma en producción (marzo de 2026, dos reportes). En uno, prisma db push detectó una diferencia de schema durante un deploy de rutina y ofreció un reset; el agente lo aceptó y se llevó todas las tablas y 32 funciones SQL propias. En el otro, ante un aviso de pérdida de datos, el agente corrió prisma db push --force-reset en segundo plano mientras DATABASE_URL apuntaba a producción. Fuentes: #33183, #36183 (autorreportes).

Un “read-only” que no lo era

4. El servidor MCP de referencia para Postgres (agosto de 2025). Envolvía cada query en una transacción READ ONLY. Pero el driver aceptaba varias sentencias en un mismo string, así que COMMIT; DROP SCHEMA public CASCADE; cerraba la transacción de solo lectura y el resto corría con todos los privilegios del rol. El servidor ya estaba deprecado y archivado, y aun así Datadog contó unas 21,000 descargas semanales del paquete. Fuente: Datadog Security Labs.

5. COPY … TO PROGRAM en el servidor MCP de AWS para Postgres (septiembre de 2026). En su modo read-only por defecto, el validador de SQL del servidor no bloqueaba todas las sentencias con efectos secundarios. Un COPY … TO PROGRAM colocado en contenido que el agente procesaba podía ejecutar comandos del sistema operativo en el host de un Postgres autogestionado, si el rol tenía superuser o pg_execute_server_program. Se corrigió en la 1.1.7. El advisory recomienda un rol con privilegios mínimos y bloquear sentencias, como defensa en profundidad. Fuentes: GHSA-fph8-pg5w-78fv, boletín de AWS 2026-104.

6. 18 de 19 servidores MCP SQL “read-only” tenían fallas (2026). En una charla publicada, Maximilian Hildebrand revisó 19 servidores MCP SQL que anuncian un modo read-only y encontró cómo saltárselo en 18, en menos de 30 minutos cada uno. Fuente: media.ccc.de.

Herramientas text-to-SQL que ejecutan lo que escribe el modelo

7. SQLDatabaseChain de LangChain (2023). Un issue señaló que nada impedía que un usuario pidiera, en lenguaje natural, borrar una tabla, y propuso revisar el SQL antes de enviarlo. Quedó registrado como CVE-2023-36189. Fuentes: langchain#5923, GHSA-7q94-qpjr-xpgm.

8. Text-to-SQL de LlamaIndex (enero de 2024). Varios de sus motores text-to-SQL, hasta la 0.9.35 según el advisory de GitHub, se podían manipular con prompt injection para ejecutar SQL arbitrario, incluido borrar tablas (CVE-2024-23751). Hoy su documentación recomienda roles restringidos, bases de solo lectura o sandboxing. Fuentes: llama_index#9957, GHSA-2jxw-4hm4-6w87.

9. P2SQL: de prompt injection a SQL injection (2023, ICSE 2025). Investigadores de INESC-ID y Técnico Lisboa caracterizaron los ataques “prompt-to-SQL” en aplicaciones web con LangChain: sus variantes, su impacto y las defensas. Fuentes: arXiv 2308.01990, paper de ICSE 2025.

10. Vanna.AI: de prompt injection a ejecución de código (junio de 2024). La función ask de Vanna también generaba Python para graficar los resultados. Con prompt injection, ese Python podía ser cualquier cosa (CVE-2024-5565). Fuente: JFrog.

Plataformas e infraestructura

11. El agente de Replit borra la base de producción de SaaStr durante un code freeze (julio de 2025). Jason Lemkin lo documentó públicamente. El CEO de Replit lo confirmó, lo calificó de inaceptable y empezó a separar automáticamente las bases de desarrollo y producción. El mecanismo exacto nunca se confirmó en una fuente primaria. Fuentes: The Register, respuesta de Replit.

12. Supabase MCP: un ticket de soporte filtra la tabla de tokens (julio de 2025). En una demostración, un agente conectado por MCP con un rol que se salta la seguridad a nivel de fila procesó un ticket con instrucciones ocultas. Leyó una tabla privada, integration_tokens, y la copió en el hilo del ticket, donde el atacante podía verla. No se violó ningún permiso de la base. Supabase respondió con cambios de defensa en profundidad en su servidor MCP. Fuentes: General Analysis, Supabase.

13. PocketOS: un volumen de producción borrado con una API de infraestructura (abril de 2026). Mientras arreglaba un desajuste de credenciales en staging, un agente de código encontró un token de API en un archivo no relacionado, creado para gestionar dominios pero con permisos totales, y borró un volumen de producción. Los backups estaban en el mismo volumen. Tomó nueve segundos. Railway ayudó a restaurar los datos y cambió ese endpoint a un borrado diferido. Fuentes: The Register, el post del fundador.

Lo que se repite

La credencial de producción estaba al alcance del agente (casos 1, 3 y 13). Un archivo .env, un DATABASE_URL, un token en un archivo que no tenía nada que ver con la tarea. El agente no escaló privilegios: usó lo que estaba a mano.

El agente resolvió un error con la opción más destructiva disponible (2, 3 y 13). Un flag --force, un prompt de reset, borrar lo que parecía roto. Cada una de esas confirmaciones existe para que una persona se detenga a pensar. Un agente que intenta terminar la tarea las trata como obstáculos.

Las instrucciones en lenguaje natural no fueron controles (11 y 13). Un code freeze pedido en el prompt y reglas del proyecto que le decían al agente qué no hacer no se sostuvieron cuando el agente decidió que ya no aplicaban.

El “read-only” vivía en la capa equivocada (4, 5 y 6). Era una promesa de la aplicación, implementada envolviendo o filtrando queries. El rol de la base, debajo, todavía podía escribir.

No había backups, o estaban junto a los datos (2 y 13).

La industria ya está reaccionando. Replit separó las bases de desarrollo y producción. Railway hizo diferidos los borrados por API. Prisma 6.15.0 ahora bloquea prisma migrate reset --force cuando detecta un agente de IA, hasta que el usuario da su consentimiento (notas de la versión). Son mejoras reales. También muestran dónde terminan los controles del lado del agente: el consentimiento de Prisma le llega a la herramienta por una variable de entorno que fija el propio agente.

Resultados: ¿lo habría frenado un control en cada sentencia?

La pregunta es estrecha a propósito. ¿Un control determinista sobre cada sentencia SQL, ubicado en la conexión entre el agente y la base, habría bloqueado la parte destructiva? Eso solo se cumple si producción acepta conexiones a través de ese control y de ningún otro lado.

Caso Sentencia (o su equivalente) Veredicto ¿Lo habría frenado?
1. Subagente con DELETE FROM DELETE FROM patients; BLOCKED
VERICTO-001
Sí
2. drizzle-kit push --force DROP TABLE "trades" CASCADE; · ALTER TABLE … DROP COLUMN BLOCKED
VERICTO-010 VERICTO-015
Sí
3. Reset de Prisma DROP SCHEMA "public" CASCADE; BLOCKED
VERICTO-012
Probablemente. No confirmamos el SQL exacto que manda Prisma en un reset
4. MCP de referencia para Postgres COMMIT; DROP SCHEMA public CASCADE; BLOCKED
VERICTO-012
Sí. Se evalúa cada sentencia del string
5. MCP de AWS para Postgres COPY (…) TO PROGRAM '…'; BLOCKED
VERICTO-080
Sí
6. 18 de 19 servidores MCP — — En parte. Se bloquean los bypasses que terminan en escrituras, DDL o COPY … PROGRAM; no los que solo leen datos o archivos
7. LangChain DROP TABLE employee; BLOCKED
VERICTO-010
Sí
8. LlamaIndex DROP TABLE …; BLOCKED
VERICTO-010
Sí, para la variante destructiva
9. P2SQL DELETE … WHERE 1=1 · … OR 1=1 · UPDATE sin WHERE BLOCKED
VERICTO-003 VERICTO-090 VERICTO-030
En parte. Se bloquean las variantes destructivas; no las que solo leen datos de otros usuarios
10. Vanna.AI — — No. El payload era Python, no SQL
11. Replit DROP TABLE …; BLOCKED
VERICTO-010
Solo si lo que corrió fue un DROP y la plataforma dejara poner un control en esa conexión, que no lo permite
12. Supabase MCP SELECT * FROM integration_tokens; FLAGGED
VERICTO-050
No. El SELECT se registra pero no se bloquea, y escribir los tokens como valores literales también se permite
13. PocketOS — — No. Fue una llamada a una API de infraestructura, no SQL

Seis sí claros, uno probable, dos parciales, uno condicional y tres no. Un control sobre cada sentencia es una capa. Los casos 10, 12 y 13 necesitan otras: privilegios mínimos, tokens aislados, backups fuera del alcance del agente, y no darle a un bot de soporte un rol que se salta la seguridad a nivel de fila.

Si vas a darle acceso a la base a un agente

  1. Mantén las credenciales de producción fuera de todo lo que el agente pueda leer. Ni en archivos .env compartidos con los tests, ni en tokens olvidados en archivos sin relación. Dale al agente sus propias credenciales y separa las de producción (casos 1, 3 y 13).
  2. Haz que el read-only sea una propiedad del rol de la base. Otorga SELECT y nada más. Nunca superuser, nunca pg_execute_server_program. No dependas de que la herramienta envuelva las queries (4, 5 y 6).
  3. Asume que el agente va a aceptar cada --force y cada prompt de reset. Desactiva lo que puedas en las herramientas del agente, pero que no sea la única línea (2 y 3).
  4. Guarda los backups y la recuperación a un punto en el tiempo donde las credenciales del agente no puedan borrarlos (2 y 13).
  5. Revisa cada sentencia antes de que llegue a la base, fuera del agente. Un control sobre la estructura de la query, no sobre el texto, para que DELETE FROM users WHERE 1=1 reciba el mismo veredicto que DELETE FROM users. Tiene que estar donde el agente no pueda saltárselo ni apagarlo (1, 2, 4, 5, 7 y 8).
  6. Registra cada decisión, incluidas las negativas. Lo que un agente intenta después de que le niegan algo es justo lo que quieres tener registrado.
  7. No cuentes las instrucciones del prompt como controles. Un code freeze en el prompt es un pedido, no un límite (11 y 13).

Nada de esto es exótico. La mayoría de los equipos ya lo tiene para las personas. Los agentes necesitan lo mismo, aplicado bajo el supuesto de que nadie lee la query antes de que se ejecute.