Diagnose this API log showing a SQL syntax error raised from a query parameter
An alert fires on a spike of HTTP 500s from one endpoint. This is the log record behind it.
Constraints:
refis a customer-supplied query-string parameter- the same 500 body is what the client received
- the team's first instinct is to add input filtering at the edge
2026-03-04T11:22:09Z ERROR api.orders psycopg2.errors.SyntaxError:
syntax error at or near "'"
LINE 1: ...FROM orders WHERE customer_ref = 'AC-1042'' ORDER BY created_at
request_id=8f1e path=/api/orders query=ref=AC-1042%27 status=500
response_body=stack trace + failing SQL statement returned to client
Diagnose the cause and say what must be fixed.
The parameter's quote reached the SQL parser and closed the literal early, which proves the query is concatenated — this is SQL injection, not bad input. Fix both: bind ref as a parameter and stop returning the driver error to the client.
- ✗Reading the syntax error as malformed input rather than proof of concatenation
- ✗Fixing only the leaked stack trace and leaving the query concatenated
- ✗Treating edge filtering of the parameter as the root-cause remediation
- →Why does suppressing the error make the flaw harder to find but no safer?
- →What would you search the codebase for to find sibling queries built the same way?
What the log says
syntax error at or near "'" together with customer_ref = 'AC-1042'' means the quote from ref (%27) reached the query text and closed the string literal early. A parameterized query cannot fail this way — there the value never takes part in parsing. So the statement is built by concatenation and the endpoint is injectable.
The second defect is in response_body: the stack trace and the SQL itself go to the client, disclosing the schema and confirming the injection with no guesswork.
What to fix
# before — the value lands inside the query text
cur.execute(f"SELECT * FROM orders WHERE customer_ref = '{ref}' ORDER BY created_at")
# after — the value is bound, the parser sees only a placeholder
cur.execute(
"SELECT * FROM orders WHERE customer_ref = %s ORDER BY created_at",
(ref,),
)
Plus the error handler returns a generic 500 with a request_id and logs the detail only. Filtering ref at the edge is a useful extra layer, but not the root-cause fix.