flawopen.com/SQL Injection/Numeric IDs
Why is string formatting dangerous even for numeric IDs?
The risk was never about the value's real-world meaning — it's about the fact that the query is built by gluing text together at all. "This is always a number" is an assumption about the data, not a guarantee enforced by the code.
Where the assumption breaks
A URL query parameter or form field is text on the wire, regardless of what the database column behind it expects. Nothing stops an attacker from sending id=1 OR 1=1 instead of a number — the web framework doesn't reject it before your code sees it unless you explicitly validate the type.
Code that's safe today because the ID genuinely always comes from a trusted internal source can become unsafe the moment a new caller passes a user-supplied value into the same function — the vulnerability was latent the whole time, waiting for that change.
In dynamically typed languages especially, a value that looks numeric when logged or debugged can still be a string containing extra characters — the type you expect and the type you actually have aren't guaranteed to match without explicit validation.
Parameterize the query, even after type validation
Validate an ID against the application’s expected format, then pass it through the database driver’s parameter API. Validation protects the input contract; parameterization keeps the input from being interpreted as SQL syntax. Driver behavior on the wire can vary, so do not assume every driver transmits values the same way. PostgreSQL parameters cannot represent table or column identifiers; select dynamic query structure from a fixed application allowlist.
Bind the numeric value; validate its meaning
Validate an ID as a positive safe integer when the application requires that type, but still bind it as a query value. Validation protects the application’s input contract; parameterization keeps input out of SQL syntax. PostgreSQL parameters cannot stand in for table or column identifiers, so choose any dynamic query structure from a fixed application allowlist.
const rawId = new URL(request.url).searchParams.get("id");
const result = await pool.query(
`SELECT id FROM users WHERE id = ${rawId}`
);
const rawId = new URL(request.url).searchParams.get("id");
if (rawId === null || !/^[0-9]+$/.test(rawId)) {
return new Response("Invalid id", { status: 400 });
}
const id = Number(rawId);
if (!Number.isSafeInteger(id) || id < 1) {
return new Response("Invalid id", { status: 400 });
}
const result = await pool.query(
"SELECT id FROM users WHERE id = $1",
Array.of(id)
);
References
- flawopen.com — SQL Injection (all languages)
- What is a parameterized query?
- node-postgres — Parameterized query API