Autopsia: cómo un agente LangChain borró 847,293 usuarios en producción

Reconstrucción técnica del incidente. Qué falló, por qué no se detectó, y qué lo habría prevenido.

12 min de lectura
INCIDENTE LANGCHAIN POSTGRES

Los datos de este incidente han sido anonimizados y modificados ligeramente para proteger la privacidad de la empresa afectada. El patrón de fallo es real y documentado.

Contexto del sistema

Una startup de fintech con 85 ingenieros construyó un asistente de IA para sus agentes de soporte. El asistente usaba GPT-4 con function calling para consultar datos de clientes.

El agente de LangChain tenía acceso a una tool llamada query_database con permisos de lectura/escritura sobre la base de datos de producción.

from langchain import SQLDatabase, SQLDatabaseChain
from langchain.llms import OpenAI

db = SQLDatabase.from_uri("postgres://analyst:password@prod-db:5432/production")
llm = OpenAI(temperature=0)
db_chain = SQLDatabaseChain.from_llm(llm, db, verbose=True)

Los permisos eran los mismos que un analista humano. Nadie pensó en restringirlos para el agente de IA.

Timeline del incidente

02:14:33 UTC — La query que lo desencadenó

Un operador de soporte preguntó al asistente: "¿Puedes limpiar los usuarios que se registraron con emails temporales?"

"Borra los usuarios con emails temporales de la base de datos de producción."

El LLM interpreta "limpiar" como DELETE en lugar de marcar como inactivos o exportar la lista. Esta es la naturaleza del lenguaje natural — ambiguo por definición.

DELETE FROM users;

La condición LIKE '%@%.temp%' OR LIKE '%@%.tmp%' no había sido probada. Coincidía con virtualmente todos los emails de la base de datos.

02:14:34 UTC — La query llega a producción

SQLDatabaseChain ejecuta la query sin validación adicional. La query pasa directamente a PostgreSQL.

No hubo error en los logs de la aplicación. La query ejecutó correctamente. La detección fue por monitorización de métricas de negocio.

02:14:37 UTC — 847,293 filas eliminadas

La respuesta del agente al error de bloqueo depende de cómo esté configurado el sistema de ReAct/reflection. Algunos agentes lo interpretan como un error temporal y lo reintentan.

02:31 UTC — El error se detecta

La alerta habría llegado al equipo de guardia en segundos, con el nodo AST exacto y la query original. El debug habría tardado minutos, no horas.

02:48 UTC — Inicio de recuperación

Se envían emails a los 847,293 usuarios afectados. El incidente requiere notificación según GDPR Art. 33 (violación de datos personales).

03:12 UTC — Servicio restaurado

El equipo inicia la restauración desde el último backup automático de RDS (30 minutos antes del incidente). 847,293 usuarios afectados, 34 minutos de inactividad del servicio de soporte.

07:40 UTC — Notificación a usuarios afectados

La base de datos se restaura al estado anterior. Se pierden 30 minutos de transacciones (registros de soporte del período entre el backup y el incidente).

Análisis del path de fallo

Este incidente tuvo cuatro puntos de fallo independientes que se combinaron para causar el desastre.

  1. Permisos excesivos: el agente tenía acceso DELETE en producción
  2. Sin validación de queries: ninguna capa entre el LLM y la DB
  3. Prompt ambiguo: "limpiar" es ambiguo para un LLM sin contexto de negocio
  4. Sin revisión humana para operaciones de escritura masiva

Lo que habría cambiado con AST parsing

Con Vericto como proxy entre SQLDatabaseChain y PostgreSQL, la query DELETE FROM users WHERE email LIKE ... habría sido interceptada antes de llegar a la base de datos.

  1. La query llega al proxy de Vericto vía protocolo de wire de Postgres
  2. pg_query construye el AST completo: DeleteStmt > WhereClause = BoolExpr(OR)
  3. VERICTO-090 detecta tautología OR en el WHERE (patrón de inyección / query no acotada)
  4. La query es bloqueada con SQLSTATE 42501 antes de llegar a la DB
  5. El agente recibe un error de permisos — exactamente como si no tuviera acceso DELETE
  6. El evento queda registrado en el audit trail con el nodo AST y la regla activada
  7. El agente de Slack recibe una notificación inmediata con el detalle del bloqueo

El LLM puede regenerar la query con diferentes parámetros después de un bloqueo. Las alertas en tiempo real permiten al equipo intervenir antes de que el agente lo intente de nuevo.

Lecciones del incidente

Más allá del caso específico, este incidente ilustra un principio general: un agente de IA no debería tener acceso irrestricto a operaciones destructivas en bases de datos de producción.

Aunque el agente recibe un error de permisos, el LLM puede regenerar la query con diferentes parámetros. Por eso la segunda defensa — alertas en tiempo real — es igualmente importante.

Estas son las cuatro lecciones más importantes de este incidente.

  • Los LLMs interpretan el lenguaje natural de forma literal y sin contexto de negocio
  • Los permisos de base de datos no distinguen entre humanos y agentes de IA
  • Una query sintácticamente correcta no es necesariamente una query intencionada
  • La detección de anomalías no sustituye a la prevención determinística

Qué hacer hoy

Si tienes un pipeline LLM-to-SQL en producción — o en staging donde podrías desplegarlo — hay tres pasos que puedes tomar hoy:

  1. Audita los permisos de base de datos de tus agentes de IA. ¿Tienen acceso DELETE? ¿DROP? ¿En producción?
  2. Añade una capa de validación determinística entre el LLM y la base de datos (Vericto u otra solución equivalente).
  3. Configura alertas en tiempo real para cualquier intento de operación destructiva, bloqueado o no.

Conectar Vericto toma menos de 5 minutos y no requiere cambios en tu código. El único cambio es el host en tu connection string.