Segurança em Primeiro Lugar

Prevenção de SQL Injection

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

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

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 vinculação segura de parâmetros. Atacantes podem manipular o significado da consulta para acessar dados não autorizados, modificar registros ou disparar operações não intencionais.

Padrões Comuns de Ataque

Bypass de Autenticação

admin' OR '1'='1

Tenta burlar o login tornando a condição WHERE sempre verdadeira

Exfiltração de Dados

' UNION SELECT password FROM users--

Usa UNION para tentar extrair dados de outras tabelas

Comandos Destrutivos

'; DROP TABLE users; --

Tenta anexar instruções extras para apagar dados

Abuso de Bug na Ordem de Parâmetros

Exploit: se placeholders e params estiverem desalinhados, verificações de autorização podem usar os valores errados

Exemplo: WHERE userId=$1 AND isAdmin=$2, mas params=[true, 123] em vez de [123, true] pode conceder acesso de admin indevidamente

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

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

  1. Camada 1: Vinculação Automática de Parâmetros

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

    • Valores do usuário são representados com placeholders específicos do dialeto (ex.: $1 ou ?)
    • Entradas de array são parametrizadas (placeholders expandidos ou parâmetros de array, conforme o dialeto/estratégia)
    • Valores NULL e opcionais são tratados sem concatenar input do usuário no texto SQL
    • Valores de data/hora são parametrizados
    • Valores JSON são parametrizados
  2. Camada 2: Validação de Nomes de Campo

    Cada nome de campo é validado contra os metadados do schema Prisma antes da geração da consulta.

    • Apenas campos definidos no schema são permitidos
    • Filtros de relação validados via metadados do schema
    • Nenhum nome de campo arbitrário é aceito
    • Campos não suportados/computados são rejeitados quando aplicável
    • Payloads de prototype pollution são rejeitados (ex.: __proto__, constructor)
  3. Camada 3: Sanitização de Identificadores

    Nomes de tabelas, nomes de colunas e aliases são validados e colocados entre aspas/escapados com segurança quando necessário.

    • Caracteres de controle rejeitados
    • Palavras reservadas são colocadas entre aspas quando necessário
    • Qualificação por schema suportada quando configurada
    • Escape de aspas duplas (ou equivalente do dialeto) para identificadores
    • Tamanho máximo de identificador imposto por dialeto
  4. Camada 4: Validação de Operadores

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

    • Operadores de string: contains, startsWith, endsWith
    • Operadores numéricos: lt, lte, gt, gte
    • Operadores de array: in, notIn com validação de tipo
    • Operadores lógicos: AND, OR, NOT validados estruturalmente
    • Operadores desconhecidos rejeitados imediatamente
  5. Camada 5: Garantia de Ordem dos Parâmetros

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

    • Contador sequencial impede reordenação
    • Construção do SQL e do array de params em sincronia
    • Sem buffer intermediário nem operações de reordenação
    • Verificação em teste: SQL gerado e params sempre alinhados
    • Correspondência um-para-um: placeholder N → params[N-1] (ou mapeamento equivalente no dialeto)

Garantias Formais de Segurança

Esboços de prova da resistência a injection sob as premissas de design declaradas (vinculação de parâmetros, sem bypass via SQL bruto, uso correto do driver)

Teorema 1: Isolamento de Valores

Para todo valor V fornecido pelo usuário, não existe caminho de execução em que V apareça como texto SQL na string da consulta gerada.

Por construção, todos os caminhos de código que tratam valores do usuário chamam addParameter(value), que: 1. Armazena o valor em um array params separado 2. Retorna um token placeholder para o texto SQL 3. Apenas o token placeholder é anexado à string SQL 4. O driver do banco recebe o texto SQL e os valores dos parâmetros separadamente ∴ Valores do usuário não são interpretados como sintaxe SQL

const addParameter = (params: any[], value: any) => {
  params.push(value)
  const index = params.length
  return `$${index}`
}

Teorema 2: Fechamento dos Nomes de Campo

O conjunto de nomes de campo F em qualquer consulta gerada é um subconjunto dos campos definidos no schema S, ou seja, F ⊆ S.

Para cada referência de campo: 1. O nome do campo é extraído do objeto da consulta 2. É feita uma busca nos metadados do schema 3. Se o campo não existe no schema, um erro é lançado 4. Apenas campos validados chegam à geração de SQL ∴ Nomes de campo arbitrários não podem aparecer no 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: Segurança de Operadores

Para qualquer operador O aplicado ao campo F, O pertence aos operadores permitidos para o tipo T de F.

Algoritmo de validação de operadores: 1. Extrair o tipo de campo T do schema 2. Definir allowed_ops(T) = { operadores válidos para o tipo T } 3. Para o operador O na consulta: - Se O ∉ allowed_ops(T), lançar erro - Senão, aplicar o operador usando vinculação de parâmetros ∴ Apenas operadores adequados ao tipo podem ser usados

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: Segurança de Identificadores

Todos os identificadores SQL I são validados para impedir caracteres de controle e garantir aspas/escape seguros por dialeto.

Processamento de identificadores: 1. Rejeitar se contiver caracteres de controle 2. Escapar aspas internas conforme as regras do dialeto 3. Colocar identificadores entre aspas quando necessário (palavras reservadas ou caracteres especiais) 4. Impor limites de tamanho de identificador por dialeto ∴ Identificadores não podem quebrar a sintaxe SQL nem introduzir tokens injetados

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: Consistência da Ordem dos Parâmetros

Para cada posição de placeholder N emitida no texto SQL, params[N-1] contém exatamente o valor destinado a essa posição, sem reordenação nem desalinhamento.

Garantia de ordenação dos parâmetros: 1. Um único tracker mantém a ordem de inserção 2. Cada chamada add() anexa o valor a params e emite imediatamente o próximo token placeholder 3. A construção do texto SQL e a dos params avançam em sincronia 4. Não ocorrem operações intermediárias de reordenação ∴ A correspondência um-para-um é mantida do início ao fim

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

A suíte atual valida a proteção contra vetores comuns de SQL injection e casos de borda

Parametrização de Valores (basic.test.ts)

  • Strings com aspas: user'with'quotes
  • Strings com ponto e vírgula: user;extra
  • Palavras-chave SQL como valores: DROP TABLE users
  • Injection complexa: '; DROP TABLE users; --
  • Ataques UNION: ' UNION SELECT * FROM users--
  • Ataques booleanos: admin' OR '1'='1
  • Injection via comentário: test@example.com' -- comment

Validação de Nomes de Campo (identifiers.test.ts)

  • Rejeitar nomes de campo maliciosos em SELECT
  • Rejeitar nomes de campo maliciosos em WHERE
  • Rejeitar nomes de campo maliciosos em ORDER BY
  • Rejeitar SQL injection em nomes de campo
  • Rejeitar campos inexistentes
  • Rejeitar prototype pollution: __proto__, constructor

Segurança de Padrões LIKE (like-patterns.test.ts)

  • Injection com curinga: %' OR '1'='1
  • Injection com underscore: test_' OR '1'='1
  • Tratamento de barra invertida: test\\'; DROP--
  • Múltiplos curingas: %_%'; DROP--
  • Injection sem distinção de maiúsculas e minúsculas: '; UNION SELECT--

Segurança de Operadores de Array (array-operators.test.ts)

  • IN com arrays maliciosos: ['; DROP--', 'UNION SELECT--']
  • NOT IN com injection: ['; TRUNCATE--', 'DELETE FROM--']
  • Tratamento de array vazio
  • Validação de arrays grandes (100+ itens)
  • Tratamento de arrays com tipos mistos

Casos de Borda (edge-cases.test.ts)

  • Injection Unicode: \u0027 OR \u00271\u0027=\u00271
  • Injection codificada em hex: 0x31=0x31--
  • Codificada em URL: %27%3B%20DROP%20TABLE
  • Consultas empilhadas: '; DROP TABLE users; SELECT
  • Blind baseada em tempo (específica do dialeto): WAITFOR DELAY '00:00:05'--
  • Baseada em UNION: UNION ALL SELECT null, password
  • Tentativas de injection de segunda ordem

Verificação da Ordem dos Parâmetros (basic.test.ts)

  • Posições sequenciais de placeholders
  • Array de parâmetros corresponde à ordem dos placeholders
  • Consultas complexas mantêm a ordem entre condições
  • Condições OR/AND aninhadas preservam a sequência de parâmetros
  • Filtros de relação mantêm o mapeamento correto de parâmetros

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

❌ Inseguro: Concatenação de String
unsafe.ts
const email = req.body.email
const sql = "SELECT * FROM users WHERE email = '" + email + "'"
✅ Seguro: Vinculação Automática de Parâmetros
safe.ts
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

O rastreamento centralizado de parâmetros garante que cada valor fornecido pelo usuário seja vinculado como parâmetro, com ordem estável

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
}

Aspas em Identificadores

A validação de identificadores e o uso de aspas conforme o dialeto impedem injection de sintaxe 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 contorne a validação de schema nem a checagem de campos. São camadas críticas de segurança.

Faça
Use a biblioteca como foi projetada, com validação completa
Não faça
Contorne a validação de schema ou injete SQL bruto no texto da consulta

Mantenha o Schema Atualizado

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

Faça
Rode prisma db pull e prisma generate após mudanças no schema
Não faça
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.

Faça
Use os tipos gerados pelo Prisma para os argumentos das consultas
Não faça
Converta input do usuário para any antes de passar para consultas

Monitore Padrões de Consulta

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

Faça
Ative o log de consultas e revise os padrões
Não faça
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.