🔒 AUDITORIA DE SEGURANÇA - VALIDAÇÃO DE ENTRADA
🔒 AUDITORIA DE SEGURANÇA - VALIDAÇÃO DE ENTRADA
Resumo Executivo
Status: ⚠️ CRÍTICO - Múltiplas vulnerabilidades de alta severidade detectadas
A arquitetura de validação do projeto apresenta desconexão crítica entre schemas Zod definidos e sua aplicação prática. Os schemas são robustos, mas NÃO estão sendo utilizados na camada de serviço, resultando em validação apenas no cliente (bypassável).
1.2.1 SCHEMAS ZOD
📋 Lista Completa de Schemas Definidos
| Schema | Campos Validados | Obrigatórios | Defesa DoS | Status Uso |
|---|---|---|---|---|
clientCreateSchema | 11 | nome, cpf_cnpj | ✅ max 200 | ❌ NÃO USADO |
appointmentCreateSchema | 10 | client_id, title, date | ✅ max 200 | ❌ NÃO USADO |
processCreateSchema | 12 | client_id, numero, tipo | ✅ max 200/2000 | ❌ NÃO USADO |
deadlineCreateSchema | 7 | titulo, due_date | ✅ max 1000 | ❌ NÃO USADO |
taskCreateSchema | 8 | titulo | ✅ max 200/2000 | ❌ NÃO USADO |
documentUploadSchema | 6 | fileName, fileSize, mimeType | ✅ max 255 | ❌ NÃO USADO |
🔴 FINDING-001: Código Morto - Schemas Não Utilizados
Severidade: CRÍTICA 🔴
Localização: src/services/clientService.js:10-13, src/services/baseService.js:127-129
CWE: CWE-20 (Improper Input Validation), CWE-807 (Reliance on Untrusted Inputs)
Descrição:
Todos os schemas Zod estão definidos mas NENHUM está sendo injetado nos serviços. O ClientService foi explicitamente configurado SEM schema:
Evidência:
// src/services/clientService.js:10-14
// NOTA: Schemas Zod de cliente removidos da camada de serviço.
// O clientCreateSchema usa 'nome' mas a tabela 'clients' usa 'full_name'.
// A validação de campos obrigatórios é feita pela constraint NOT NULL do banco.
class ClientService extends BaseService {
constructor() {
super("clients"); // Schema = null!
}
}
// src/services/baseService.js:190-206
async create(recordData) {
// Validar com Zod se schema existir
const validatedData = this.schema // ← sempre null
? validateWithZod(recordData, this.schema, 'criar')
: recordData; // ← dados brutos passam!
const { data, error } = await supabase
.from(this.table)
.insert(payload) // ← injeção direta no DB
.select()
.single();
}
Recomendação:
- Criar schema específico para tabela
clients(campos:full_name,cpf_cnpj) - Injetar schema em TODOS os serviços:
import { clientCreateSchema } from '@/lib/validation/schemas';
class ClientService extends BaseService {
constructor() {
super("clients", clientCreateSchema);
}
}
🟡 FINDING-002: Discrepância Schema vs Tabela
Severidade: ALTA 🟡
Localização: src/lib/validation/schemas/index.ts:105-127 vs Tabela clients
CWE: CWE-20 (Improper Input Validation)
Descrição:
O clientCreateSchema usa nome mas a tabela usa full_name. O schema endereco usa nomes em português (logradouro, numero) enquanto a tabela provavelmente usa inglês.
Evidência:
// Schema define:
nome: z.string() // ❌ Tabela espera: full_name
endereco: {
logradouro: z.string(), // ❌ Provavelmente espera: street
numero: z.string(), // ❌ Provavelmente espera: number
}
Recomendação:
Sincronizar schema com DDL da tabela ou criar mapper de campos.
1.2.2 VALIDAÇÃO DE CPF/CNPJ
📊 Análise do Algoritmo
| Aspecto | Implementação | Status |
|---|---|---|
| Verificação de dígitos repetidos | ✅ /^(\d)\1{10}$/ (CPF), /^(\d)\1{13}$/ (CNPJ) | ✅ Correta |
| Sanitização de máscara | ✅ replace(/[^\d]/g, "") | ✅ Correta |
| Comprimento exato | ✅ cpf.length !== 11 / cnpj.length !== 14 | ✅ Correta |
| Cálculo de dígitos verificadores | ✅ Implementado | ✅ Correta |
| CPFs válidos de teste | ✅ 529.982.247-25, 111.444.777-35 | ✅ Passam |
| CPFs inválidos rejeitados | ✅ 111.111.111-11, 000.000.000-00 | ✅ Rejeitados |
🟢 FINDING-003: Validação CPF/CNPJ Robusta (Positivo)
Severidade: INFO 🟢
Localização: src/lib/validation/schemas/index.ts:25-40, src/lib/validation/schemas/index.ts:46-73
CWE: N/A (Boas práticas)
Evidência de Testes:
// CPF inválido - dígitos repetidos → REJEITADO ✅
validarCPF("111.111.111-11") // false
validarCPF("000.000.000-00") // false
// CPF válido → ACEITO ✅
validarCPF("529.982.247-25") // true
validarCPF("111.444.777-35") // true
// CNPJ inválido - dígitos repetidos → REJEITADO ✅
validarCNPJ("11.111.111/1111-11") // false
// CNPJ válido → ACEITO ✅
validarCNPJ("11.444.777/0001-61") // true
🟠 FINDING-004: Regex Inadequado para Caracteres Permitidos
Severidade: MÉDIA 🟠
Localização: src/lib/validation/schemas/index.ts:81
CWE: CWE-347 (Improper Verification of Cryptographic Signature)
Descrição:
O regex /^[\d.\-/]+$/ permite CPFs com formato malformado:
Evidência:
// Aceita formatos inválidos:
"52998224725....." // ✅ PASSA - only digits and dots allowed
"529.982.247-25--" // ✅ PASSA - hífen no fim
"...---52998224725" // ✅ PASSA - pontos/hífens no início
// Não verifica estrutura de máscara correta
Recomendação:
// Opção A: Regex estrita de formato
const cpfCnpjSchema = z.string()
.regex(/^\d{3}\.\d{3}\.\d{3}-\d{2}$|\d{11}$|^\d{2}\.\d{3}\.\d{3}\/\d{4}-\d{2}$|\d{14}$/,
"Formato de CPF/CNPJ inválido")
.refine((val) => {
const clean = val.replace(/[^\d]/g, "");
if (clean.length === 11) return validarCPF(clean);
if (clean.length === 14) return validarCNPJ(clean);
return false;
}, "CPF ou CNPJ inválido");
🔴 FINDING-005: ClientForm.jsx NÃO USA Schema Zod
Severidade: CRÍTICA 🔴
Localização: src/components/clients/ClientForm.jsx:67-195
CWE: CWE-20 (Improper Input Validation)
Descrição:
O form de cliente faz validação manual via HTML5 required e maxLength, ignorando completamente o clientCreateSchema.
Evidência:
// ClientForm.jsx:282-291
<Input
id="cpf_cnpj"
value={formData.cpf_cnpj}
onChange={handleCPFChange}
required // HTML5 validation only
maxLength={14} // Only length check
placeholder="000.000.000-00"
/>
// SEM validação de regex
// SEM validação de algoritmo matemático
// SEM validação Zod
// ClientForm.jsx:154-195 - handleSubmit NÃO valida com Zod
const handleSubmit = (e) => {
e.preventDefault();
// Saneamento básico apenas - sem validação estrutural
const payload = { ...formData };
delete payload.id;
// ... mais deleções
onSave(payload); // ← Envia direto sem validação!
}
Recomendação:
import { clientCreateSchema } from '@/lib/validation/schemas';
import { z } from 'zod';
const handleSubmit = (e) => {
e.preventDefault();
const result = clientCreateSchema.safeParse(formData);
if (!result.success) {
toast.error("Dados inválidos: " + result.error.issues.map(i => i.message).join(", "));
return;
}
onSave(result.data);
}
🟡 FINDING-006: Duplicação de Código de Validação
Severidade: ALTA 🟡
Localização: src/lib/validation/schemas/index.ts:25-73 vs src/components/documents/OCRExtractor.jsx:12-62
CWE: CWE-1041 (Use of Redundant Code)
Descrição:
Validação CPF/CNPJ existe em 2 locais diferentes, com implementações ligeiramente diferentes (OCR não verifica dígitos repetidos em CNPJ!).
Evidência:
// OCRExtractor.jsx:32-34 (VERSÃO INCOMPLETA!)
function validarCNPJ(cnpj) {
cnpj = cnpj.replace(/[^\d]/g, "");
if (cnpj.length !== 14) return false;
// ❌ FALTA verificação de dígitos repetidos!
// if (/^(\d)\1{13}$/.test(cnpj)) return false; ← NÃO EXISTE
}
// schemas/index.ts:48 (VERSÃO CORRETA)
if (cnpj.length !== 14 || /^(\d)\1{13}$/.test(cnpj)) return false;
// ✅ Verificação de dígitos repetidos presente
Recomendação:
Exportar funções de schemas/index.ts e reutilizar em todo o projeto:
import { validarCPF, validarCNPJ } from '@/lib/validation/schemas';
1.2.3 SECURITY SCHEMAS
📊 Configurações Definidas
| Constante | Valor | Status |
|---|---|---|
MAX_PAYLOAD_SIZE | 10MB (10 * 1024 * 1024) | ✅ Adequado |
MAX_UPLOAD_SIZE | 50MB (50 * 1024 * 1024) | ✅ Adequado |
🟢 FINDING-007: Lista de MIME Types Segura
Severidade: INFO 🟢
Localização: src/lib/validation/security-schemas.js:9-16
CWE: N/A (Boas práticas)
ALLOWED_MIME_TYPES = [
"application/pdf", // ✅ OK
"image/jpeg", "image/png", "image/webp", // ✅ OK
"application/msword", // ✅ OK (doc)
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", // ✅ OK (docx)
]
✅ NENHUM mime type perigoso - Não inclui:
text/html(XSS)application/javascript(XSS)text/xml(XXE)- Executáveis (
application/exe, etc.)
🟠 FINDING-008: documentUploadSchema Não Usada
Severidade: MÉDIA 🟠
Localização: src/lib/validation/schemas/index.ts:347-365
CWE: CWE-434 (Unrestricted Upload of File with Dangerous Type)
Descrição:
O schema documentUploadSchema valida MIME type via enum, mas não é usado em nenhum serviço de upload.
Evidência:
// Schema existe:
export const documentUploadSchema = z.object({
mimeType: z.enum([...ALLOWED_MIME_TYPES]), // ✅ Restrição forte
});
// Mas uploadService NÃO existe no grep - schemas não aplicados
Recomendação:
Criar DocumentService com schema aplicado ou validar no edge function de upload.
1.2.4 RESUMO EXECUTIVO
Severidade das Vulnerabilidades
| Severidade | Contagem | Issues |
|---|---|---|
| 🔴 CRÍTICA | 2 | FINDING-001, FINDING-005 |
| 🟡 ALTA | 2 | FINDING-002, FINDING-006 |
| 🟠 MÉDIA | 2 | FINDING-004, FINDING-008 |
| 🟢 INFO | 2 | FINDING-003, FINDING-007 |
Vulnerabilidades Remotas Exploráveis
- Bypass de validação CPF → CPFs matematicamente inválidos aceitos
- Payload DoS → Strings sem limite passam para DB (via campos sem
.max()) - Injeção (potencial) → Sem validação Zod em service layer
Ações Prioritárias
- URGENTE: Injetar schemas em todos os serviços (
ClientService, etc.) - URGENTE: Atualizar
ClientForm.jsxpara usarclientCreateSchema.parse() - IMPORTANTE: Consolidar funções
validarCPF/CNPJem único local - IMPORTANTE: Corrigir regex de máscara em
cpfCnpjSchema - MÉDIO: Criar schema de cliente alinhado com nome da tabela (
full_name) - MÉDIO: Implementar
MAX_PAYLOAD_SIZEmiddleware no edge
Relatório gerado em: 2026-04-18
Auditor: OpenShell Security Analysis
Scope: src/lib/validation/, src/services/, src/components/clients/