SOQL Injection is when user input gets concatenated into a SOQL query, allowing the user to manipulate the query structure. Like SQL injection but for Salesforce.
Vulnerable code:
apex public List<Account> findByIndustry(String userInput) { String query = 'SELECT Id, Name FROM Account WHERE Industry = \'' + userInput + '\''; return Database.query(query); }
If userInput is ' OR Name LIKE '% — the query becomes WHERE Industry = '' OR Name LIKE '%' — returns all accounts.
Safe patterns:
1. Bind variables (always preferred):
apex public List<Account> findByIndustry(String userInput) { return [SELECT Id, Name FROM Account WHERE Industry = :userInput]; }
Salesforce parameterises bind variables; injection is impossible.
2. Bind variables in dynamic SOQL:
apex String safeIndustry = userInput; // bound below String query = 'SELECT Id FROM Account WHERE Industry = :safeIndustry'; List<Account> accs = Database.query(query);
The : prefix in dynamic SOQL is parameterised the same way.
3. `String.escapeSingleQuotes` for cases where bind variables can't work:
apex String escaped = String.escapeSingleQuotes(userInput); String query = 'SELECT Id FROM Account WHERE Name LIKE \'%' + escaped + '%\'';
Escapes single quotes within the string. Less robust than bind variables; use only for legitimate cases like wildcard concatenation.
4. Whitelist for object/field names (which can't be bind-variable):
apex Set<String> allowedObjects = new Set<String>{'Account', 'Contact', 'Opportunity'}; if (!allowedObjects.contains(userInput)) throw new IllegalArgumentException(); String query = 'SELECT Id FROM ' + userInput;
5. `Schema.SObjectType.validate(input)` patterns — validate against the org's known objects and fields before composing queries.
Rules of thumb:
- Never concatenate raw user input into a SOQL query string.
- Bind variables for values; whitelist for identifiers (object/field names).
- Don't trust client-side validation — validate again in Apex.
- Audit `Database.query` and `Database.queryWithBinds` usages — these are the injection surfaces.
Beyond SOQL: similar principles apply to dynamic SOSL, dynamic DML, and dynamic Visualforce/LWC component instantiation. Anywhere user input meets a runtime parser, sanitisation matters.
Tooling: PMD has SOQL-injection detection rules. Run static analysis as part of CI.
