🔒 AUDITORIA DE SEGURANÇA - RV-ADV
🔒 AUDITORIA DE SEGURANÇA - RV-ADV
1.2.3 Sanitização de Uploads | 1.2.4 SQL Injection | 1.2.5 XSS Prevention
Data da Auditoria: 2026-04-18
Executor: OpenShell Security Audit
Escopo: Frontend (src/), Edge Functions (supabase/functions/)
📊 RESUMO EXECUTIVO
| Categoria | Itens Auditados | Findings | Severidade Máx |
|---|---|---|---|
| Sanitização de Uploads | 4 | 2 | ALTA |
| SQL Injection | 5 | 1 | MÉDIA |
| XSS Prevention | 6 | 2 | MÉDIA |
1.2.3 SANITIZAÇÃO DE UPLOADS
📍 Localização: src/lib/validation/security-schemas.js
✅ FINDING 1 - Configuração Adequada de ALLOWED_MIME_TYPES
Severidade: INFO
Status: ✅ SEGURO
// Linhas 9-16
export const ALLOWED_MIME_TYPES = [
"application/pdf",
"image/jpeg",
"image/png",
"image/webp",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
];
Avaliação: Lista de MIME types bem definida, excluindo tipos perigosos (.exe, .sh, .html, .js).
⚠️ FINDING 2 - Validação de MIME Type Client-Side Apenas
Severidade: ALTA
CWE: CWE-434 (Unrestricted Upload of File with Dangerous Type)
Localização:
src/components/documents/DocumentUpload.jsx:112src/components/documents/ClientDocumentsSection.jsx:518-538
Evidência:
// DocumentUpload.jsx:112 - Aceita apenas por extensão client-side
<input
type="file"
accept=".pdf,.doc,.docx,.jpg,.jpeg,.png,.docm,.dotx"
onChange={handleFileChange}
/>
// ClientDocumentsSection.jsx:518-538 - Validação só de tamanho e formato
const validFormats = [
"application/pdf",
"image/jpeg",
"image/png",
// ...
];
Problema:
- O atributo
acceptHTML pode ser burlado - O
file.typepode ser spoofed (ex: enviar .exe comoapplication/pdf) - NÃO HÁ validação server-side do MIME type real do arquivo
Recomendação:
- Implementar validação server-side na Edge Function
ocr-classify-documentouai-proxy - Verificar magic numbers (file signatures) ao invés de confiar em
file.type - Usar biblioteca como
file-typeoumagic-bytesno backend
⚠️ FINDING 3 - Ausência de Validação de Nome de Arquivo
Severidade: ALTA
CWE: CWE-22 (Path Traversal)
Localização:
src/services/aiService.js:13-16
Evidência:
// aiService.js:13-16
async function uploadFileToStorage(file, bucket = 'client-documents', folder = 'documents') {
const fileExt = file.name.split('.').pop(); // Vulnerável a path traversal
const fileName = `${Date.now()}_${Math.random().toString(36).substring(2)}.${fileExt}`;
const filePath = `${folder}/${fileName}`; // Não sanitiza o folder
Problema:
- Nome do arquivo original não é sanitizado antes de extrair extensão
- Possível path traversal com nomes como
../../../etc/passwd - Parâmetro
foldernão é validado
Recomendação:
// Correção sugerida
function sanitizeFilename(filename) {
// Remove path traversal
return filename
.replace(/\.{2,}[\/\\]/g, '')
.replace(/[\/\\]/g, '-')
.replace(/[^a-zA-Z0-9.\-_]/g, '');
}
const fileExt = sanitizeFilename(file.name).split('.').pop();
🔍 FINDING 4 - Limite de Tamanho Inconsistente
Severidade: MÉDIA
Localização: Código vs Schema
Evidência - Schema:
// security-schemas.js:6-7
export const MAX_PAYLOAD_SIZE = 10 * 1024 * 1024; // 10MB
export const MAX_UPLOAD_SIZE = 50 * 1024 * 1024; // 50MB
Evidência - Código:
// ClientDocumentsSection.jsx:518
if (file.size > 10 * 1024 * 1024) { // Usando 10MB, não 50MB
toast.error("Arquivo muito grande. Máximo 10MB.");
return;
}
Problema: O schema define MAX_UPLOAD_SIZE=50MB, mas o código usa 10MB. Embora seja mais restritivo (seguro), a inconsistência pode causar confusão.
1.2.4 SQL INJECTION
📍 Localização: Serviços Supabase
⚠️ FINDING 5 - Uso de .ilike com Template String
Severidade: MÉDIA
CWE: CWE-89 (SQL Injection)
Localização: src/services/clientService.js:131
Evidência:
// clientService.js:125-133
async searchByName(searchTerm, limit = 20) {
const { data, error } = await supabase
.from(this.table)
.select("*")
.or(`full_name.ilike.%${searchTerm}%,cpf_cnpj.ilike.%${searchTerm}%`) // ⚠️
.eq('status', 'ativo')
.limit(limit);
Problema:
Embora o Supabase escape automaticamente parâmetros, template strings com .ilike e .or podem ser vulneráveis a wildcard abuse e em casos específicos de caracteres especiais.
Evidência de Teste:
// Teste: searchTerm = "test%; DROP TABLE clients; --"
// A query resultante seria:
// SELECT * FROM clients WHERE (full_name.ilike.%test%; DROP TABLE clients; --% OR ...)
No entanto, o Supabase/PostgREST normalmente rejeita queries malformadas. O risco real é:
- Wildcard abuse:
"%%"pode causar DoS (retornar todos registros) - Performance:
"%"sem prefixo força full table scan
Recomendação:
// Correção sugerida
async searchByName(searchTerm, limit = 20) {
// Sanitizar: remover caracteres especiais, limitar wildcards
const sanitized = searchTerm
.replace(/[%_]/g, '') // Remove wildcards SQL
.trim()
.slice(0, 100); // Limita tamanho
if (sanitized.length === 0) return [];
const { data, error } = await supabase
.from(this.table)
.select("id, full_name, cpf_cnpj") // Limita colunas
.ilike('full_name', `%${sanitized}%`)
.eq('status', 'ativo')
.limit(Math.min(limit, 100));
✅ FINDING 6 - RPC Seguro (buscar_jurisprudencia)
Severidade: INFO
Localização: src/services/jurisprudenciaService.js:60
Evidência:
// Linhas 60-64
const { data, error } = await supabase.rpc('buscar_jurisprudencia', {
query_embedding: embedData.embedding, // Array de números
match_count: resolvedMatchCount, // Número limitado
similarity_threshold: 0.4, // Constante
});
Avaliação: ✅ RPC parametrizado corretamente com tipos seguros. Não há concatenação de SQL.
✅ FINDING 7 - Queries BaseService (Prepared Statements)
Severidade: INFO
Localização: src/services/baseService.js
Avaliação: Todas as operações CRUD usam o cliente Supabase nativo que automaticamente parametriza queries:
.eq(),.filter(),.select()- Prepared statements implícitos- Sem concatenação de SQL
- Schema Zod para validação de entrada
1.2.5 XSS PREVENTION
📍 Localização: Componentes React
⚠️ FINDING 8 - Uso de dangerouslySetInnerHTML
Severidade: MÉDIA
CWE: CWE-79 (Cross-site Scripting)
Localização:
src/components/ui/chart.jsx:61-80src/modules/periciapro/components/ui/chart.jsx:61-80
Evidência:
// chart.jsx:61-80
return (
<style
dangerouslySetInnerHTML={{
__html: Object.entries(THEMES)
.map(([theme, prefix]) => `
${prefix} [data-chart=${id}] {
${colorConfig
.map(([key, itemConfig]) => {
const color = itemConfig.theme?.[theme] || itemConfig.color;
return color ? ` --color-${key}: ${color};` : null;
})
.join("\n")}
}
`)
.join("\n"),
}}
/>
);
Análise:
- Color é controlado - vem de configuração estatizada (THEMES)
- Key vem de colorConfig - se este for derivado de dados de usuário, há risco
Risco: Se colorConfig receber dados do usuário sem sanitização:
// Cenário hipotético de ataque:
// colorConfig = [["key<script>alert(1)</script>", { color: "#fff" }]]
// Resultado: <style> --color-key<script>alert(1)</script>: #fff; </style>
Recomendação:
// Sanitizar keys e values antes de interpolar
const sanitizeCss = (str) => str.replace(/[^a-zA-Z0-9_-]/g, '');
return color ? ` --color-${sanitizeCss(key)}: ${sanitizeCss(color)};` : null;
✅ FINDING 9 - Ausência de DOMPurify/Markdown Sanitization
Severidade: INFO
Localização: Uso de react-markdown
Evidência:
// package.json
"react-markdown": "^9.0.1"
Avaliação:
react-markdownv9+ sanitiza HTML por padrão (não renderiza raw HTML)- Não há necessidade de DOMPurify explícito
- Nenhum uso de
skipHtmlouescapeHtmldesativado encontrado
✅ FINDING 10 - URLs em Links (window.open)
Severidade: INFO
Localização:
src/components/documents/ClientDocumentsSection.jsx:759src/components/documents/DocumentViewer.jsx:39
Evidência:
// ClientDocumentsSection.jsx:759
window.open(doc.file_url, "_blank")
// DocumentViewer.jsx:39
window.open(document.file_url, "_blank");
Análise:
file_urlvem do Supabase Storage (bucket 'client-documents')- As URLs são geradas pelo Supabase (domínio controlado)
- Não há risco de
javascript:scheme injection - Status: ✅ SEGURO
✅ FINDING 11 - React Auto-Escape Ativo
Severidade: INFO
Avaliação: Todo o projeto usa React JSX que automaticamente escapa conteúdo dinâmico:
{userInput}→ Escapado automaticamente<div>{description}</div>→ description escapado- Não há uso de
innerHTMLdireto (exceto o caso do chart)
📋 MATRIZ DE RISCOS AGREGADOS
| # | Finding | Severidade | Status | CWE | Prioridade Correção |
|---|---|---|---|---|---|
| 1 | Upload sem validação server-side de MIME | ALTA | 🔴 ABERTO | CWE-434 | IMEDIATA |
| 2 | Path traversal em nome de arquivo | ALTA | 🔴 ABERTO | CWE-22 | IMEDIATA |
| 3 | searchByName com template strings | MÉDIA | 🟡 ABERTO | CWE-89 | 1 semana |
| 4 | dangerouslySetInnerHTML em chart | MÉDIA | 🟡 ABERTO | CWE-79 | 1 semana |
| 5 | Inconsistência de MAX_UPLOAD_SIZE | BAIXA | 🟢 INFORMATIVO | - | Quando conveniente |
| 6 | ALLOWED_MIME_TYPES adequado | INFO | ✅ OK | - | - |
| 7 | RPC buscar_jurisprudencia seguro | INFO | ✅ OK | - | - |
| 8 | Prepared statements em BaseService | INFO | ✅ OK | - | - |
| 9 | react-markdown adequado | INFO | ✅ OK | - | - |
| 10 | window.open seguro em documentos | INFO | ✅ OK | - | - |
| 11 | React auto-escape ativo | INFO | ✅ OK | - | - |
🛠️ RECOMENDAÇÕES PRIORITÁRIAS
Prioridade 1 (Imediata - 24h)
- Validar MIME type no Edge Function:
// supabase/functions/ocr-classify-document/index.ts
import { fileTypeFromBlob } from 'file-type';
export default async (req: Request) => {
const blob = await req.blob();
const fileType = await fileTypeFromBlob(blob);
const ALLOWED_TYPES = ['application/pdf', 'image/jpeg', 'image/png'];
if (!ALLOWED_TYPES.includes(fileType?.mime)) {
return new Response(JSON.stringify({ error: 'Invalid file type' }), { status: 400 });
}
// ...
}
- Sanitizar nomes de arquivo:
// src/services/aiService.js
function sanitizePath(input) {
return input
.replace(/\.{2,}[\/\\]/g, '')
.replace(/[\/\\]/g, '-')
.slice(0, 100);
}
Prioridade 2 (1 semana)
- Refatorar searchByName:
// Usar .ilike() de forma segura
const sanitized = searchTerm.replace(/[%_]/g, '').trim();
await supabase
.from('clients')
.select('id, full_name, cpf_cnpj')
.ilike('full_name', `%${sanitized}%`)
.limit(20);
- Sanitizar chart styles:
const sanitizeCss = (str) => str?.replace(/[^a-zA-Z0-9_-]/g, '') || '';
📁 ARQUIVOS REFERENCIADOS
src/lib/validation/security-schemas.js
src/components/documents/DocumentUpload.jsx
src/components/documents/ClientDocumentsSection.jsx
src/components/documents/DocumentViewer.jsx
src/services/aiService.js
src/services/clientService.js
src/services/baseService.js
src/services/jurisprudenciaService.js
src/components/ui/chart.jsx
supabase/functions/ocr-classify-document/index.ts
Relatório gerado automaticamente por OpenShell Security Audit Tool
Baseado em guidelines OWASP, CWE, Supabase Security Best Practices