Evasión de autenticación
admin' OR '1'='1Intenta eludir el login haciendo que la condición WHERE siempre sea verdadera
Los valores proporcionados por el usuario nunca se interpolan en el texto SQL — los valores se enlazan mediante placeholders de parámetros (dependiente del dialecto, p. ej. $1 o ?)
La inyección SQL sigue siendo una de las vulnerabilidades más críticas en aplicaciones web
La inyección SQL ocurre cuando datos no confiables se incorporan al texto SQL sin un enlace seguro por parámetros. Los atacantes pueden manipular el significado de la consulta para acceder a datos no autorizados, modificar registros o provocar operaciones no deseadas.
admin' OR '1'='1Intenta eludir el login haciendo que la condición WHERE siempre sea verdadera
' UNION SELECT password FROM users--Usa UNION para intentar extraer datos de otras tablas
'; DROP TABLE users; --Intenta añadir sentencias extra para borrar datos
Exploit: si placeholders y params no coinciden, los checks de autorización pueden usar valores incorrectosEjemplo: WHERE userId=$1 AND isAdmin=$2, pero params=[true, 123] en vez de [123, true] puede conceder acceso admin incorrectamente
Cada consulta pasa por múltiples capas de validación de seguridad
Todos los valores proporcionados por el usuario se convierten en parámetros enlazados. Los valores no entran al texto SQL como literales.
Cada nombre de campo se valida contra los metadatos del esquema de Prisma antes de generar la consulta.
Nombres de tablas, columnas y aliases se validan y se citan/escapan de forma segura según sea requerido.
Solo se permiten operadores conocidos y seguros para cada tipo de campo.
Un orden estricto asegura que cada placeholder mapea exactamente a un parámetro en el orden de inserción, evitando desajustes.
Esbozos de prueba de resistencia a inyección bajo supuestos de diseño (enlace de parámetros, sin bypass de SQL crudo, uso correcto del driver)
Para todo valor proporcionado por el usuario V, no existe ruta de ejecución donde V aparezca como texto SQL en el string de consulta generado.
Por construcción, todas las rutas que manejan valores del usuario llaman a addParameter(value) que: 1. Almacena el valor en un array params separado 2. Devuelve un token placeholder para el texto SQL 3. Solo el token placeholder se agrega al string SQL 4. El driver recibe el texto SQL y los valores de parámetros por separado ∴ Los valores del usuario no se parsean como sintaxis SQL
const addParameter = (params: any[], value: any) => {
params.push(value)
const index = params.length
return `$${index}`
}El conjunto de nombres de campo F en cualquier consulta generada es un subconjunto de los campos definidos en el esquema S, es decir, F ⊆ S.
Para cada referencia de campo: 1. Se extrae el nombre del campo del objeto de consulta 2. Se busca en metadatos del esquema 3. Si el campo no existe, se lanza error 4. Solo campos validados llegan a la generación de SQL ∴ No pueden aparecer nombres de campos arbitrarios en 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)
}Para cualquier operador O aplicado al campo F, O es miembro de los operadores permitidos para el tipo T de F.
Algoritmo de validación de operadores: 1. Extraer el tipo de campo T del esquema 2. Definir allowed_ops(T) = { operadores válidos para el tipo T } 3. Para el operador O en la consulta: - Si O ∉ allowed_ops(T), lanzar error - Si no, aplicar el operador usando enlace de parámetros ∴ Solo se usan operadores apropiados al tipo
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')
}Todos los identificadores SQL I se validan para prevenir caracteres de control y asegurar un quoting/escaping seguro según dialecto.
Procesamiento de identificadores: 1. Rechazar si contiene caracteres de control 2. Escapar comillas internas según reglas del dialecto 3. Citar identificadores cuando es necesario (palabras reservadas o caracteres especiales) 4. Aplicar límites de longitud por dialecto ∴ Los identificadores no pueden romper la sintaxis SQL ni introducir tokens inyectados
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
}Para cada posición de placeholder N emitida en el texto SQL, params[N-1] contiene el valor exacto destinado a esa posición, sin reordenamientos ni desajustes.
Garantía de orden de parámetros: 1. Un único tracker mantiene el orden de inserción 2. Cada llamada add() agrega el valor a params y emite inmediatamente el siguiente placeholder 3. La construcción del SQL y del array params avanza en sincronía 4. No hay reordenamientos intermedios ∴ Se mantiene correspondencia 1 a 1 durante todo el proceso
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()La suite actual valida la protección contra vectores comunes de inyección SQL y casos límite
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 }Un tracking centralizado de parámetros asegura que cada valor del usuario se enlace como parámetro y mantenga orden estable
class ParameterTracker {
private params: any[] = []
add(value: any): string {
this.params.push(value)
return `$${this.params.length}`
}
getParams(): any[] {
return this.params
}
}La validación basada en esquema previene acceso arbitrario a campos
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
}Validación y quoting de identificadores consciente del dialecto previene inyección basada en identificadores
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
}No omitas la validación del esquema ni las comprobaciones de campos. Son capas críticas de seguridad.
Asegúrate de que tu esquema Prisma refleje con precisión la estructura de tu base de datos.
TypeScript aporta seguridad en compilación, pero la validación en runtime añade defensa en profundidad.
Registra y monitorea SQL generado y parámetros en producción para detectar anomalías y mal uso.
La protección contra inyección SQL está integrada en cada capa de esta librería. Revisa las pruebas de seguridad y los detalles de implementación directamente.