SOQL injection vulnerability — Database.query with concatenated user input
Code that builds a SOQL query by concatenating user input is vulnerable to SOQL injection — a hostile user can extend the query to read or modify data they shouldn't. Apex's `String.escapeSingleQuotes` or bind variables solve this; some scanners flag this as a defect even before exploitation.
Also seen asSOQL injection·Database.query injection·string concatenation SOQL·concatenated user input
SOQL injection is the same family of vulnerability as SQL injection. The shape is identical: user input gets concatenated into a query string, an attacker types ' OR Id != null -- (or similar), and the resulting query returns far more than intended.
The vulnerable pattern
String name = ApexPages.currentPage().getParameters().get('name');
String soql = 'SELECT Id, Email FROM Contact WHERE Name = \'' + name + '\'';
List<Contact> hits = Database.query(soql);
If name is ' OR Email != null --, the resulting query is:
SELECT Id, Email FROM Contact WHERE Name = '' OR Email != null --'
Which returns every Contact in the org. An attacker can extract all email addresses through one cleverly-crafted parameter.
Fix 1: use bind variables (preferred)
Apex bind variables (:variable) are not concatenated — the platform escapes them automatically:
String name = ApexPages.currentPage().getParameters().get('name');
List<Contact> hits = [SELECT Id, Email FROM Contact WHERE Name = :name];
This is safe regardless of name's contents. Use bind variables anywhere you can.
Fix 2: escape single quotes (when bind isn't available)
For dynamic queries where the column or table name varies (and you can't use bind), escape:
String objectType = inputObjectType; // e.g., 'Account' from a config
String name = String.escapeSingleQuotes(ApexPages.currentPage().getParameters().get('name'));
String soql = 'SELECT Id FROM ' + objectType + ' WHERE Name = \'' + name + '\'';
List<SObject> hits = Database.query(soql);
String.escapeSingleQuotes doubles up apostrophes so the user's input becomes a literal string, not query syntax. Apply it to every user-controlled string that goes into a query.
Fix 3: validate objectType against a whitelist
If the object name itself is dynamic, validate it:
Set<String> allowed = new Set<String>{'Account', 'Contact', 'Lead'};
if (!allowed.contains(objectType)) {
throw new IllegalArgumentException('Object not allowed: ' + objectType);
}
Don't trust Schema.getGlobalDescribe().containsKey(objectType) for security — that returns true for any object the running user can see, including ones you don't want to query through this code path.
Tooling
Run PMD Apex or Salesforce Code Analyzer in CI. Both flag string-concatenated SOQL with high confidence. CI gates that fail on SOQL injection finds catch this before it ships.
A common false sense of security
// ❌ Still vulnerable
String soql = 'SELECT Id FROM Contact WHERE Name LIKE \'%' + name + '%\'';
Wrapping in % doesn't escape; you still need String.escapeSingleQuotes. Bind variables also work with LIKE:
// ✅ Safe
String pattern = '%' + name + '%';
List<Contact> hits = [SELECT Id FROM Contact WHERE Name LIKE :pattern];
The % is added to the variable, not the query template — bind handles the rest.
