Prevent SQL Injection in Django Raw Queries
Flags Django raw(), extra(), RawSQL, and cursor.execute() calls that build SQL with f-strings, % formatting, or .format() instead of params.
What it flags
from django.contrib.auth.decorators import login_required
from django.db import connection
from django.http import JsonResponse
@login_required
def search_customers(request):
term = request.GET.get("q", "")
sort = request.GET.get("sort", "name")
with connection.cursor() as cursor:
# Request values are pasted straight into the SQL string
cursor.execute(
"SELECT id, name, email FROM crm_customer "
f"WHERE name ILIKE '%{term}%' ORDER BY {sort} LIMIT 50"
)
columns = [col[0] for col in cursor.description]
rows = [dict(zip(columns, row)) for row in cursor.fetchall()]
return JsonResponse({"results": rows})from django.contrib.auth.decorators import login_required
from django.db import connection
from django.http import JsonResponse
SORT_OPTIONS = {"name": "name", "newest": "created_at DESC"}
@login_required
def search_customers(request):
term = request.GET.get("q", "")
order_by = SORT_OPTIONS.get(request.GET.get("sort"), "name")
with connection.cursor() as cursor:
cursor.execute(
"SELECT id, name, email FROM crm_customer "
f"WHERE name ILIKE %s ORDER BY {order_by} LIMIT 50",
[f"%{term}%"],
)
columns = [col[0] for col in cursor.description]
rows = [dict(zip(columns, row)) for row in cursor.fetchall()]
return JsonResponse({"results": rows})Why it matters
The Django ORM parameterizes the queries it builds, so injection bugs collect in the few places that drop to SQL: raw(), extra(), RawSQL, and connection.cursor(). An f-string and a params list return the same rows in tests with ordinary input, so the unsafe version passes review unless someone reads the SQL string closely. Identifiers such as ORDER BY columns cannot be bound as parameters at all, which makes sort and filter options a common entry point.
Agent instructions
cubic checks every pull request against these instructions. Paste them into AGENTS.md or Cursor rules and your AI coding tool follows them while it writes code.
AGENTS.md
Add this section to AGENTS.md at the repository root. Codex, Cursor, and other AI coding tools read it.
## Prevent SQL Injection in Django Raw Queries Applies to `**/*.py`. Review Django code that runs SQL outside the ORM's query builder for values interpolated into the SQL string. Flag changes that: - Build the SQL passed to `Model.objects.raw()`, `RawSQL()`, `cursor.execute()`, or `cursor.executemany()` with f-strings, `%` formatting, `.format()`, or concatenation of variable values instead of the `params` argument. - Pass interpolated strings to `extra(where=..., select=..., tables=...)` instead of `params` and `select_params`, or add new `extra()` calls, which Django's documentation calls a last resort it aims to deprecate. - Wrap placeholders in quotes (`'%s'`); the driver quotes parameter values itself, and Django's documentation warns against it. - Insert table names, column names, or `ORDER BY` clauses from request data without mapping them through a fixed allowlist, since identifiers cannot be bound as parameters. - Format `LIKE` wildcards or `IN` lists into the SQL instead of putting wildcards in the parameter value and generating one `%s` per list item. - Pass request data as field names or lookup keys to `filter()`, `exclude()`, `order_by()`, or `values()`, such as `filter(**request.GET.dict())`, which lets callers query any field or relation, including password hashes on related users. Do not flag: - Placeholder lists built from a count, such as `", ".join(["%s"] * len(ids))`, with the values passed in `params`. - Constant SQL fragments or identifiers interpolated from code-defined constants or an allowlist. - `RunSQL` operations in migrations that contain only static SQL.
Checks files matching **/*.py.
More Django agents
See all Django code review standards
Run this agent on every pull request
cubic reviews every pull request against the agents you turn on and comments on the lines that break them.