PRODPlataforma BaaS soberana europeaAbrir panel →

IA nativa · 9 lectura mínima

Asegure NL2SQL contra la inyección SQL de LLM

Affane Daylami · Fondateur · 16 de abril de 2026

volver al blog

Un punto final NL2SQL transforma una pregunta en lenguaje natural en una consulta SQL, luego esta consulta se ejecuta en su base de datos. Por lo tanto, el riesgo no es la clásica inyección SQL, una cadena mal escapada en un formulario: es un modelo de lenguaje que decide por sí solo qué SQL escribir. Pedirle cortésmente al modelo que solo genere SELECT en el indicador del sistema no impide nada estructuralmente, es una instrucción, no un control de acceso. El único método que funciona es validar el SQL generado posteriormente, con un analizador que construye su árbol sintáctico.

Este texto en inglés se generó automáticamente a partir del original en francés y aún no ha sido revisado.
Esta página fue traducida automáticamente. La versión en inglés es autorizada.

Esta guía detalla el método que realmente funciona: restricción estructural para SELECCIONAR, lista blanca cerrada de funciones, límite de fila obligatorio y bloqueo del esquema consultado. Cada paso depende del validador realmente implementado en el motor NL2SQL de Aurabase, una capacidad de su IA nativa integrada en el backend, no de un servicio de terceros creado después del hecho. Si el tema es nuevo para usted, nuestra descripción general de NL2SQL sienta las bases, y el tutorial paso a paso muestra cómo construir el punto final completo.

Lo esencial

  • La ingeniería rápida (“solo genera SELECT”) es no un control de seguridad: un modelo puede alucinar, guiarse por una pregunta ambigua o simplemente ignorar las instrucciones.
  • La validación que se cumple es estructural: un analizador construye el árbol sintáctico (AST) de la solicitud y rechaza por defecto todo lo que no esté autorizado explícitamente.
  • Cuatro capas concretas limitan el riesgo: SELECT estricto (ni subconsulta, ni CTE, ni UNION), lista blanca cerrada de diez funciones, LIMIT obligatorio y limitado, acceso bloqueado al catálogo del sistema y esquemas de no inquilino.
  • El esquema consultado debe provenir del servidor , nunca de un campo en la solicitud del cliente: de lo contrario, nada impide que una persona que llama proporcione su propio esquema para evitar la validación.
  • En Aurabase, este validador (Rust crate sqlparser) se prueba con casos contradictorios documentados en el código: funciones prohibidas ocultas en FILTER, en un intraagregado ORDER BYo en OFFSET.
#
El verdadero problema

¿Por qué una instrucción en el sistema no bloquea nada?

Un mensaje del sistema que dice "solo genera consultas SELECT" es una preferencia, no una barrera. El modelo lo respeta la mayor parte del tiempo porque ha sido entrenado para seguir las instrucciones, no porque una restricción técnica le impida físicamente escribir algo más. Dos clases de fracasos hacen que esta confianza en la producción sea insuficiente.

La primera surge de la propia pregunta. Un usuario, mal intencionado o simplemente creativo en su formulación, puede dirigir la pregunta de tal manera que empuje el modelo hacia SQL que no debería haber escrito: una unión a una tabla sensible, un filtro que elude la lógica esperada, una llamada a una función del sistema. El modelo no distingue entre una pregunta legítima y otra diseñada para manipularla.

El segundo no requiere malicia. Un modelo puede alucinar el nombre de una tabla, olvidar el LIMIT que solicitó el mensaje o generar un SELECT * sin restricciones en una tabla grande. El resultado es el mismo en ambos casos: SQL potencialmente costoso o intrusivo, que ha pasado el filtro de solicitud y está a punto de ejecutarse en una base de datos real.

La imagen que ayuda

El indicador del sistema sigue siendo útil, dirige el modelo hacia el resultado correcto la mayor parte del tiempo. Pero una señal de “prohibido el acceso” no detiene a nadie que no sepa leer o que decida ignorarla. Necesita una puerta cerrada detrás, no sólo un panel delante.

#
Paso 1

Analice el SQL generado en un árbol de sintaxis, nunca en una cadena sin formato

La primera línea de defensa es analizar el SQL producido por el modelo con un analizador real para el dialecto de destino y luego validar la estructura resultante, no el texto sin formato. La búsqueda de palabras prohibidas en la cadena de caracteres (“DROP”, “DELETE”, “;”) se omite trivialmente: mayúsculas y minúsculas diferentes, comentario insertado en medio de una palabra clave, comillas escritas. Un árbol de sintaxis describe sin ambigüedades lo que realmente hace la consulta.

Aurabase implementa este paso con la caja Rust sqlparser y su dialecto PostgreSqlDialect. Incluso antes del análisis, un primer filtro léxico rechaza dos construcciones que son difíciles de razonar correctamente una vez en el árbol: cotizaciones de dólares ($$...$$), que pueden ocultar contenido arbitrario en una cadena, y comentarios de varias líneas (/* */), que pueden ocultar el final real de una instrucción.

nl2sql/validator.rsrust
let dialect = PostgreSqlDialect {};
let mut statements = Parser::parse_sql(&dialect, sql)
    .map_err(|e| AuraError::BadRequest(format!("Erreur de parsing SQL: {}", e)))?;

if statements.len() > 1 {
    return Err(AuraError::BadRequest("Multi-statements non autorisés".to_string()));
}

Este rechazo de declaraciones múltiples por sí solo bloquea la forma más conocida de inyección SQL mediante apilamiento: SELECT * FROM users; DROP TABLE users;--. El analizador solo devuelve una instrucción procesable, la segunda simplemente nunca se alcanza, sin importar cómo esté redactada en la pregunta original.

#
Paso 2

Restringir estructuralmente a un SELECT simple

Una vez obtenido el árbol, la validación más amplia es aceptar solo un tipo de nodo raíz, una consulta (Statement::Query), y rechazar todo lo demás: INSERT, UPDATE, DELETE, DROP, CREATE, ALTER. Ya no es una instrucción rápida, es una condición sobre el tipo de objeto analizado, que ninguna formulación hábil de la pregunta puede eludir.

Incluso dentro de un SELECT, varias construcciones siguen siendo peligrosas y merecen su propio rechazo explícito:

Construcción rechazada¿Por qué es peligroso?
CTE/CONPuede encadenar lógica adicional no deseada antes del SELECT final.
Subconsultas, UNION / INTERSECT / EXCEPTOAmplía la superficie de lo que una sola pregunta puede hacer en una sola consulta.
SELECCIONAR...ENCrea una tabla: escritura disfrazada de lectura.
PARA ACTUALIZAR / PARA COMPARTIRInstalación de cerraduras, riesgo de contención con el tráfico de producción.
Funciones de tabla (generate_series, pg_read_file...)Acceso al sistema o denegación de servicio mediante líneas generadas bajo demanda.

Un caso de prueba tomado del repositorio ilustra concretamente el último punto: SELECT * INTO backup FROM users es rechazado, aunque no contiene ni una palabra clave de escritura visible ni una función sospechosa. La forma de la solicitud es suficiente para descalificarla.

#
Paso 3

Lista blanca de funciones, no lista negra

Una lista negra de funciones prohibidas (pg_sleep, pg_read_file, dblink...) requiere anticipar cada función peligrosa una por una, mientras Postgres expone varios cientos de ellas. Una lista blanca invierte la carga de la prueba: solo se autorizan diez funciones, count, sum, avg, min, max, lower, upper, coalesce, date_trunc, now. Todo lo demás está prohibido de forma predeterminada, incluida una característica legítima que nadie ha pensado en agregar todavía.

Un único pase de validación no siempre es suficiente. Un recorrido estructural del árbol enumera sus puntos de entrada uno por uno (proyección, DÓNDE, JOIN, GROUP BY...), y es fácil olvidar uno: una función prohibida se puede ocultar en una cláusula FILTER (WHERE pg_sleep(10) IS NOT NULL), en un ORDER BY intraagregado (sum(id ORDER BY pg_sleep(10))), en WITHIN GROUP, DISTINCT ONo OFFSET.

nl2sql/tests.rsrust
#[test]
fn test_rejette_fonction_interdite_dans_filter() {
    assert!(validate(
        "SELECT count(*) FILTER (WHERE pg_sleep(10) IS NOT NULL) FROM users",
        None, None
    ).is_err());
}

Por tanto, el validador de Aurabase añade una segunda pasada exhaustiva, que recorre todas las expresiones del árbol donde quiera que estén, independientemente de la ruta estructural. Es una supuesta defensa en profundidad: si el primer pase falla en un caso, el segundo lo alcanza.

#
Paso 4

Encuadernado las líneas devueltas: LÍMITE obligatorio y limitado

SELECT * sigue autorizado, es útil para la minería de datos. El riesgo no es la estrella, es la ausencia de un techo a una consulta escrita por un modelo: una pregunta mal formulada puede traer de vuelta una tabla entera, con el coste de memoria y el tiempo de respuesta que eso implica.

Aurabase aplica una regla sencilla y transparente. Si el SQL generado no tiene un LIMIT, el servidor agrega uno (100 líneas por defecto, valor anunciado al modelo en el indicador del sistema). Si SQL solicita un LIMIT más allá de un límite máximo (1000 filas de forma predeterminada), la consulta se rechaza explícitamente en lugar de reducirse silenciosamente. Ambos valores son configurables en el lado del servidor (AI_NL2SQL_DEFAULT_LIMIT, AI_NL2SQL_MAX_LIMIT) y el servidor incluso se niega a iniciarse si la falla excede el límite máximo.

réponse /v1/ai/{project_id}/nl2sql (extrait)json
{
  "sql": "SELECT count(*) FROM orders WHERE status = 'paid' LIMIT 100",
  "limit": 100,
  "limit_injected": true
}
Información

Negarse en lugar de recortar silenciosamente tiene un interés directo: un límite máximo aplicado sin decirlo daría a quien llama la ilusión de que su petición fue respetada, mientras que el resultado se habría truncado sin que él lo supiera. limit_injected siempre dice si el valor proviene del modelo o del servidor.

#
Paso 5

Bloquear el acceso al esquema: catálogo del sistema y esquema cruzado

Dos filtraciones distintas amenazan un motor NL2SQL conectado a una base de datos real: el acceso al catálogo del sistema Postgres y el acceso a un esquema que no pertenece a la persona que llama. Ambos se bloquean tras la validación, independientemente de cualquier política RLS colocada en sentido posterior.

pg_catalog siempre es parte de search_path, lo que significa que un nombre no calificado como pg_authid o pg_stat_activity accede a él directamente, sin prefijo. El validador de Aurabase bloquea cualquier nombre que comience con pg_, así como information_schema y el esquema interno aura_console, ya sea calificado o no.

En un nombre de dos componentes (schema.table), solo se permite el esquema del proyecto que realiza la llamada; cualquier otro valor se rechaza. Un nombre con tres o más componentes se rechaza automáticamente. Este límite en el nivel de consulta generada se suma al aislamiento a nivel de base de datos que se detalla en nuestro artículo sobreaislamiento multiinquilino: uno evita que el SQL generado apunte a otro esquema, el otro evita que la conexión misma llegue a otra base de datos. Ninguno reemplaza al otro.

#
Paso 6

Nunca permita que el cliente redefina el esquema consultado

Una trampa discreta acecha a cualquier API NL2SQL que acepte un parámetro que describa el esquema o las tablas permitidas en la consulta del cliente. Si se utiliza este mismo parámetro para construir el mensaje y validar el SQL de salida, una persona que llama puede mentir sobre lo que está permitido y la validación luego valida contra esta mentira en lugar de contra la realidad de la base de datos.

Aurabase realiza una introspección del esquema base del proyecto real en cada llamada, con un breve caché de treinta segundos para el rendimiento, y rechaza explícitamente (error 400) cualquier campo schema, allowed_schemao schema_context enviado en el cuerpo de la solicitud, en lugar de aceptarlo y luego sobrescribirlo silenciosamente. La diferencia importa: un campo aceptado y luego ignorado da la ilusión de un control que no existe; un campo rechazado lo dice de inmediato.

#
Lista de verificación

Audite su propia canalización NL2SQL antes de la producción

Ya sea que utilice Aurabase o cree su propio proceso sobre un LLM genérico, los siguientes puntos cubren lo que con mayor frecuencia se pasa por alto.

Si escribe el validador usted mismo

  • Analice SQL con un analizador real para su dialecto exacto, nunca con coincidencia de patrones en una cadena.
  • Adopte un rechazo predeterminado: se debe rechazar cualquier tipo de nodo, cualquier función no autorizada explícitamente, no sólo los casos peligrosos ya identificados.
  • Acepte solo una declaración por consulta; este es el rechazo más simple contra consultas acumuladas.
  • Pruebe el validador con casos adversarios reales (función prohibida en FILTER, en un ORDER BY intraagregado, en OFFSET), no solo con casos obvios.
  • A pesar de todo, ejecute el SQL validado con un rol de Postgres con privilegios reducidos sobre el esquema esperado: el validador limita la forma de la consulta, el rol limita lo que puede lograr físicamente si se le escapa un caso.

Si está evaluando un marco NL2SQL de terceros

  • Pregunte explícitamente si la validación es estructural (AST) o sólo una instrucción rápida: la respuesta lo cambia todo.
  • Verifique que se aplique un límite de línea de forma predeterminada, no solo documentado como una práctica recomendada a su cargo.
  • Compruebe si el cliente API puede proporcionar el esquema utilizado para la validación, lo que reabriría exactamente la falla descrita anteriormente.
  • Compare varias herramientas según este criterio específico antes de elegir: nuestra comparación de las herramientas NL2SQL detalla lo que distingue los enfoques disponibles en 2026.
#
Defensa en profundidad

El validador reduce el riesgo, no reemplaza al RLS

Un validador AST sólido reduce el riesgo en el origen: el SQL que llega a su base de datos ya tiene un formato conocido y acotado. Sin embargo, no reemplaza las políticas RLS en sus tablas confidenciales, que deciden qué filas tiene derecho a ver un usuario determinado. Las dos capas responden a preguntas diferentes: el validador limita la forma de la consulta generada, el RLS limita los datos que puede devolver para un usuario específico. Mantenga ambos activos, incluso si uno parece redundante con el otro.

NL2SQL cubre preguntas estructuradas sobre sus tablas. Para preguntas sobre contenido no estructurado, documentos, notas, tickets, el RAG nativo de Aurabase sigue una lógica de seguridad comparable, detallada en nuestro tutorial de canalización de RAG en pgvector.

#
Preguntas frecuentes

Preguntas frecuentes

¿Es la ingeniería rápida completamente inútil para proteger NL2SQL?+
No, sigue siendo útil para dirigir el modelo hacia SQL correcto y relevante la mayor parte del tiempo. Pero no es un control de seguridad: una pregunta ambigua o manipulada puede pasarlo por alto, y una instrucción rápida no bloquea físicamente nada. Sigue siendo necesario un validador estructural después de la generación, independientemente de la calidad del mensaje.
¿Deberíamos seguir activando RLS si el SQL generado ya está validado y restringido a SELECT?+
Sí. El validador limita la forma de la solicitud (sin subconsulta, sin función que no esté en la lista blanca, LÍMITE limitado), no los derechos comerciales sobre los datos. El RLS sigue siendo la capa que decide qué líneas tiene derecho a ver un usuario específico, incluso dentro de un SELECT perfectamente válido.
¿Una lista blanca cerrada de diez funciones no limita demasiado las posibles preguntas?+
Sí, y es voluntario. Cubre la mayoría de los análisis de lectura (recuento, suma, promedio, mínimo, máximo, inferior, superior, coalesce, date_trunc, ahora) y niega todo lo demás de forma predeterminada, incluida una función legítima que nadie ha pensado en agregar todavía. Cada incorporación debe ser una decisión explícita, no un descuido en una lista negra.

¿LISTO PARA IMPLEMENTAR?

Tu backend en cinco minutos.

No se requiere tarjeta de crédito · 500 MB gratis · 50,000 MAU