🔒 Seguridad primero

Inyección SQL Prevención

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 ?)

100%
Enlace por parámetros
Todos los valores del usuario usan placeholders
Cero
Interpolación de valores
Los datos del usuario nunca aparecen en el texto SQL
Garantizado
Conservación de orden
Los placeholders mapean a params 1:1
137
Pruebas de seguridad
Cobertura de intentos de inyección (suite actual)

Entendiendo la inyección SQL

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.

Patrones comunes de ataque

Evasión de autenticación

admin' OR '1'='1

Intenta eludir el login haciendo que la condición WHERE siempre sea verdadera

Exfiltración de datos

' UNION SELECT password FROM users--

Usa UNION para intentar extraer datos de otras tablas

Comandos destructivos

'; DROP TABLE users; --

Intenta añadir sentencias extra para borrar datos

Abuso de bug de orden de parámetros

Exploit: si placeholders y params no coinciden, los checks de autorización pueden usar valores incorrectos

Ejemplo: WHERE userId=$1 AND isAdmin=$2, pero params=[true, 123] en vez de [123, true] puede conceder acceso admin incorrectamente

Estrategia de protección en múltiples capas

Cada consulta pasa por múltiples capas de validación de seguridad

Capa 1: Enlace automático de parámetros

Todos los valores proporcionados por el usuario se convierten en parámetros enlazados. Los valores no entran al texto SQL como literales.

  • Los valores del usuario se representan con placeholders específicos del dialecto (p. ej. $1 o ?)
  • Los arrays se parametrizan (placeholders expandidos o parámetros array según dialecto/estrategia)
  • NULL y valores opcionales se manejan sin concatenar input del usuario al texto SQL
  • Valores de fecha/hora se parametrizan
  • Valores JSON se parametrizan

Capa 2: Validación de nombres de campos

Cada nombre de campo se valida contra los metadatos del esquema de Prisma antes de generar la consulta.

  • Solo se permiten campos definidos en el esquema
  • Filtros de relación validados mediante metadatos del esquema
  • No se aceptan nombres de campo arbitrarios
  • Campos no soportados o computados se rechazan cuando corresponde
  • Se rechazan payloads de prototype pollution (p. ej. __proto__, constructor)

Capa 3: Sanitización de identificadores

Nombres de tablas, columnas y aliases se validan y se citan/escapan de forma segura según sea requerido.

  • Se rechazan caracteres de control
  • Palabras reservadas se citan cuando es necesario
  • Soporte para calificación de esquema cuando está configurado
  • Escape de comillas dobles (o equivalente del dialecto) para identificadores
  • Se aplica longitud máxima de identificadores según dialecto

Capa 4: Validación de operadores

Solo se permiten operadores conocidos y seguros para cada tipo de campo.

  • Operadores string: contains, startsWith, endsWith
  • Operadores numéricos: lt, lte, gt, gte
  • Operadores de array: in, notIn con validación de tipos
  • Operadores lógicos: AND, OR, NOT validados estructuralmente
  • Operadores desconocidos se rechazan inmediatamente

Capa 5: Garantía de orden de parámetros

Un orden estricto asegura que cada placeholder mapea exactamente a un parámetro en el orden de inserción, evitando desajustes.

  • Contador secuencial evita reordenamiento
  • Construcción de SQL y del array de params en sincronía
  • Sin buffers intermedios ni reordenamientos
  • Verificación por tests: SQL generado y params siempre alinean
  • Correspondencia 1 a 1: placeholder N → params[N-1] (o mapeo equivalente según dialecto)

Garantías formales de seguridad

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)

Teorema 1: Aislamiento de valores

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}`
}

Teorema 2: Clausura de nombres de campos

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)
}

Teorema 3: Seguridad de operadores

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')
}

Teorema 4: Seguridad de identificadores

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
}

Teorema 5: Consistencia del orden de parámetros

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()

Cobertura integral de pruebas de seguridad

137 pruebas validan protección contra vectores comunes de inyección SQL y casos límite (suite actual)

Parametrización de valores (basic.test.ts)

  • Strings con comillas: user'with'quotes
  • Strings con punto y coma: user;extra
  • Keywords SQL como valores: DROP TABLE users
  • Inyección compleja: '; DROP TABLE users; --
  • Ataques UNION: ' UNION SELECT * FROM users--
  • Ataques booleanos: admin' OR '1'='1
  • Inyección de comentarios: test@example.com' -- comment

Validación de nombres de campos (identifiers.test.ts)

  • Rechazar nombres de campo maliciosos en SELECT
  • Rechazar nombres de campo maliciosos en WHERE
  • Rechazar nombres de campo maliciosos en ORDER BY
  • Rechazar inyección SQL en nombres de campo
  • Rechazar campos inexistentes
  • Rechazar prototype pollution: __proto__, constructor

Seguridad de patrones LIKE (like-patterns.test.ts)

  • Inyección de comodín: %' OR '1'='1
  • Inyección con guion bajo: test_' OR '1'='1
  • Manejo de backslash: test\\'; DROP--
  • Múltiples comodines: %_%'; DROP--
  • Inyección case-insensitive: '; UNION SELECT--

Seguridad de operadores de array (array-operators.test.ts)

  • IN con arrays maliciosos: ['; DROP--', 'UNION SELECT--']
  • NOT IN con inyección: ['; TRUNCATE--', 'DELETE FROM--']
  • Manejo de array vacío
  • Validación de arrays grandes (100+ items)
  • Manejo de arrays con tipos mixtos

Casos límite (edge-cases.test.ts)

  • Inyección Unicode: \u0027 OR \u00271\u0027=\u00271
  • Inyección codificada en hex: 0x31=0x31--
  • URL encoded: %27%3B%20DROP%20TABLE
  • Consultas apiladas: '; DROP TABLE users; SELECT
  • Time-based blind (según dialecto): WAITFOR DELAY '00:00:05'--
  • Basada en UNION: UNION ALL SELECT null, password
  • Intentos de inyección de segundo orden

Verificación de orden de parámetros (basic.test.ts)

  • Posiciones de placeholders secuenciales
  • El array de parámetros coincide con el orden de placeholders
  • Consultas complejas mantienen el orden a través de condiciones
  • Condiciones OR/AND anidadas preservan la secuencia de parámetros
  • Filtros de relación mantienen el mapeo correcto de parámetros

Seguro vs. inseguro: comparación de código

❌ Inseguro: concatenación de strings

const email = req.body.email
const sql = "SELECT * FROM users WHERE email = '" + email + "'"

✅ Seguro: enlace automático de parámetros

const email = req.body.email
const { sql, params } = toSQL('User', 'findMany', {
  where: { email }
})

const result = { sql, params }

Detalles de implementación

Gestión de parámetros

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
  }
}

Validación de campos

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
}

Citado de identificadores

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
}

Mejores prácticas de seguridad

Nunca desactivar la validación

No omitas la validación del esquema ni las comprobaciones de campos. Son capas críticas de seguridad.

Do: Usa la librería tal como está diseñada con validación completa
Don't: Omitir validación del esquema o inyectar SQL crudo en el texto de consulta

Mantener el esquema actualizado

Asegúrate de que tu esquema Prisma refleje con precisión la estructura de tu base de datos.

Do: Ejecuta prisma db pull y prisma generate tras cambios de esquema
Don't: Usar DMMF o definiciones de esquema desactualizadas

Validar tipos de entrada

TypeScript aporta seguridad en compilación, pero la validación en runtime añade defensa en profundidad.

Do: Usa los tipos generados por Prisma para argumentos de consulta
Don't: Convertir input del usuario a any antes de pasarlo a consultas

Monitorear patrones de consulta

Registra y monitorea SQL generado y parámetros en producción para detectar anomalías y mal uso.

Do: Habilita logging de consultas y revisa patrones
Don't: Ejecutar en producción sin monitoreo

Seguridad por diseño

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.