Agentes SQL: cómo consultar datos con IA sin dar acceso peligroso a tu base de datos
Un agente SQL seguro no entrega una conexión al LLM: traduce intención dentro de un contrato y deja que el runtime y la base de datos apliquen identidad, roles, allowlists, límites, RLS y auditoría.
Un agente SQL no debe recibir una cadena de conexión, un usuario con permisos amplios ni la autoridad de inventar su propio alcance. Un LLM traduce una intención —por ejemplo, «compara ingresos por región este trimestre»— a una propuesta estructurada; el runtime decide si esa propuesta cabe en el contrato; y la base de datos vuelve a aplicar identidad, rol, allowlist, coste y RLS.
Esto también reduce prompt injection. La pregunta y los comentarios de una columna son datos no confiables; no pueden cambiar tenant, rol o política. La [guía de prompt injection en agentes](/prompt-injection-agentes-ia-seguridad-evals/) explica la frontera general: las instrucciones recuperadas o introducidas por el usuario nunca deben convertirse en permisos.
Arquitectura de tres capas
Capa 1 — intención. El adaptador recibe el texto y el contexto autenticado. Devuelve un objeto tipado con métrica, dimensiones, periodo, filtros declarados y nivel de precisión. Rechaza peticiones ambiguas que podrían revelar datos personales y pide aclaración. El tenant, el usuario y la región no salen del prompt: se inyectan desde la sesión validada.
Capa 2 — plan permitido. Un planner genera una consulta lógica sobre un catálogo pequeño de vistas analíticas, no sobre todas las tablas. Un validador comprueba que cada relación, columna, función y ordenación esté en la allowlist; que no existan INSERT, UPDATE, DELETE, DDL, subconsultas inesperadas ni acceso a funciones peligrosas; y que el coste estimado y la cardinalidad máxima sean aceptables. Si el plan no se puede demostrar, se rechaza o pasa a revisión.
Capa 3 — ejecutor. El servicio obtiene la identidad real del usuario, fija `app.tenant_id` en la conexión, usa un rol de solo lectura, prepara la consulta con parámetros y aplica `statement_timeout`, límite de filas y cancelación. El resultado se serializa con un esquema conocido y se registra con un hash de la consulta, política, duración, filas y decisión. La respuesta del modelo se construye a partir de ese resultado, no de una ejecución que el modelo pueda repetir libremente.
¿Te está sirviendo? Hay una dosis cada semana
Te resumo herramientas de IA para devs, agentes, MCP, seguridad y workflows en un email de 5 minutos. En español y sin ruido.
Suscribirme gratis<figure style="margin:34px 0;font-family:system-ui,sans-serif;"><img src="https://devaisemanal.com/content/images/2026/09/architecture-8.png" alt="Arquitectura de tres capas para agentes SQL: intención, plan permitido y ejecutor con identidad, allowlist, RLS, timeout, límites y aprobación de mutaciones" style="width:100%;height:auto;border-radius:12px;border:1px solid #dbe3ef;background:#f8fafc;" /><figcaption style="font-size:14px;color:#64748b;margin-top:10px;line-height:1.5;">El LLM propone una intención y un plan; la identidad, la política y la base de datos deciden qué llega a ejecutarse. Las mutaciones salen por una rama separada con aprobación.</figcaption></figure>
Checklist
Ejemplo ejecutable: validador de lectura
Este ejemplo de Python no pretende sustituir un parser SQL de producción; muestra la frontera mínima que debe existir antes de llamar a PostgreSQL. La consulta final está parametrizada, solo acepta vistas conocidas y limita coste y resultado. En un servicio real añadirías un parser AST, `EXPLAIN` con límites y una conexión separada.
La interpolación de nombres solo aparece después de comparar con una allowlist fija; los valores permanecen como parámetros. El ejecutor debe abrir una transacción de solo lectura, fijar timeout y `row_security`, y fallar cerrado si no puede fijar el contexto de identidad. Un string que parezca SQL no es una prueba de que el plan sea válido.
PostgreSQL: identidad, rol de solo lectura y RLS
En PostgreSQL crea un rol de aplicación sin privilegios de escritura y concédele acceso únicamente a las vistas analíticas. Revoca `CREATE` en schemas accesibles y evita que el usuario del agente pueda cambiar funciones, search_path o políticas. El pool de conexiones debe limpiar estado al devolver una conexión: un `tenant_id` heredado sería una fuga silenciosa.
Puntos a revisar
Lo que conviene comprobar
Row-Level Security (RLS) es una segunda barrera, no una excusa para omitir filtros del servicio. La sesión autenticada puede fijar `SET LOCAL app.tenant_id = '...'` y la política comparar ese valor con cada fila. Revisa la [documentación oficial de RLS de PostgreSQL](https://www.postgresql.org/docs/current/ddl-rowsecurity.html), en particular el comportamiento de owners, roles con bypass y el modo `FORCE ROW LEVEL SECURITY` cuando corresponda.
Prueba la combinación real de rol, vista, función y política. Un superusuario o un owner puede observar filas que el rol de aplicación no ve. Si el servicio usa un pooler, valida que `SET LOCAL` vive dentro de la transacción correcta y que un error hace rollback antes de reutilizar la conexión.
Coste, límites y mutaciones
La seguridad también es disponibilidad. Define un máximo de filas, columnas, duración, memoria y llamadas por identidad. Configura `statement_timeout` y cancela la consulta al superar el presupuesto; PostgreSQL documenta este parámetro en su [configuración de cliente](https://www.postgresql.org/docs/current/runtime-config-client.html). Añade límites del gateway para que una persona no pueda abrir cientos de consultas caras en paralelo.
No devuelvas millones de filas para que otro modelo las resuma. Agrega en la base de datos, pagina con cursores controlados y redondea o suprime grupos pequeños si existe riesgo de reidentificación. Muestra al usuario cuándo una respuesta es aproximada, truncada o requiere exportación aprobada.
Las mutaciones son otro producto. No las escondas detrás de una herramienta `execute_sql`. Si el caso necesita crear un ticket o corregir un dato, expón una operación de dominio con argumentos tipados, validación, idempotencia, dry-run y aprobación humana. El principio de mínimo privilegio y la reducción de agencia excesiva son precisamente el foco de [OWASP LLM06](https://genai.owasp.org/llmrisk/llm062025-excessive-agency/).
Microsoft describe un principio parecido para Copilot Agent Mode con SQL Server: el agente debería operar con least privilege y permisos alineados con el usuario, no con un acceso administrativo global. La implementación concreta cambia entre motores, pero la frontera es la misma: la herramienta propone; la identidad y el servidor autorizan.
Preguntas frecuentes
¿Puedo dar al LLM una conexión de solo lectura?
No es suficiente. El rol debe limitar vistas, funciones, schemas, filas, tiempo y coste; además la conexión debe fijar la identidad del usuario y limpiar el contexto entre peticiones.
¿RLS sustituye al filtro de tenant del agente?
No. RLS debe ser la barrera final, mientras que el servicio conserva el filtro explícito para reducir datos, coste y riesgo de errores. Prueba ambas capas con roles reales.
¿Es seguro permitir SQL arbitrario si bloqueo INSERT y DELETE?
No necesariamente. SELECT puede leer secretos, ejecutar funciones, provocar scans caros o combinar tablas de forma inesperada. Usa un AST, catálogo de vistas, presupuesto y un ejecutor restringido.
¿Qué hago con una consulta que necesita escribir?
Expón una herramienta de dominio separada, con argumentos tipados, dry-run, idempotencia, autorización y aprobación humana. No eleves el rol del agente SQL general.
¿Cómo detecto que el agente está inventando columnas?
Valida el plan contra un catálogo versionado y devuelve un error estructurado para pedir aclaración. No intentes arreglar silenciosamente el SQL en producción.
¿Puedo reutilizar resultados en caché?
Solo con una clave que incluya identidad o ámbito, tenant, política, versión del catálogo y parámetros relevantes. La caché nunca puede saltarse una comprobación de autorización fresca.
Cómo desplegar un agente SQL seguro
- Definir el contrato. Expresa métricas, dimensiones, filtros y límites en un esquema versionado, y separa intención de autorización.
- Crear el catálogo. Publica pocas vistas analíticas y columnas permitidas; excluye secretos, datos innecesarios y funciones con efectos laterales.
- Validar el plan. Inspecciona el AST o genera SQL desde el contrato, comprueba allowlist, joins, coste, cardinalidad y ausencia de mutaciones.
- Ejecutar con identidad. Usa rol de solo lectura, tenant autenticado, RLS, parámetros, transacción, timeout, límite de filas y cancelación.
- Auditar y evaluar. Registra decisiones sanitizadas y prueba trayectorias normales, ambiguas, costosas, multi-tenant y adversariales.
- Ampliar gradualmente. Empieza en shadow mode, mide calidad y coste, y exige aprobación humana para cualquier operación de escritura.
Checklist
Conclusión: el modelo propone, el sistema decide
La pregunta útil no es si un modelo puede escribir SQL correcto, sino qué ocurre cuando escribe uno correcto para la persona equivocada, contra la vista equivocada o con un coste inesperado. La respuesta debe estar fuera del prompt: un contrato estrecho, un plan verificable, un ejecutor con identidad y una base de datos que vuelva a aplicar sus políticas.
Con esa separación, los agentes SQL dejan de ser una puerta directa a producción y se convierten en una interfaz analítica gobernable. Puedes mejorar el modelo, cambiar el catálogo o añadir una métrica sin entregar autoridad nueva por accidente.
Fuentes y referencias
También te puede interesar
RAG multi-tenant seguro: filtros y permisosPrompt injection en agentes de IA: guía prácticaEvaluación de agentes en producciónRouting LLM en producción: fallbacks y contratosObservabilidad GenAI con OpenTelemetryRecibe una lectura semanal de herramientas IA para devs
Cada semana te resumo herramientas de IA para devs, agentes, MCP, seguridad y workflows en un email de 5 minutos. En español y sin ruido.
Suscribirme gratis