Authentication Bypass
admin' OR '1'='1Attempts to bypass login by making the WHERE condition always true
User-provided values are never interpolated into SQL text — values are bound via parameter placeholders (dialect-specific, e.g. $1 or ?)
SQL injection remains one of the most critical web application vulnerabilities
SQL injection occurs when untrusted data is incorporated into SQL text without safe parameter binding. Attackers can manipulate query meaning to access unauthorized data, modify records, or trigger unintended operations.
admin' OR '1'='1Attempts to bypass login by making the WHERE condition always true
' UNION SELECT password FROM users--Uses UNION to attempt extracting data from other tables
'; DROP TABLE users; --Attempts to append extra statements to delete data
Exploit: If placeholders and params are mismatched, authorization checks may use the wrong valuesExample: WHERE userId=$1 AND isAdmin=$2, but params=[true, 123] instead of [123, true] can incorrectly grant admin access
Every query passes through multiple security validation layers
All user-provided values are converted into bound parameters. Values do not enter SQL text as literals.
Every field name is validated against the Prisma schema metadata before query generation.
Table names, column names, and aliases are validated and safely quoted/escaped as required.
Only known, safe operators are allowed for each field type.
Strict ordering ensures each placeholder maps to exactly one parameter in insertion order, preventing mismatches.
Proof sketches for injection resistance under stated design assumptions (parameter binding, no raw-SQL bypass, correct driver usage)
For all user-provided values V, there exists no execution path where V appears as SQL text in the generated query string.
By construction, all code paths that handle user values call addParameter(value) which: 1. Stores the value in a separate params array 2. Returns a placeholder token for the SQL text 3. Only the placeholder token is appended into the SQL string 4. The database driver receives SQL text and parameter values separately ∴ User values are not parsed as SQL syntax
const addParameter = (params: any[], value: any) => {
params.push(value)
const index = params.length
return `$${index}`
}The set of field names F in any generated query is a subset of schema-defined fields S, i.e., F ⊆ S.
For every field reference: 1. Field name extracted from query object 2. Lookup performed against schema metadata 3. If the field does not exist in the schema, an error is thrown 4. Only validated fields reach SQL generation ∴ Arbitrary field names cannot appear in SQL
const validateField = (field: string, model: Model) => {
if (!model.fields.has(field)) {
throw new Error(`Field ${field} does not exist`)
}
return model.fields.get(field)
}For any operator O applied to field F, O is a member of the allowed operators for F's type T.
Operator validation algorithm: 1. Extract field type T from schema 2. Define allowed_ops(T) = { valid operators for type T } 3. For operator O in query: - If O ∉ allowed_ops(T), throw error - Else apply operator using parameter binding ∴ Only type-appropriate operators can be used
const ALLOWED_OPS: Record<string, string[]> = {
String: ['contains', 'startsWith', 'endsWith'],
Int: ['lt', 'lte', 'gt', 'gte']
}
if (!ALLOWED_OPS[fieldType]?.includes(operator)) {
throw new Error('Invalid operator')
}All SQL identifiers I are validated to prevent control characters and to ensure safe quoting/escaping per dialect.
Identifier processing: 1. Reject if contains control characters 2. Escape internal quote characters per dialect rules 3. Quote identifiers when required (reserved keywords or special characters) 4. Enforce per-dialect identifier length limits ∴ Identifiers cannot break SQL syntax or introduce injected tokens
const quoteIdentifier = (id: string) => {
if (/[\x00-\x1F]/.test(id)) {
throw new Error('Invalid characters')
}
const escaped = id.replace(/"/g, '""')
const needsQuoting = /[^a-z0-9_]/i.test(id) || isReservedKeyword(id)
return needsQuoting ? `"${escaped}"` : id
}For every placeholder position N emitted into the SQL text, params[N-1] contains the exact value intended for that position, with no reordering or mismatch.
Parameter ordering guarantee: 1. A single tracker maintains insertion order 2. Each add() call appends the value to params and immediately emits the next placeholder token 3. SQL text construction and params construction proceed in lockstep 4. No intermediate reordering operations occur ∴ One-to-one correspondence is maintained throughout
class ParameterTracker {
private params: any[] = []
add(value: any): string {
this.params.push(value)
return `$${this.params.length}`
}
getParams(): any[] {
return this.params
}
}
const tracker = new ParameterTracker()
const sql = `WHERE email = ${tracker.add(email)} AND age > ${tracker.add(age)}`
const params = tracker.getParams()The current suite validates protection against common SQL injection vectors and edge cases
const email = req.body.email
const sql = "SELECT * FROM users WHERE email = '" + email + "'"const email = req.body.email
const { sql, params } = toSQL('User', 'findMany', {
where: { email }
})
const result = { sql, params }Centralized parameter tracking ensures every user-provided value is parameter bound and order-stable
class ParameterTracker {
private params: any[] = []
add(value: any): string {
this.params.push(value)
return `$${this.params.length}`
}
getParams(): any[] {
return this.params
}
}Schema-based validation prevents arbitrary field access
function validateField(
fieldName: string,
model: ModelInfo
): FieldInfo {
const field = model.fields.get(fieldName)
if (!field) {
throw new Error(
`Field "${fieldName}" does not exist`
)
}
return field
}Dialect-aware identifier validation and quoting prevents identifier-based syntax injection
function quoteIdentifier(identifier: string): string {
if (/[\x00-\x1F]/.test(identifier)) {
throw new Error('Invalid control characters')
}
const escaped = identifier.replace(/"/g, '""')
const needsQuoting = /[^a-z0-9_]/i.test(identifier) || isReservedKeyword(identifier)
if (needsQuoting) {
return `"${escaped}"`
}
return identifier
}Do not bypass schema validation or field checking. These are critical security layers.
Ensure your Prisma schema accurately reflects your database structure.
TypeScript provides compile-time safety, but runtime validation adds defense in depth.
Log and monitor generated SQL and parameters in production to detect anomalies and misuse.
SQL injection protection is built into every layer of this library. Review the security tests and implementation details directly.