Intermediate 30 min read

Dynamic SOQL and Injection Risk

By the end of this lesson, you'll be able to:

  • Build a query as a String and execute it with Database.query()
  • Explain what SOQL injection is and how it happens
  • Use String.escapeSingleQuotes() to defend against it when bind variables aren't possible

Prerequisites: "Bind Variables"

Building and running a dynamic query

String fieldName = 'Industry';
String queryString = 'SELECT Id, Name FROM Account WHERE ' + fieldName + ' = \'Technology\'';

List<Account> accounts = Database.query(queryString);
  1. String fieldName = 'Industry'; — the field name itself isn't known until the code runs.
  2. String queryString = 'SELECT Id, Name FROM Account WHERE ' + fieldName + ' = \'Technology\''; — builds the query text as a plain String.
  3. (blank line)
  4. List<Account> accounts = Database.query(queryString); — Database.query(someString) executes a query built as a String at runtime — necessary when a fixed inline query (every example so far) can't express what's needed.

What SOQL injection looks like

// DANGEROUS: directly concatenating raw user input into query text
String userInput = "'; DELETE Account WHERE Id != '000000000000000AAA"; // a malicious example
String queryString = 'SELECT Id FROM Account WHERE Name = \'' + userInput + '\'';
  1. // DANGEROUS: directly concatenating raw user input into query text — the label says it all.
  2. String userInput = "'; DELETE Account WHERE Id != '000000000000000AAA"; — a malicious example of what a user could type into a form field.
  3. String queryString = 'SELECT Id FROM Account WHERE Name = \'' + userInput + '\''; — concatenates it straight into the query text, unescaped.

Warning: If userInput comes directly from user input and reaches line 3 unescaped, a carefully crafted value can alter the query's actual structure — this is SOQL injection, the same category of vulnerability as SQL injection in traditional web applications, and it can expose or corrupt data the attacker should never have touched.

The defense: bind variables first, escaping second

// BEST: bind variables work in dynamic SOQL too
String targetName = userSuppliedName;
List<Account> accounts = Database.query('SELECT Id FROM Account WHERE Name = :targetName');

// If you truly must concatenate raw text, escape it first
String safeInput = String.escapeSingleQuotes(userSuppliedName);
String queryString = 'SELECT Id FROM Account WHERE Name = \'' + safeInput + '\'';
  1. // BEST: bind variables work in dynamic SOQL too — the preferred defense.
  2. String targetName = userSuppliedName; — the raw value.
  3. List<Account> accounts = Database.query('SELECT Id FROM Account WHERE Name = :targetName'); — the bind variable (previous lesson) works inside a dynamic SOQL string too — never concatenates raw text into the query.
  4. (blank line)
  5. // If you truly must concatenate raw text, escape it first — the fallback, for the rare case a bind variable genuinely can't be used.
  6. String safeInput = String.escapeSingleQuotes(userSuppliedName); — String.escapeSingleQuotes() neutralizes quote characters that could otherwise break out of the intended query structure.
  7. String queryString = 'SELECT Id FROM Account WHERE Name = \'' + safeInput + '\''; — now safe to concatenate.

Exercise

As a comment, explain why concatenating raw user input directly into a dynamic SOQL string is dangerous, and name the safer alternative.

Show hint

Think about what a malicious input could do to the query's structure.

APEX

Dynamic SOQL and Injection Risk Quiz

1. What is SOQL injection?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

Dynamic SOQL builds a query as a String at runtime for cases a fixed query can't handle — but building that String carelessly from user input opens the door to SOQL injection.