Blog do SQLFix
Língua natural para SQL: verificações de segurança que importam
Traduzir uma pergunta para SQL é genuinamente útil para os primeiros rascunhos, mas a execução automática da saída do modelo contra um banco de dados ao vivo é como um erro de digitação se torna uma tabela excluída. A postura segura é apenas rascunho: o modelo propõe, um humano ou um pipeline protegido decide. O SQLForm segue esse modelo de apenas rascunho e nunca executa o que gera sem uma etapa de revisão.
1. Comece com um pipeline apenas de rascunho
Construí o SQLForm em torno da ideia de que um modelo de linguagem é um assistente de elaboração de rascunhos, e não um motor de execução. Quando um utilizador pede “mostre-me as vendas do mês passado por região”, o modelo devolve uma declaração SELECT. Essa declaração é colocada numa fila de rascunhos e nunca é enviada para o conector de produção. Um operador humano ou um portão automatizado deve explicitamente aprovar o rascunho antes que ele saia da fila. Esta separação cria um registo de auditoria claro: todas as consultas que tocam dados têm um passo de aprovação registado.
A fila de rascunhos é persistida numa tabela leve com colunas para ID de utilizador, carimbo de data e hora, prompt bruto, SQL gerado e estado de aprovação. Se o sistema falhar, a fila pode ser reproduzida e nenhuma consulta é perdida. Escolhi este design após ver equipes perderem dados porque um script de demonstração executou uma declaração DELETE que o modelo pensou que era uma declaração SELECT. A fila impede essa classe de erro por construção.
2. Detectar declarações mutantes antes que elas atinjam a fila
A primeira linha de defesa é uma varredura lexical simples. Procuro pelos tokens DELETE, UPDATE, INSERT, DROP, TRUNCATE, ALTER, GRANT, REVOKE e CREATE. Se algum deles aparecer, o rascunho é marcado como mutante e colocado em um estado de espera que requer aprovação explícita de um revisor sénior. Mesmo um SELECT que contenha uma cláusula WITH que chama uma função mutante é detectado porque a varredura é realizada na string SQL bruta antes de qualquer análise.
Também verifico os separadores de declarações. Um ponto e vírgula seguido por outro token significa que o utilizador pode estar a tentar encadear operações. Por exemplo, “SELECT * FROM users; DELETE FROM logs” passaria uma verificação ingênua de apenas SELECT, mas é bloqueado porque o scanner vê o token DELETE após o primeiro ponto e vírgula.
Para evitar falsos positivos em nomes de objetos legítimos (como uma coluna nomeada “delete_flag”), o scanner só trata os tokens como palavras-chave quando eles são seguidos por espaço em branco ou um parêntese, correspondendo à gramática SQL típica. Isso mantém a regra precisa sem banir identificadores úteis.
3. Executar esboços em uma sandbox somente leitura
Uma vez que um esboço é aprovado, eu o executo contra uma réplica somente leitura do esquema de produção. A réplica é atualizada diariamente a partir do primário e não tem privilégios de escrita atribuídos à função de conexão utilizada pelo SQLForm. Se o modelo produzir alguma vez uma instrução de mutação, o banco de dados retornará um erro e nenhum dado será alterado. Isso transforma um erro potencialmente caro em uma falha visível que o revisor pode ver imediatamente.
Eu também impeço a propriedade somente leitura ao nível da conexão definindo o parâmetro do PostgreSQL default_transaction_read_only = on para a sessão. No MySQL, eu inicio a sessão com SET SESSION TRANSACTION ISOLATION LEVEL READ ONLY;. Essas configurações fazem com que o servidor rejeite qualquer comando WRITE, ALTER ou DROP com um código de erro claro, que eu exibo na interface do usuário.
Usar uma réplica tem um benefício adicional: a consulta pode ser executada em dados ligeiramente desatualizados sem afetar os SLAs, e eu posso monitorar o comprimento da consulta e o uso de recursos sem arriscar a carga de produção. Se a réplica atrasar além de um limiar configurável, o sistema mantém o esboço e notifica o aprovador de que os dados podem estar desatualizados.
4. Parametrizar consultas aprovadas
Depois de um rascunho passar pela etapa de revisão humana, mudo o SQL para a camada de aplicação como uma instrução preparada. O texto gerado pelo modelo torna-se uma template onde quaisquer valores fornecidos pelo utilizador são vinculados como parâmetros. Nunca concateno strings cruas, mesmo após a aprovação, porque isso reintroduz o risco de injeção que o rascunho pretendia evitar.
Sigo as orientações do OWASP SQL Injection Prevention Cheat Sheet, que lista as instruções preparadas como a principal defesa. Na prática, utilizo a API nativa do controlador de base de dados: pg_prepare / pg_execute para PostgreSQL, e PreparedStatement para MySQL. O controlador cuida da fuga e da segurança de tipo, por isso um valor que contenha uma aspa ou um ponto e vírgula é tratado como dados, não como SQL.
Para garantir que a template permaneça válida, analiso o SQL aprovado com um analisador SQL leve (como sqlparse) e verifico que cada espaço reservado corresponda a um índice de parâmetro. Se o analisador encontrar um token solto que pareça um valor, mas não esteja vinculado, o rascunho é devolvido para revisão.
5. Revisão humana como ponto de controlo
O revisor não é esperado ser um especialista em SQL; a lista de verificação é deliberadamente curta. Primeiro, verificam se a lista SELECT corresponde às colunas mencionadas no pedido original. Se o utilizador pediu “receita total por região” e a consulta devolve COUNT(*), o revisor sinaliza-o. Em segundo lugar, confirmam que nenhum token de mutação aparece, confiando no scanner, mas também fazendo uma varredura visual para qualquer obfuscção astuta. Em terceiro lugar, lançam um olhar à cláusula WHERE para ver se acidentalmente extrai mais linhas do que o pretendido (por exemplo, faltando um filtro de data que causaria uma varredura completa da tabela).
Trato a consulta gerada como uma sugestão, exatamente como eu faria uma revisão do SQL escrito por um novo colega de equipa. O revisor pode aprovar, solicitar uma alteração ou rejeitar. Se solicitarem uma alteração, podem editar o rascunho diretamente na interface do utilizador; a versão editada torna-se um novo rascunho e passa pelos mesmos controlos novamente. Este ciclo garante que a saída do modelo nunca é a autoridade final.
O ganho de velocidade vem da eliminação da necessidade de escrever SQL de base a partir do zero. Vi equipes reduzir o tempo para produzir uma consulta de rascunho de dez minutos para menos de dois minutos, mantendo ainda um humano no loop para segurança.
O que isto não é
Não afirmo que o SQLForm deteta todos os possíveis erros. O scanner léxico pode ser evitado por codificações invulgares ou utilizando sintaxe específica do banco de dados que se assemelha a um comentário. Um atacante determinado pode criar uma solicitação que faz com que o modelo produza uma declaração que utiliza um procedimento armazenado com efeitos colaterais, que o scanner não sinalizaria porque o nome do procedimento não está na nossa lista de palavras-chave. A réplica somente leitura protege contra escritas acidentais, mas se a réplica estiver mal configurada e herdar direitos de escrita, a rede de segurança desaparece.
A revisão humana é essencial, mas os revisores podem perder erros lógicos sutis, especialmente quando a consulta é longa ou utiliza funções de janela complexas. O processo reduz o risco, mas não o elimina. Trato o SQLForm como uma ferramenta de redução de risco, e não como uma bala de prata.
Referências
https://cheatsheetseries.owasp.org/cheatsheets/LLM_Prompt_Injection_Prevention_Cheat_Sheet.html
https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
https://cwe.mitre.org/data/definitions/798.html
https://cwe.mitre.org/data/definitions/89
Perguntas Frequentes
Apenas contra uma réplica somente leitura ou com um portão rígido que bloqueia declarações de mutação. A execução automática de SQL gerado contra produção é como um erro de rascunho se torna perda de dados. Mantenha o modelo em um papel de proposta apenas.
Um rascunho de modelo é uma entrada não confiável até ser revisado, da mesma classe que a injeção de SQL CWE-89. Um papel somente leitura significa que mesmo uma execução acidental não pode alterar os dados de produção.
Não. Uma vez que uma consulta se move para o código da aplicação, use declarações preparadas. Concatenar valores de volta a uma string reintroduz o risco de injeção. Revisão e parametrização são controles separados.
As declarações DELETE, UPDATE, DROP, TRUNCATE e de concessão de privilégios nunca devem ser executadas em um caminho de execução automática. Elas exigem aprovação explícita e separada.
Experimente o produto: SQLForm
2026-09-16 · fontes primárias apenas · sem métricas de tráfego fabricadas
Artigos relacionados
Consultas parametrizadas vs SQL gerado
Explore a matriz completa de tópicos de micro-SaaS de IA →
O NL2SQL pode ser seguro para execução automática?
Apenas contra uma réplica somente leitura ou com um portão duro que bloqueia declarações de mutação. A execução automática de SQL gerado contra produção é como um erro de rascunho se torna perda de dados. Mantenha o modelo em um papel de proposta apenas.
Por que um contexto somente leitura é importante? Um rascunho de modelo é uma entrada não confiável até ser revisado, da mesma classe que a injeção de SQL CWE-89. Um papel somente leitura significa que mesmo uma execução acidental não pode alterar os dados de produção. Rever o SQL significa que posso ignorar a parametrização?
Um rascunho de modelo é uma entrada não confiável até ser revisado, da mesma classe que a injeção de SQL CWE-89. Um papel somente leitura significa que mesmo uma execução acidental não pode alterar os dados de produção.
Quais declarações um camada de segurança deve bloquear por padrão?
As declarações DELETE, UPDATE, DROP, TRUNCATE e de concessão de privilégios nunca devem ser executadas em um caminho de execução automática. Elas requerem aprovação explícita e separada.
Apenas para suporte à decisão. Não é um substituto para a revisão do DBA ou testes de carga de produção. Sem garantias de desempenho.