Interactive Playground
Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.
pathfinder scan --ruleset python/PYTHON-DJANGO-SEC-006 --project .About This Rule
Understanding the vulnerability and how it is detected
This rule detects SQL injection vulnerabilities in Django applications where user input from HTTP request parameters is used to construct SQL strings via f-strings, string concatenation, or % formatting before being passed to a database execution function.
This rule complements the specific sink rules (SEC-001 through SEC-004) by catching the pattern where the SQL construction and execution are in separate functions or files. A common pattern is a view that builds a query string from request parameters and passes it to a database utility function which calls cursor.execute() or raw(). The taint flows through the string construction step and reaches the sink in a different code location.
The rule specifically tracks the construction of SQL strings using user-controlled data, making it effective at catching patterns like: sql = f"SELECT * FROM orders WHERE user_id = {user_id}" or: sql = "SELECT * FROM users WHERE name = '" + username + "'"
Security Implications
Potential attack scenarios if this vulnerability is exploited
Multi-Hop Injection via String Building
The most dangerous injection patterns involve SQL strings built incrementally across multiple function calls. User input may be appended to a query string in one function, the partially-built string passed to another, and the final string executed in a third. Taint analysis across these hops catches what per-function review would miss.
F-String SQL Construction
Python f-strings are concise and natural to write, making them a common pitfall for SQL construction. A query like f"SELECT * FROM t WHERE id = {user_id}" looks harmless but is a direct injection vector. Any value of user_id that contains SQL metacharacters will alter the query structure.
Conditional SQL Building Patterns
A common pattern is building WHERE clause conditions dynamically based on which filters a user requests. Each condition appended with concatenation is a separate injection point. This pattern requires careful use of Django's Q() objects or parameterized query fragments instead.
Logging and Debugging SQL as a Secondary Path
Developers often construct SQL strings for logging or debugging purposes without intending to execute them, but these strings sometimes end up being executed in error handling paths or when debug logging is disabled. This rule catches construction regardless of the intended use.
How to Fix
Recommended remediation steps
- 1Never use f-strings, % formatting, or string concatenation to embed user input into SQL strings; use parameterized query placeholders (%s) with a separate values list.
- 2Refactor dynamic SQL building patterns to use Django's ORM Q() objects, filter chaining, and annotate() for type-safe dynamic query construction.
- 3When raw SQL is unavoidable, build the SQL string as a static template with %s placeholders and keep all dynamic values in a separate list passed to execute().
- 4Perform code review specifically looking for SQL string construction that touches request parameters, even when the execution happens in a different function.
- 5Use Django Debug Toolbar in development to inspect generated SQL and verify parameterization is working correctly.
Detection Scope
How Code Pathfinder analyzes your code for this vulnerability
This rule performs inter-procedural taint analysis with global scope. Sources include calls("request.GET.get"), calls("request.POST.get"), calls("request.GET.__getitem__"), calls("request.POST.__getitem__"), calls("request.body"), and calls("request.read"). The rule tracks taint through string construction operations (f-strings, concatenation, % formatting) and follows the resulting tainted string to SQL execution sinks: calls("*.execute"), calls("*.raw"), calls("*.extra"), and calls("RawSQL"). The .tracks(0) setting ensures only the SQL string argument (not the params argument) of these sinks is considered. Sanitizers include int(), float(), and shlex.quote() for string values.
Compliance & Standards
Industry frameworks and regulations that require detection of this vulnerability
References
External resources and documentation
Similar Rules
Explore related security rules for Python
Django SQL Injection via cursor.execute()
User input flows to cursor.execute() without parameterization, enabling SQL injection attacks.
Django SQL Injection via QuerySet.raw()
User input flows to QuerySet.raw() without parameterization, enabling SQL injection through Django's ORM raw query interface.
Django SQL Injection via QuerySet.extra()
User input flows to QuerySet.extra() without parameterization, enabling SQL injection through Django's legacy ORM extension interface.
Frequently Asked Questions
Common questions about Django Tainted SQL String Construction
New feature
Get these findings posted directly on your GitHub pull requests
The Django Tainted SQL String Construction rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.