Em algum momento você precisa montar a query em runtime: filtros que o usuário escolhe, campos descobertos dinamicamente, relatórios configuráveis. O problema é que concatenar string para montar SOQL abre a porta para injection. Dá para ter o dinamismo sem o risco. Este guia mostra as quatro defesas que eu aplico.
O risco: concatenar entrada do usuário na query
O padrão perigoso é este. O valor vem do cliente e entra direto na string da query:
// NÃO faça isso
String q = 'SELECT Id FROM Account WHERE Name = \'' + entrada + '\'';
Database.query(q); // entrada = "x' OR Name != '" quebra a intenção da query
Defesa 1: valores sempre por bind variables
Nunca coloque o valor na string. Passe por bind, com Database.queryWithBinds. O
valor é tratado como dado, nunca como parte do código da query.
Map<String, Object> binds = new Map<String, Object>{ 'nome' => entrada };
Database.queryWithBinds(
'SELECT Id FROM Account WHERE Name = :nome',
binds, AccessLevel.USER_MODE);
Defesa 2: nomes de campo só entram se estiverem numa whitelist
Bind resolve valores, mas não resolve nome de campo ou de objeto vindos do cliente (para
montar filtros dinâmicos, por exemplo). A regra: o nome só entra na query se existir numa whitelist gerada
pelo próprio schema, via Schema.describe. Nome desconhecido lança exceção, nunca vai para a
query.
Map<String, Schema.SObjectField> campos =
Account.SObjectType.getDescribe().fields.getMap();
if (!campos.containsKey(campoDoCliente.toLowerCase())) {
throw new AuraHandledException('Campo inválido');
}
// só agora campoDoCliente pode entrar na string da query
Defesa 3: literais que não aceitam bind passam por escape
Alguns trechos (como valores dentro de INCLUDES em multipicklist) não aceitam bind. Nesses
casos, aplique String.escapeSingleQuotes no literal antes de concatenar.
String literal = String.escapeSingleQuotes(valor);
Defesa 4: a query final roda em USER_MODE
Rodar com WITH USER_MODE (ou AccessLevel.USER_MODE) faz o Salesforce respeitar o
CRUD e o FLS do usuário de ponta a ponta. Mesmo que a lógica esteja correta, o usuário nunca vê campo ou
registro que ele não teria permissão de ver. Esse mesmo modo cobre os
três níveis de acesso que todo código Apex precisa
respeitar, não só na query dinâmica. Segurança em profundidade. Na
Summer '26 esse USER_MODE vira o padrão do
Apex na API 67, então forçá-lo hoje é se adiantar ao que a plataforma já está tornando obrigatório.
O teste que prova que está seguro
Toda camada de SOQL dinâmico deveria ter um teste que tenta injetar e verifica que a
tentativa é rejeitada. Se um nome de campo malicioso como Name FROM Account-- for aceito, o
teste falha. É assim que você garante que a whitelist realmente protege.
Precisa de filtros dinâmicos ou relatórios configuráveis com segurança?
Eu construo camadas de consulta dinâmica à prova de injection, com describe, bind e USER_MODE, tudo testado inclusive contra tentativa de injection. Comece com um diagnóstico gratuito de 45 minutos.
Falar no WhatsApp Ver serviços