El problema con los allowlists
Cuando los equipos de ingeniería piensan en proteger sus bases de datos de agentes LLM, la primera solución que aparece es un allowlist: define qué operaciones están permitidas (SELECT) y bloquea el resto (DELETE, DROP, UPDATE).
El problema es que este enfoque bloquea por el primer token de la query, no por su semántica. Y los agentes de IA generan patrones que un allowlist simple no puede detectar.
Un allowlist no sabe la diferencia entre
UPDATE users SET name = $1 WHERE id = $2yUPDATE users SET role = 'admin'. Para un allowlist, ambas son "UPDATE" — o ambas pasan, o ambas se bloquean.
Si bloqueas todos los UPDATE, rompes tu aplicación. Si permites todos los UPDATE, estás a un prompt ambiguo de un desastre de producción.
Lo que un allowlist no puede detectar
Estos son los cuatro patrones que vemos repetidamente en pipelines LLM-to-SQL reales, que pasan todos los allowlists de operaciones sin problema:
1. UPDATE sin WHERE clause
-- Un agente responde a "actualiza el precio del producto"
-- con contexto ambiguo y genera esto:
UPDATE products SET price = 0;
Un allowlist que permite UPDATE no detecta esto. La query modifica todas las filas de la tabla — en una tabla de catálogo con 50,000 productos, el daño es inmediato e irreversible.
2. DELETE con WHERE siempre verdadero
-- El LLM fue instruido para "limpiar registros viejos" pero
-- generó una condición trivialmente verdadera:
DELETE FROM sessions WHERE 1=1;
Tiene WHERE clause — pasa cualquier allowlist que solo verifique la presencia de WHERE. Pero WHERE 1=1 es semánticamente idéntico a no tener WHERE. El AST parsing evalúa si el predicado puede ser false para algún valor. 1=1 nunca puede ser false, por lo que el engine lo trata como ausente.
3. TRUNCATE disfrazado en un CTE
-- Patrón observado en agentes que construyen queries multi-step:
WITH cleanup AS (
SELECT id FROM old_data
)
TRUNCATE TABLE orders;
Un allowlist que bloquea TRUNCATE por el primer token podría no detectar TRUNCATE cuando aparece después de una cláusula WITH. El AST parsing recorre el árbol completo y detecta TruncateStmt en cualquier nivel de anidamiento.
4. DROP TABLE en subquery anidada
-- Raro pero documentado en modelos con function calling agresivo:
DO $$
BEGIN
DROP TABLE IF EXISTS users;
END $$;
El primer token es DO, no DROP. Un allowlist que solo verifica tokens iniciales lo pasa. El AST de Postgres descompone el bloque PL/pgSQL y detecta el DropStmt dentro.
Cómo funciona el AST parsing frente a estos casos
El AST (Abstract Syntax Tree) parsing construye una representación completa del árbol sintáctico de la query. En lugar de leer el texto de izquierda a derecha, el parser convierte la query en una estructura de datos jerárquica donde cada nodo tiene un tipo, propiedades y nodos hijo.
Para la query UPDATE products SET price = 0, el árbol AST se ve así:
UpdateStmt
├── relation: "products"
├── targetList:
│ └── ResTarget
│ ├── name: "price"
│ └── val: Integer(0)
└── whereClause: NULL ← el nodo que activa VETRO-042
La regla VETRO-042 evalúa exactamente una condición: UpdateStmt.whereClause == NULL. Si se cumple, la query se bloquea — independientemente de qué tabla se modifique, qué columnas, o con qué valores.
Para el caso de WHERE 1=1, la evaluación es más sofisticada:
DeleteStmt
├── relation: "sessions"
└── whereClause:
└── A_Expr
├── kind: AEXPR_OP
├── name: "="
├── lexpr: Integer(1)
└── rexpr: Integer(1) ← ambos operandos son literales iguales
→ evaluable estáticamente → siempre true
El engine de Vetro incluye un evaluador estático de constantes que detecta expresiones booleanas literales. 1=1, 'a'='a', true = true, NOT false — todas son detectadas como predicados triviales y tratadas como ausencia de WHERE.
¿No es suficiente con los permisos de base de datos?
Es la objeción que escuchamos más frecuentemente: "simplemente le damos al agente un usuario de base de datos con permisos solo de SELECT".
El problema con esta estrategia es que es demasiado restrictiva en la práctica. Los agentes de IA que construyen valor real necesitan poder escribir: insertar eventos, actualizar estados, registrar métricas. Un usuario solo-SELECT no puede hacer nada de esto.
Y cuando le das permisos de escritura para las operaciones legítimas, estás de vuelta al problema original: el agente puede generar un UPDATE o DELETE sin WHERE clause, y los permisos de base de datos no distinguen entre UPDATE users SET name = $1 WHERE id = $2 y UPDATE users SET role = 'admin'.
Los permisos de base de datos y el AST parsing son capas de defensa complementarias, no alternativas. Vetro no reemplaza el principio de least privilege en tu base de datos — lo complementa con un análisis semántico que los permisos no pueden hacer.
Por qué importa que sea determinístico
La alternativa al AST parsing para detectar queries peligrosas es usar otro modelo de ML que "entienda" si una query es peligrosa. Este enfoque tiene tres problemas fundamentales que lo hacen inadecuado para el path de seguridad:
- Falsos negativos: un modelo entrenado en queries históricas puede no conocer los patrones específicos que genera tu LLM en tu dominio
- Falsos positivos: bloquear queries legítimas en producción es tan catastrófico para la experiencia del equipo como dejar pasar queries destructivas
- No auditable: no puedes incluir en un reporte SOC2 "un modelo con 99.2% de accuracy protege nuestros datos" — pero sí puedes incluir "el parser AST bloquea determinísticamente cualquier UpdateStmt con whereClause = NULL"
El AST parsing de UPDATE products SET price = 0 produce el mismo resultado en el 100% de las ejecuciones, en cualquier momento, con cualquier carga del sistema. No hay varianza estocástica, no hay drift de modelo, no hay temperatura de inferencia. Es matemática.
Cómo implementarlo en <5 minutos
Si tienes un pipeline LLM-to-SQL sobre Postgres o MySQL, la integración de Vetro es un cambio de una línea en tu connection string:
- DATABASE_URL=postgres://user:pass@prod-db.host:5432/mydb
+ DATABASE_URL=postgres://user:pass@proxy.vetro.dev:5432/workspace_id/mydb
Tu ORM (Prisma, SQLAlchemy, Drizzle, ActiveRecord) no sabe que hay un proxy en el medio. Las queries llegan igual, Vetro las intercepta, analiza el AST, y si son seguras las reenvía a tu base de datos real con menos de 2ms de overhead.
Puedes explorar la lista completa de reglas y sus condiciones AST en la Referencia de Reglas AST.