Formatted SQL Query Passed to cursor.execute()

HIGH

SQL query built with string formatting detected. String-formatted SQL strings are a common SQL injection vector. Use parameterized queries.

Rule Information

Language
Python
Category
Python Core
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythonsql-injectionstring-formattingcursor-executedatabaseCWE-89OWASP-A03
CWE References

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-LANG-SEC-084 --project .
1
2
3
4
5
6
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

About This Rule

Understanding the vulnerability and how it is detected

This rule detects the general pattern of SQL queries constructed using string formatting operations (f-strings, % operator, .format(), or string concatenation with +) and passed to any database cursor.execute() method, regardless of the specific database driver being used.

This covers drivers not addressed by more specific rules, including sqlite3, MySQL Connector, PyMySQL, cx_Oracle, and any custom database abstraction layer that exposes a cursor.execute() interface.

String-formatted SQL is the root cause of SQL injection vulnerabilities across all database drivers. The pattern is always the same: untrusted data embedded in the SQL string before it reaches the database engine, where the database interprets the embedded data as SQL syntax.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Universal SQL Injection Risk

SQL injection via string-formatted queries is the most widespread database vulnerability class. Every database engine (PostgreSQL, MySQL, SQLite, Oracle, MSSQL) is affected when query strings are constructed from user input rather than using parameterized queries.

2

SQLite Local Database Exposure

Python applications using sqlite3 with string-formatted queries are vulnerable. Local SQLite databases may contain sensitive application state, user data, and credentials that can be exfiltrated or corrupted via SQL injection.

3

ORM Bypass via Raw SQL

Applications using ORMs often include escape hatches for raw SQL (Django's connection.cursor().execute(), SQLAlchemy's text()). String-formatted raw SQL in these contexts bypasses the ORM's parameterization protections.

4

Blind SQL Injection via Timing

Even when query results are not directly returned to the user (e.g., in background workers or event processors), time-based blind SQL injection using SLEEP() or pg_sleep() allows an attacker to extract data one bit at a time.

How to Fix

Recommended remediation steps

  • 1Use the placeholder syntax appropriate to your database driver: ? for sqlite3, %s for psycopg2/MySQL/pg8000, $1/$2 for asyncpg, :name for SQLAlchemy text().
  • 2Never construct SQL strings using f-strings, % operator, .format(), or string concatenation with user-controlled values.
  • 3For ORMs, use the ORM's query building API rather than raw SQL; fall back to text() with explicit bindparams when raw SQL is needed.
  • 4Validate and restrict dynamic SQL elements (ORDER BY directions, column names) to allowlists when they cannot be parameterized.
  • 5Enable database-level audit logging to detect and alert on unusual query patterns that may indicate exploitation attempts.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule detects cursor.execute() and cursor.executemany() calls where the SQL string argument is built using string formatting operations. It covers any cursor-like object regardless of the specific database driver, serving as a catch-all for drivers not covered by more specific rules.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

CWE Top 25
CWE-89 ranked #3 in 2023 Most Dangerous Software Weaknesses
OWASP Top 10
A03:2021 - Injection
PCI DSS v4.0
Requirement 6.2.4 - Protect web-facing applications against SQL injection
NIST SP 800-53
SI-10: Information Input Validation

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Formatted SQL Query Passed to cursor.execute()

sqlite3: ? (question mark). psycopg2, aiopg, pg8000, MySQL Connector, PyMySQL: %s. asyncpg: $1, $2, $3 (numbered). cx_Oracle: :name (named) or :1, :2 (positional). SQLAlchemy text(): :param_name. ODBC drivers (pyodbc): ? (question mark). Always consult your driver's documentation; the concept is the same but syntax varies.
String formatting is acceptable for static, developer-controlled values that are not user-influenced. For example, table names or column names from a hardcoded allowlist. However, once any user-controlled value could influence the SQL string, even indirectly, parameterization is required for that value.
Yes. SQLAlchemy's text() function accepts raw SQL strings. If the string passed to text() is formatted with user input instead of using SQLAlchemy's bindparam() or the :name syntax, it is flagged. SQLAlchemy's ORM query methods are safe by default and are not flagged.
Calling stored procedures with parameterized arguments is safe. However, if a stored procedure uses EXEC sp_executesql or dynamic SQL internally with concatenated parameters, the injection risk exists inside the stored procedure. Audit stored procedure code for dynamic SQL with parameter concatenation.
For LIKE queries, include the wildcard characters in the Python string before parameterization: cursor.execute("SELECT * FROM t WHERE name LIKE %s", (f"%{user_input}%",)). Do not include % in the SQL template. The database driver handles escaping of the user input including any % or _ characters that would be interpreted as LIKE wildcards.
Code Pathfinder's taint analysis tracks user input from all sources through string operations to cursor.execute() calls. Combined with the database-specific rules (PYTHON-LANG-SEC-080 through 083), this rule provides broad coverage of SQL injection patterns across all Python database drivers.

New feature

Get these findings posted directly on your GitHub pull requests

The Formatted SQL Query Passed to cursor.execute() rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works