Safeguard
Security Guides•

Prepared statements stop SQL injection, except for table and column names

Parameterized queries bind values safely but not identifiers like column names or ORDER BY. How to handle dynamic sorting in Java and C# without injection.

Safeguard Security Team
3 min read

Parameterized queries keep user values out of the SQL structure, which stops classic SQL injection. They bind values only. Table names, column names and sort direction cannot be parameters, so those need a fixed allowlist in your code. Treat any dynamic identifier as a design decision.

What a placeholder does

OWASP describes SQL injection as input that changes the structure of a SQL statement. With a placeholder, the statement text and the value travel separately. The database receives a statement with a slot, and the value is bound into the slot.

OWASP's Java example:

String query = "SELECT account_balance FROM user_data WHERE user_name = ? ";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, custname);
ResultSet results = pstmt.executeQuery();

The input is never concatenated into the SQL text. OWASP also notes that parameterization must happen on the server side, since many client libraries build queries by concatenation.

What you cannot bind

Microsoft's Entity Framework Core documentation says databases do not allow parameterizing column names or any other part of the schema. A query like WHERE {propertyName} = {propertyValue} does not work with parameters for the name.

The same applies to ORDER BY columns, sort direction, table names and LIMIT in some databases. These are where injection still shows up in code that otherwise uses prepared statements.

The safe pattern: map to constants

Do not pass the user's string into the query. Map allowed inputs to fixed strings in your code.

String col = switch (sortParam) {
    case "name" -> "name";
    case "created" -> "created_at";
    default -> "id";
};
String dir = "desc".equalsIgnoreCase(dirParam) ? "DESC" : "ASC";
String sql = "SELECT id, name FROM users ORDER BY " + col + " " + dir;

The only text that reaches the SQL comes from your own literals. Anything unexpected falls back to a default.

Think before making it dynamic

The EF Core docs add a design point: accepting a column name from a user can let them pick a column that is not indexed, making the query slow enough to overload the database, or a column holding data you did not mean to expose. Unless the case is truly dynamic, two fixed queries are often better than one dynamic query.

Stored procedures are not magic

OWASP warns that injection can still happen if SQL is built dynamically inside a stored procedure. The fix is bind variables there too, such as sp_executesql with parameters on SQL Server.

Review checklist

  • No string concatenation or interpolation of request data into SQL, except through an allowlist map.
  • Sorting and filtering fields come from a fixed set.
  • Dynamic SQL inside stored procedures uses bind variables.
  • Database accounts have only the privileges the app needs.

Safeguard's SBOM and dependency scanning covers the ORMs and drivers in your tree. A free scan is at gold.safeguard.sh/scan.

Quick check

Can I escape a column name instead of using an allowlist?

Escaping is easy to get wrong and differs by database. An allowlist of known columns is simpler to review.

Is an ORM automatically safe?

Only for the queries it builds from its own API. Raw SQL escape hatches carry the same risk as hand written SQL.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.