Modern frameworks parameterize ordinary queries for you, so test where that protection ends: raw query helpers, dynamic sort and filter fields, process calls and template rendering. List those sinks, write tests that send syntax characters through each, and assert that the query's meaning and scope do not change.
Why is a framework not the end of the story?
An ORM or query builder separates values from SQL when you use its normal methods. OWASP's SQL Injection Prevention Cheat Sheet calls parameterized queries the primary defense, and frameworks make that the easy path. The remaining bugs sit in code that steps off the path, because the framework cannot protect a string you assembled yourself.
The same cheat sheet explains one common step off: parts of a query that cannot use bind variables, such as table names, column names and the sort order indicator. For those it says to use allow-list input validation or redesign the query. A constructed example: a list endpoint accepts ?sort=created and passes the value into a raw ordering clause because the ORM's helper "did not support it". That one line is the whole bug, and no amount of ORM use elsewhere in the app helps.
What is the test plan?
Step 1: find the sinks. Search the codebase for raw query methods, string formatting near SQL keywords, calls that start processes, template rendering of user-supplied template text, and any code that builds filter or search expressions. Write them in a table with the file, the input source and the interpreter. Ten minutes of searching produces the test list.
Step 2: write one test per sink. For each, send inputs that contain the syntax the downstream interpreter cares about. For a database that means quotes, comment markers and a keyword such as OR. For a shell it means separators and substitution characters. The point is not an exotic payload. It is to see whether special characters change structure.
Step 3: assert structure and scope, not just absence of errors. A query that silently returns every row passes a "no exception" test. Create two users with different data, call the endpoint as one, and assert that the response contains none of the other user's records and the same count as a normal request. For ordering, assert that unknown sort values fall back to the default or return a validation error.
Step 4: assert what the framework sees. Many frameworks let you capture the SQL emitted in tests. Assert that the statement text contains placeholders and does not contain the test input. If your test suite can inspect the bound values, assert the input appears there.
Step 5: run it in CI and keep it. These tests are cheap. They catch the day someone swaps a helper for a raw call.
What about dynamic features such as sorting and filtering?
Give users a small vocabulary and keep the real names on the server. A sort parameter becomes one of a handful of keys, mapped to column expressions you wrote. A filter parameter becomes a set of known fields with typed values. If the user's text ever becomes part of the expression, you are doing the allow-list check at the wrong layer.
How should you use scanners in this process?
Static analysis finds many patterns of string-built queries and is good at the sink inventory in step 1. Dynamic testing against a running staging environment finds what the code reading missed, including second order cases where stored data is used later. Neither replaces the tests above, because only you know what scope a request is supposed to have. Test only systems you own or are authorized to test, and use staging data.
What to do
- Build the sink table: raw queries, process calls, template rendering, filter builders.
- Replace string building with the framework's parameter methods wherever they exist.
- Map dynamic sort and filter fields to server-side allow-lists.
- Write one scope-asserting test per sink with two users' data.
- Capture emitted SQL in tests and assert placeholders.
- Run static analysis and an authorized dynamic scan in CI or on a schedule.
- Add a review rule that any new raw call needs a test and a named reviewer.
Safeguard's code scanning and DAST scanning can be part of step 6, and the demo below shows how a scan reports findings.
Quick check
If we use an ORM everywhere, do we still need injection tests?
Yes, for the places where you step outside its normal methods: raw queries, dynamic ordering, process calls and template rendering.
What should an injection test assert?
That the structure and scope of the operation did not change: the right rows, the right count, a validation error for unknown fields, and placeholders in the emitted statement.
Sources
Last reviewed: 18 October 2026.