Em sistemas que geram SQL, filtros ou código com um modelo de linguagem, existem duas perguntas de autorização, e é fácil responder só a primeira:
- Quem pode pedir a consulta?
- O que a consulta gerada pode tocar?
Autorizar a primeira e ignorar a segunda é trancar a porta e deixar a janela aberta. O usuário tinha permissão para perguntar sobre os dados da unidade dele; o SQL que o modelo escreveu não sabe disso.
Carregue só o que já foi autorizado
A proteção mais forte contra o modelo acessar o que não deve é o dado não estar lá. Em vez de abrir uma engine com todos os contextos e confiar que o modelo escolha o certo, a execução recebe só o que já passou pela autorização:
ctx = authorize_data_context(user, context_id) # predicado do servidor
engine = load_engine_only(ctx.storage_prefix)Se o processo daquela execução não enxerga os outros datasets, nenhum prompt, por mais criativo, faz o modelo chegar neles.
System prompt é sugestão
“Use apenas estas tabelas” no system prompt não é controle. O modelo é um componente probabilístico e vai, eventualmente, fazer outra coisa, seja por erro, seja porque alguém pediu com jeitinho. A saída precisa passar por validação determinística antes de executar:
pergunta
-> contexto autorizado
-> modelo gera candidato
-> parser / AST / validação
-> recusa se inválido
-> auditoria
-> execução em engine escopada
-> respostaO guard mínimo
Para SQL gerado, o mínimo que eu considero aceitável:
- um único statement;
- só leitura, com DDL, DML e comandos administrativos bloqueados;
- nenhuma função de arquivo ou de rede;
- toda relação referenciada dentro da allowlist do contexto;
- limite de linhas, timeout e orçamento de memória;
- conexão read-only, mesmo que o guard falhe.
O último item é o que dá tranquilidade: o guard é uma camada, a conexão read-only é outra e a engine escopada é uma terceira. Nenhuma precisa ser perfeita sozinha.
Regex é andaime
Dá para começar com regex, desde que coberta por testes adversariais. Mas regex é piso temporário, não destino. Comentários, strings, identificadores entre aspas e sintaxe específica de dialeto tornam qualquer regex frágil:
SELECT * FROM "public"."Pacientes" /* ; DROP */
SELECT * FROM report WHERE note = 'FROM secrets'Um parser compatível com o dialeto que vai executar a query transforma a pergunta “isso parece perigoso?” em “quais relações essa árvore referencia?”, e essa segunda pergunta tem resposta exata.
A injeção vem do dado
A pergunta do usuário não é a única entrada não confiável. Nome de coluna, valor de célula, metadado de planilha, texto de PDF ingerido: tudo isso chega ao contexto do modelo e pode conter algo que parece instrução. Delimitar esses elementos como dados ajuda, mas a defesa decisiva continua sendo validar a saída. Se a resposta só pode ser um SELECT sobre relações autorizadas, uma instrução escondida numa planilha não tem para onde ir.
Menos ferramentas, menos superfície
No caminho de consulta sensível, o agente recebe só o necessário. Se a tarefa é leitura analítica, nada de escrita em filesystem, rede arbitrária, shell, Python livre, mutação de banco ou credencial de outro serviço. Menor privilégio vale para agentes do mesmo jeito que vale para pessoas, e com mais razão: o agente lê tudo o que aparece na frente dele.
Isso conversa com Mascare antes de chamar o LLM: uma camada cuida do que o modelo vê, a outra cuida do que ele consegue fazer.