🔒 Segurança em Primeiro Lugar

SQL Injection Prevenção

Valores fornecidos pelo usuário nunca são interpolados no texto SQL — os valores são vinculados via placeholders de parâmetros (dependente do dialeto, ex.: $1 ou ?)

100%
Parâmetros Vinculados
Todos os valores do usuário usam placeholders
Zero
Interpolação de Valores
Dados do usuário nunca aparecem no texto SQL
Garantido
Preservação de Ordem
Placeholders mapeiam params 1:1
137
Testes de Segurança
Cobertura de tentativas de injection (suíte atual)

Entendendo SQL Injection

SQL injection continua sendo uma das vulnerabilidades mais críticas em aplicações web

SQL injection ocorre quando dados não confiáveis são incorporados no texto SQL sem parameter binding seguro. Atacantes podem manipular o significado da query para acessar dados não autorizados, modificar registros ou disparar operações não intencionais.

Padrões Comuns de Ataque

Authentication Bypass

admin' OR '1'='1

Attempts to bypass login by making the WHERE condition always true

Data Exfiltration

' UNION SELECT password FROM users--

Uses UNION to attempt extracting data from other tables

Destructive Commands

'; DROP TABLE users; --

Attempts to append extra statements to delete data

Parameter Order Bug Abuse

Exploit: If placeholders and params are mismatched, authorization checks may use the wrong values

Example: WHERE userId=$1 AND isAdmin=$2, but params=[true, 123] instead of [123, true] can incorrectly grant admin access

Estratégia de Proteção em Múltiplas Camadas

Cada query passa por múltiplas camadas de validação de segurança

Camada 1: Parameter Binding Automático

Todos os valores do usuário são convertidos em parâmetros vinculados. Valores não entram no texto SQL como literais.

  • User values are represented using dialect-specific placeholders (e.g. $1 or ?)
  • Array inputs are parameterized (expanded placeholders or array parameters depending on dialect/strategy)
  • NULL and optional values handled without concatenating user input into SQL text
  • Date/time values are parameterized
  • JSON values are parameterized

Camada 2: Validação de Nomes de Campo

Cada nome de campo é validado contra o metadata do schema Prisma antes da geração do SQL.

  • Only schema-defined fields are allowed
  • Relation filters validated via schema metadata
  • No arbitrary field names accepted
  • Unsupported/computed fields rejected where applicable
  • Prototype pollution payloads are rejected (e.g. __proto__, constructor)

Camada 3: Sanitização de Identificadores

Nomes de tabelas, colunas e aliases são validados e corretamente quoted/escaped quando necessário.

  • Control characters rejected
  • Reserved keywords are quoted when needed
  • Schema qualification supported where configured
  • Double-quote escaping (or dialect equivalent) for identifiers
  • Maximum identifier length enforced per dialect

Camada 4: Validação de Operadores

Apenas operadores conhecidos e seguros são permitidos para cada tipo de campo.

  • String operators: contains, startsWith, endsWith
  • Numeric operators: lt, lte, gt, gte
  • Array operators: in, notIn with type validation
  • Logical operators: AND, OR, NOT validated structurally
  • Unknown operators rejected immediately

Camada 5: Garantia de Ordem de Parâmetros

Ordenação estrita garante que cada placeholder mapeia para exatamente um parâmetro na ordem de inserção, evitando mismatches.

  • Sequential counter prevents reordering
  • Lockstep SQL building and param array construction
  • No intermediate buffer or reordering operations
  • Test verification: generated SQL and params always align
  • One-to-one correspondence: placeholder N → params[N-1] (or equivalent mapping for the dialect)

Garantias Formais de Segurança

Proof sketches para resistência a injection sob as suposições do design (parameter binding, sem bypass raw-SQL, uso correto do driver)

Theorem 1: Value Isolation

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

Theorem 2: Field Name Closure

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

Theorem 3: Operator Safety

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

Theorem 4: Identifier Safety

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
}

Theorem 5: Parameter Order Consistency

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

Cobertura Abrangente de Testes de Segurança

137 testes validam proteção contra vetores comuns de SQL injection e edge cases (suíte atual)

Value Parameterization (basic.test.ts)

  • Strings with quotes: user'with'quotes
  • Strings with semicolons: user;extra
  • SQL keywords as values: DROP TABLE users
  • Complex injection: '; DROP TABLE users; --
  • Union attacks: ' UNION SELECT * FROM users--
  • Boolean attacks: admin' OR '1'='1
  • Comment injection: test@example.com' -- comment

Field Name Validation (identifiers.test.ts)

  • Reject malicious field names in SELECT
  • Reject malicious field names in WHERE
  • Reject malicious field names in ORDER BY
  • Reject SQL injection in field names
  • Reject non-existent fields
  • Reject prototype pollution: __proto__, constructor

LIKE Pattern Safety (like-patterns.test.ts)

  • Wildcard injection: %' OR '1'='1
  • Underscore injection: test_' OR '1'='1
  • Backslash handling: test\\'; DROP--
  • Multiple wildcards: %_%'; DROP--
  • Case insensitive injection: '; UNION SELECT--

Array Operator Safety (array-operators.test.ts)

  • IN with malicious arrays: ['; DROP--', 'UNION SELECT--']
  • NOT IN with injection: ['; TRUNCATE--', 'DELETE FROM--']
  • Empty array handling
  • Large array validation (100+ items)
  • Mixed type array handling

Edge Cases (edge-cases.test.ts)

  • Unicode injection: \u0027 OR \u00271\u0027=\u00271
  • Hex-encoded injection: 0x31=0x31--
  • URL encoded: %27%3B%20DROP%20TABLE
  • Stacked queries: '; DROP TABLE users; SELECT
  • Time-based blind (dialect-specific): WAITFOR DELAY '00:00:05'--
  • UNION-based: UNION ALL SELECT null, password
  • Second-order injection attempts

Parameter Order Verification (basic.test.ts)

  • Sequential placeholder positions
  • Parameter array matches placeholder order
  • Complex queries maintain order across conditions
  • Nested OR/AND conditions preserve parameter sequence
  • Relation filters maintain correct parameter mapping

Seguro vs. Inseguro: Comparação de Código

❌ Inseguro: Concatenação de String

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

✅ Seguro: Parameter Binding Automático

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

const result = { sql, params }

Detalhes de Implementação

Gerenciamento de Parâmetros

Rastreamento centralizado garante que cada valor do usuário seja parametrizado e preserve a ordem

class ParameterTracker {
  private params: any[] = []

  add(value: any): string {
    this.params.push(value)
    return `$${this.params.length}`
  }

  getParams(): any[] {
    return this.params
  }
}

Validação de Campos

Validação baseada em schema impede acesso arbitrário 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
}

Quoting de Identificadores

Validação e quoting por dialeto previnem injection via 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
}

Boas Práticas de Segurança

Nunca Desative Validações

Não faça bypass de validação de schema ou checagem de campos. São camadas críticas de segurança.

Do: Use a biblioteca como foi projetada, com validação completa
Don't: Bypass schema validation ou injete SQL bruto no texto da query

Mantenha o Schema Atualizado

Garanta que seu schema Prisma reflita com precisão a estrutura do banco.

Do: Rode prisma db pull e prisma generate após mudanças no schema
Don't: Use DMMF ou definições de schema desatualizadas

Valide Tipos de Input

TypeScript oferece segurança em tempo de compilação, mas validação em runtime adiciona defesa em profundidade.

Do: Use os tipos gerados pelo Prisma para args de query
Don't: Converta input do usuário para any antes de passar para queries

Monitore Padrões de Query

Registre e monitore SQL gerado e parâmetros em produção para detectar anomalias e mau uso.

Do: Ative query logging e revise padrões
Don't: Rode em produção sem monitoramento

Segurança por Design

A proteção contra SQL injection está embutida em cada camada desta biblioteca. Revise os testes de segurança e detalhes de implementação diretamente.