PRODPlataforma BaaS europeia soberanaAbra o painel →

IA nativa · 9 minutos de leitura

Proteger NL2SQL contra injeção de LLM SQL

Affane Daylami · Fondateur · 16 de abril de 2026

Voltar ao blog

Um endpoint NL2SQL transforma uma pergunta de linguagem natural em uma consulta SQL e, em seguida, essa consulta é executada em seu banco de dados. O risco, portanto, não é a clássica injeção de SQL, uma string mal escapada em um formulário: é um modelo de linguagem que sozinho decide qual SQL escrever. Pedir educadamente ao modelo para gerar apenas SELECT no prompt do sistema não impede nada estruturalmente, é uma instrução e não um controle de acesso. O único método que funciona é validar o SQL gerado posteriormente, com um analisador que constrói sua árvore sintática.

Este texto em inglês foi gerado automaticamente a partir do original em francês e ainda não foi revisado.
Esta página foi traduzida automaticamente. A versão em inglês é oficial.

Este guia detalha o método que realmente funciona: restrição estrutural para SELECT, lista de permissões fechada de funções, limite de linha obrigatório e bloqueio do esquema consultado. Cada etapa depende do validador realmente implementado no mecanismo NL2SQL do Aurabase, um recurso de sua IA nativa incorporada ao back-end, e não um serviço de terceiros criado após o fato. Se o assunto for novo para você, nossa visão geral do NL2SQL estabelece as bases, e o tutorial passo a passo mostra como construir o endpoint completo.

O essencial

  • A engenharia de prompt (“gera apenas SELECT”) não é uma verificação de segurança: um modelo pode ter alucinações, ser guiado por uma pergunta ambígua ou simplesmente ignorar as instruções.
  • A validação válida é estrutural: um analisador constrói a árvore sintática (AST) da solicitação e rejeita por padrão qualquer coisa que não seja explicitamente autorizada.
  • Quatro camadas concretas limitam o risco: SELECT estrito (nem subconsulta, nem CTE, nem UNION), lista de permissões fechada de dez funções, LIMIT obrigatório e limitado, acesso bloqueado ao catálogo do sistema e esquemas não locatários.
  • O esquema consultado deve vir do servidor , nunca de um campo na solicitação do cliente: caso contrário, nada impede que um chamador forneça seu próprio esquema para ignorar a validação.
  • Na Aurabase, este validador (Rust crate sqlparser) é testado com casos adversários documentados no código: funções proibidas escondidas em FILTER, em um intra-agregado ORDER BY, ou em OFFSET.
#
O verdadeiro problema

Por que uma instrução no prompt do sistema não bloqueia nada?

Um prompt do sistema que diz "gera apenas consultas SELECT" é uma preferência, não uma barreira. O modelo o respeita na maioria das vezes porque foi treinado para seguir as instruções, não porque uma restrição técnica o impeça fisicamente de escrever qualquer outra coisa. Duas classes de fracasso tornam esta confiança insuficiente na produção.

A primeira vem da própria pergunta. Um usuário, mal intencionado ou simplesmente criativo em sua formulação, pode direcionar a questão de forma a empurrar o modelo para SQL que ele não deveria ter escrito: uma junção a uma tabela sensível, um filtro que contorna a lógica esperada, uma chamada de função do sistema. O modelo não distingue entre uma questão legítima e outra destinada a manipulá-la.

A segunda não requer malícia. Um modelo pode alucinar um nome de tabela, esquecer o LIMIT que o prompt solicitou ou gerar um SELECT * sem quaisquer restrições em uma tabela grande. O resultado é o mesmo em ambos os casos: SQL potencialmente caro ou intrusivo, que passou no filtro de prompt e está prestes a ser executado em um banco de dados real.

A imagem que ajuda

O prompt do sistema continua útil, pois direciona o modelo para o resultado correto na maioria das vezes. Mas uma placa de “acesso proibido” não impede ninguém que não saiba ler ou que decida ignorá-la. Você precisa de uma porta fechada atrás, não apenas de um painel na frente.

#
Passo 1

Analise o SQL gerado em uma árvore de sintaxe, nunca em uma string bruta

A primeira linha de defesa é analisar o SQL produzido pelo modelo com um analisador real para o dialeto alvo e, em seguida, validar a estrutura resultante, não o texto bruto. Uma busca por palavras proibidas na cadeia de caracteres (“DROP”, “DELETE”, “;”) é trivialmente contornada: maiúsculas e minúsculas, comentário inserido no meio de uma palavra-chave, aspas digitadas. Uma árvore de sintaxe descreve inequivocamente o que a consulta realmente faz.

Aurabase implementa esta etapa com a caixa Rust sqlparser e seu dialeto PostgreSqlDialect. Mesmo antes da análise, um primeiro filtro lexical rejeita duas construções que são difíceis de raciocinar corretamente uma vez na árvore: aspas de dólar ($$...$$), que podem ocultar conteúdo arbitrário em uma string, e comentários de várias linhas (/* */), que podem ocultar o final real de uma instrução.

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()));
}

Essa rejeição apenas de múltiplas instruções bloqueia a forma mais conhecida de injeção de SQL por empilhamento: SELECT * FROM users; DROP TABLE users;--. O analisador retorna apenas uma instrução acionável, a segunda simplesmente nunca é alcançada, não importa como esteja redigida na pergunta original.

#
Etapa 2

Restringir estruturalmente a um simples SELECT

Uma vez obtida a árvore, a validação mais ampla é aceitar apenas um tipo de nó raiz, uma consulta (Statement::Query), e rejeitar todo o resto: INSERT, UPDATE, DELETE, DROP, CREATE, ALTER. Não é mais uma instrução imediata, é uma condição sobre o tipo do objeto analisado, que nenhuma formulação hábil da questão pode contornar.

Mesmo dentro de um SELECT, diversas construções permanecem perigosas e merecem sua própria rejeição explícita:

Construção rejeitadaPor que é perigoso?
CTE / COMPode encadear lógica não intencional adicional antes do SELECT final.
Subconsultas, UNION / INTERSECT / EXCEPTExpande a área de superfície do que uma única pergunta pode fazer em uma única consulta.
SELECIONE...EMCria uma tabela: escrita disfarçada de leitura.
PARA ATUALIZAR / PARA COMPARTILHARInstalação de fechaduras, risco de contenção com tráfego de produção.
Funções de tabela (generate_series, pg_read_file...)Acesso ao sistema ou negação de serviço através de linhas geradas sob demanda.

Um caso de teste retirado do repositório ilustra concretamente o último ponto: SELECT * INTO backup FROM users é rejeitado, mesmo que não contenha palavra-chave de escrita visível nem função suspeita. A forma do pedido é suficiente para desqualificá-lo.

#
Etapa 3

Lista de permissões de funções, não lista negra

Uma lista negra de funções proibidas (pg_sleep, pg_read_file, dblink...) requer a antecipação de cada função perigosa, uma por uma, enquanto o Postgres expõe várias centenas delas. Uma lista de permissões inverte o ônus da prova: apenas dez funções são autorizadas, count, sum, avg, min, max, lower, upper, coalesce, date_trunc, now. Todo o resto não é permitido por padrão, incluindo um recurso legítimo que ninguém pensou em adicionar ainda.

Uma única passagem de validação nem sempre é suficiente. Uma travessia estrutural da árvore lista seus pontos de entrada um por um (projeção, WHERE, JOIN, GROUP BY...), e é fácil esquecer um: uma função proibida pode estar oculta em uma cláusula FILTER (WHERE pg_sleep(10) IS NOT NULL), em um ORDER BY intra-agregado (sum(id ORDER BY pg_sleep(10))), em WITHIN GROUP, DISTINCT ONou 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());
}

O validador Aurabase adiciona portanto uma segunda passagem exaustiva, que percorre todas as expressões da árvore onde quer que estejam, independentemente do caminho estrutural. É uma defesa assumida em profundidade: se o primeiro passe erra um caso, o segundo o alcança.

#
Etapa 4

Limite as linhas retornadas: LIMIT obrigatório e limitado

SELECT * permanece autorizado, é útil para mineração de dados. O risco não é a estrela, é a ausência de teto em uma consulta escrita por um modelo: uma pergunta mal formulada pode trazer de volta uma tabela inteira, com o custo de memória e o tempo de resposta que isso implica.

Aurabase aplica uma regra simples e transparente. Caso o SQL gerado não possua um LIMIT, o servidor adiciona um (100 linhas por padrão, valor anunciado ao modelo no prompt do sistema). Se o SQL solicitar um LIMIT além do limite máximo (1.000 linhas por padrão), a consulta será recusada explicitamente em vez de reduzida silenciosamente. Ambos os valores são configuráveis ​​no lado do servidor (AI_NL2SQL_DEFAULT_LIMIT, AI_NL2SQL_MAX_LIMIT), e o servidor até se recusa a iniciar se a falha ultrapassar o teto.

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

Recusar, em vez de reduzir silenciosamente, tem um interesse directo: um limite máximo aplicado sem o declarar daria ao interlocutor a ilusão de que o seu pedido foi atendido, enquanto o resultado teria sido truncado sem que ele soubesse. limit_injected sempre diz se o valor vem do modelo ou do servidor.

#
Etapa 5

Bloquear o acesso ao esquema: catálogo do sistema e esquema cruzado

Dois vazamentos distintos ameaçam um mecanismo NL2SQL conectado a um banco de dados real: acesso ao catálogo do sistema Postgres e acesso a um esquema que não pertence ao chamador. Ambos são bloqueados na validação, independentemente de qualquer política de RLS colocada no downstream.

pg_catalog sempre faz parte de search_path, o que significa que um nome não qualificado como pg_authid ou pg_stat_activity o acessa diretamente, sem prefixo. O validador Aurabase bloqueia qualquer nome que comece com pg_, bem como information_schema e o esquema interno aura_console, qualificado ou não.

Em um nome de dois componentes (schema.table), apenas o esquema do projeto chamador é permitido, qualquer outro valor é rejeitado. Um nome com três ou mais componentes é automaticamente recusado. Esse limite no nível da consulta gerada é um acréscimo ao isolamento no nível do banco de dados detalhado em nosso artigo sobreisolamento multilocatário: um impede que o SQL gerado tenha como alvo outro esquema, o outro evita que a própria conexão alcance outro banco de dados. Nenhum substitui o outro.

#
Etapa 6

Nunca deixe o cliente redefinir o esquema consultado

Uma armadilha discreta aguarda qualquer API NL2SQL que aceite um parâmetro que descreve o esquema ou tabelas permitidas na consulta do cliente. Se esse mesmo parâmetro for usado para construir o prompt e validar o SQL de saída, um chamador poderá mentir sobre o que é permitido, e a validação será validada contra essa mentira, e não contra a realidade do banco de dados.

Aurabase examina o esquema base real do projeto em cada chamada, com um cache curto de trinta segundos para desempenho, e rejeita explicitamente (erro 400) qualquer campo schema, allowed_schemaou schema_context enviado no corpo da solicitação, em vez de aceitá-lo e substituí-lo silenciosamente. A diferença importa: um campo aceite e depois ignorado dá a ilusão de um controlo que não existe; um campo recusado diz isso imediatamente.

#
Lista de verificação

Audite seu próprio pipeline NL2SQL antes da produção

Quer você use o Aurabase ou construa seu próprio pipeline com base em um LLM genérico, os pontos a seguir cobrem o que é esquecido com mais frequência.

Se você mesmo escrever o validador

  • Analise SQL com um analisador real para o seu dialeto exato, nunca com correspondência de padrões em uma string.
  • Adote uma rejeição padrão: qualquer tipo de nó, qualquer função não autorizada explicitamente deve ser recusada, não apenas os casos perigosos já identificados.
  • Aceite apenas uma instrução por consulta; esta é a rejeição mais simples contra o empilhamento de consultas.
  • Teste o validador com casos reais de adversário (função proibida em FILTER, em ORDER BY intra-agregado, em OFFSET), não apenas com casos óbvios.
  • Apesar de tudo, execute o SQL validado com uma função Postgres com privilégios reduzidos no esquema esperado: o validador limita a forma da consulta, a função limita o que pode alcançar fisicamente se um caso lhe tiver escapado.

Se você estiver avaliando uma estrutura NL2SQL de terceiros

  • Pergunte explicitamente se a validação é estrutural (AST) ou apenas uma instrução imediata: a resposta muda tudo.
  • Verifique se um limite de linha é aplicado por padrão, e não apenas documentado como uma prática recomendada às suas custas.
  • Verifique se o esquema utilizado para validação pode ser fornecido pelo cliente API, o que reabriria exatamente a falha descrita acima.
  • Compare várias ferramentas neste critério específico antes de escolher: nossa comparação de ferramentas NL2SQL detalha o que distingue as abordagens disponíveis em 2026.
#
Defesa em profundidade

O validador reduz o risco, não substitui o RLS

Um validador AST sólido reduz o risco na origem: o SQL que chega ao seu banco de dados já possui um formato conhecido e limitado. No entanto, ela não substitui as políticas RLS nas suas tabelas confidenciais, que decidem quais linhas um determinado usuário tem o direito de ver. As duas camadas respondem a questões diferentes: o validador limita a forma da consulta gerada, o RLS limita os dados que pode retornar para um usuário específico. Mantenha ambos ativos, mesmo que um pareça redundante com o outro.

NL2SQL cobre questões estruturadas sobre suas tabelas. Para perguntas sobre conteúdo não estruturado, documentos, notas, tickets, o RAG nativo do Aurabase segue uma lógica de segurança comparável, detalhada em nosso tutorial de pipeline RAG em pgvector.

#
Perguntas frequentes

Perguntas frequentes

A engenharia imediata é completamente inútil para proteger o NL2SQL?+
Não, continua útil para direcionar o modelo para SQL correto e relevante na maioria das vezes. Mas não é uma verificação de segurança: uma pergunta ambígua ou manipulada pode contorná-la e uma instrução imediata não bloqueia nada fisicamente. Um validador estrutural após a geração continua sendo necessário, independentemente da qualidade do prompt.
Ainda devemos ativar o RLS se o SQL gerado já estiver validado e restrito ao SELECT?+
Sim. O validador limita a forma da solicitação (sem subconsulta, sem função fora da lista de permissões, LIMIT limitado), e não os direitos comerciais sobre os dados. O RLS continua sendo a camada que decide quais linhas um usuário específico tem o direito de ver, mesmo dentro de um SELECT perfeitamente válido.
Uma lista branca fechada de dez funções não limita muito as possíveis questões?+
Sim, e é voluntário. Ele cobre a maior parte das análises de leitura (contagem, soma, média, mínimo, máximo, inferior, superior, coalescer, data_trunc, agora) e nega todo o resto por padrão, incluindo uma função legítima que ninguém pensou em adicionar ainda. Cada adição deve ser uma decisão explícita e não um descuido numa lista negra.

PRONTO PARA IMPLEMENTAR?

Seu back-end em cinco minutos.

Não é necessário cartão de crédito · 500 MB grátis · 50.000 MAU