Text-to-SQL seguro: cómo dejar que la IA escriba consultas sin poner en riesgo tu base de datos

Generar SQL a partir de lenguaje natural es de esas demos que enamoran en cinco minutos: escribes “los 10 clientes que más compraron el último mes” y aparece la consulta perfecta. El problema empieza cuando conectas esa demo a una base de datos real.
Mientras construía SQL Copilot tuve que pensar en esto desde el día uno. Un modelo de lenguaje no es determinista, puede malinterpretar una instrucción y, si lo dejas, puede escribir un DELETE con la misma confianza con la que escribe un SELECT. Estas son las capas de protección que considero obligatorias.
1. Dale contexto del esquema, no de los datos
Para generar buen SQL el modelo necesita saber qué tablas, columnas, tipos y relaciones existen. No necesita ver tus filas.
-- Esto sí se envía como contexto
CREATE TABLE orders (
id bigint PRIMARY KEY,
customer_id bigint REFERENCES customers(id),
total numeric(12,2),
created_at timestamptz
);El esquema, es decir los nombres, tipos y llaves foráneas, basta para que el modelo entienda cómo unir tablas. Los datos de clientes, montos reales o correos se quedan en tu servidor. Además, conviene decirle al modelo qué dialecto usas (PostgreSQL, MySQL, SQL Server), porque LIMIT, TOP y las funciones de fecha cambian entre motores.
2. Solo lectura por defecto (y de verdad)
La primera línea de defensa no es el prompt: es la base de datos. Conéctate con un usuario que solo tenga permisos de lectura:
CREATE ROLE copilot_reader LOGIN PASSWORD '...';
GRANT CONNECT ON DATABASE app TO copilot_reader;
GRANT USAGE ON SCHEMA public TO copilot_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO copilot_reader;
-- Extra en PostgreSQL: toda transacción será de solo lectura
ALTER ROLE copilot_reader SET default_transaction_read_only = on;Si algo falla en las capas de arriba, el motor igual rechaza cualquier escritura. En SQL Copilot esto se refleja en el Safe Mode, que fuerza la ejecución solo-SELECT y viene activado por defecto. Desactivarlo requiere una confirmación explícita.
3. Valida el SQL con un parser, no con regex
Es tentador revisar si la consulta “empieza con SELECT”. No alcanza: un WITH ... DELETE o varias sentencias separadas por ; se te escapan fácilmente. Usa un parser de SQL y revisa el árbol:
import { Parser } from 'node-sql-parser';
const parser = new Parser();
export function assertReadOnly(sql: string, database = 'PostgresQL') {
const ast = parser.astify(sql, { database });
const statements = Array.isArray(ast) ? ast : [ast];
if (statements.length !== 1) {
throw new Error('Solo se permite una sentencia por consulta.');
}
if (statements[0].type !== 'select') {
throw new Error(`Sentencia no permitida: ${statements[0].type}`);
}
}Si el parser no puede interpretar la consulta, recházala. Es mejor pedirle al usuario que reformule que ejecutar algo que no entiendes.
4. Pon límites al costo
Una consulta de solo lectura también puede hacer daño: un SELECT * sin filtro sobre una tabla de 200 millones de filas puede tumbar una réplica. Mis reglas:
- Exigir
LIMIT(oTOPen SQL Server) en las consultas generadas. - Timeout por sentencia, por ejemplo
SET statement_timeout = '15s'en PostgreSQL. - Paginación en la interfaz, en lugar de traer todo de golpe.
5. El humano revisa antes de ejecutar
La IA propone y la persona decide. Mostrar el SQL generado, permitir editarlo y que el usuario presione “Ejecutar” cambia completamente el modelo de riesgo. También es una oportunidad de aprendizaje: una función para explicar en lenguaje claro qué hace una consulta ayuda a que el usuario confíe en lo que va a correr.
6. Cuida las credenciales
Una herramienta que se conecta a bases de datos guarda cadenas de conexión, y eso es material sensible:
- Nunca guardes contraseñas en texto plano. Cífralas localmente (por ejemplo con AES-256-GCM) o usa el llavero del sistema operativo.
- No muestres secretos completos en la interfaz.
- No envíes credenciales al modelo, nunca.
7. Deja rastro
Guarda un historial de qué se pidió, qué SQL se generó y qué se ejecutó. Cuando algo sale raro, ese historial es lo único que te permite entender qué pasó.
Resumen
| Capa | Qué evita |
|---|---|
| Esquema sin datos | Filtrar información sensible al modelo |
| Usuario de solo lectura | Escrituras accidentales, aunque falle todo lo demás |
| Validación con parser | Sentencias peligrosas camufladas |
| LIMIT y timeouts | Consultas que tumban la base de datos |
| Revisión humana | Ejecutar algo que nadie entendió |
| Credenciales cifradas | Fugas de acceso |
La IA es excelente escribiendo SQL, pero la seguridad no puede depender de que el modelo se porte bien. Tiene que estar en la arquitectura.
Comparto más tips de desarrollo e IA en TikTok: @andydev22.