Segurança
O sulfite_server implementa múltiplas camadas de segurança, desde autenticação até proteção contra SSRF e rate limiting.
Headers de segurança
Todos os responses incluem:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Cache-Control: no-store
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'none'; frame-ancestors 'none'Em produção (HTTPS), o header Strict-Transport-Security também é enviado:
Strict-Transport-Security: max-age=31536000; includeSubDomainsO HSTS pode ser desabilitado via enableHsts: false ao instanciar o middleware (útil em proxies que adicionam o header externamente).
CORS
Configurado via CORS_ORIGINS (separado por vírgula). Default: *.
| Config | Comportamento |
|---|---|
* | Qualquer origem aceita (⚠️ warning logado se auth habilitado) |
https://app.com,https://admin.app.com | Somente origens listadas |
- Métodos permitidos:
GET, POST, PUT, DELETE, OPTIONS - Headers permitidos:
Content-Type, Authorization, X-API-Key, Prefer - Max-Age: 86400 (24h)
- Preflight (
OPTIONS): retorna204 - Origens não-wildcard adicionam
Vary: Origin
Rate limiting
Habilitado via RATE_LIMIT=true. Sliding window por IP, aplicado após autenticação.
| Categoria | Rotas | Limite default | Variável |
|---|---|---|---|
read | GET, HEAD, OPTIONS | 120/min | RATE_LIMIT_READ |
write | POST, PUT, DELETE | 60/min | RATE_LIMIT_WRITE |
generate | /generate*, /output | 20/min | RATE_LIMIT_GENERATE |
Controle de memória (LRU): O rate limiter mantém no máximo 10.000 buckets por IP simultaneamente. Quando esse limite é atingido, os IPs menos recentemente usados são removidos automaticamente, prevenindo exaustão de memória em cenários de flood com IPs variados.
Resposta 429:
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Try again later.",
"retryAfter": 60
}Headers incluídos:
X-RateLimit-Limit— limite configuradoX-RateLimit-Remaining— requests restantes na janelaX-RateLimit-Reset— timestamp de resetRetry-After— segundos até o próximo request permitido
Proteção SSRF
Habilitada via ALLOWED_HOSTS (separado por vírgula). Quando configurada, bloqueia requests de datasources para:
- IPs privados:
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.0/8,0.0.0.0,169.254.0.0/16 - IPv6 privados:
::1,fc00::/7,fe80::/10 - localhost
- IPv4-mapped IPv6
- DNS rebinding: resolve DNS e verifica o IP resolvido
Requests bloqueadas retornam 403 forbidden_host.
Sanitização de queries SQL
Defense-in-depth
O QuerySanitizer é uma camada de defesa adicional. A proteção primária contra injeção SQL é o uso de queries parametrizadas nos datasources. O sanitizador bloqueia padrões claramente maliciosos antes de chegarem ao banco, mas não substitui boas práticas de parametrização.
Queries enviadas via datasources SQL (postgres, postgrest) são validadas:
Bloqueado:
- DDL:
DROP,ALTER,TRUNCATE,CREATE - DML de escrita:
INSERT INTO,UPDATE ... SET,DELETE FROM - Privilégios:
GRANT,REVOKE - Execução de código:
COPY ... TO/FROM - Multi-statement:
; - Tamanho máximo: 10 KB
Queries bloqueadas retornam 400 dangerous_query.
Criptografia de conexões
Com ENCRYPTION_KEY configurado, campos sensíveis de conexões são criptografados em repouso usando AES-256-GCM. Veja Conexões → Criptografia para detalhes.
Limites de tamanho
| O quê | Limite |
|---|---|
| Body JSON | 10 MB |
| Upload multipart | 50 MB |
| Output gerado | MAX_OUTPUT_SIZE (default 200 MB, clamped 1 KB – 2 GB) |
Middleware de erros
Exceções não tratadas são capturadas pelo middleware global:
FormatException→400 bad_request- Todas as demais →
500 internal_error(mensagem genérica, detalhes logados com dados sensíveis sanitizados)
Endpoint de auditoria
O endpoint GET /api/v1/health/security (requer canSettings) retorna o status das proteções:
{
"auth": true,
"rateLimit": true,
"ssrfGuard": true,
"encryption": true,
"corsOrigins": ["https://app.example.com"],
"corsWildcard": false
}Auditoria (logs)
Eventos de segurança são logados como JSON estruturado no stdout:
{"type":"auth_failed","ts":"2026-04-06T12:00:00.000Z","ip":"1.2.3.4","extra":{"reason":"expired"}}| Evento | Quando |
|---|---|
auth_failed | Falha de autenticação |
rate_limit_exceeded | Rate limit atingido |
token_reuse_detected | Refresh token revogado reutilizado |
family_revoked | Família de tokens revogada |
share_created | Share link criado |
share_accessed | Share acessado com sucesso |
share_code_failed | Código de acesso incorreto |
share_code_lockout | Share excluído após 5 tentativas |
share_ip_banned | IP banido por fail2ban |
share_revoked | Share revogado manualmente |
share_expired_access | Tentativa de acesso a share expirado |
export_verify_requested | OTP de exportação solicitado |
export_otp_sent | OTP enviado por email |
export_otp_failed | Código OTP incorreto |
export_otp_expired | Código OTP expirado |
export_identity_rejected | Email não permitido pela ExportPolicy |
export_completed | Exportação verificada concluída |
edit_verify_requested | OTP de edição solicitado |
edit_identity_rejected | Email não permitido pela EditPolicy |
edit_otp_sent | OTP de edição enviado por email |
edit_otp_failed | Falha no envio do OTP de edição |
Veja Autenticação → Auditoria para a lista completa de eventos de token.
Checklist de produção
- [ ] Definir
AUTH_SECRETcom valor aleatório longo - [ ] Definir
CORS_ORIGINScom origens específicas (não usar*) - [ ] Habilitar
RATE_LIMIT=true - [ ] Definir
ALLOWED_HOSTSpara datasources externos - [ ] Definir
ENCRYPTION_KEYpara criptografia de conexões e assinatura - [ ] Usar
STORAGE_BACKEND=sqliteoupostgres(não usarmemoryem prod) - [ ] Definir
TOKEN_STORE_PATHpara persistência de tokens - [ ] Monitorar logs de auditoria no stdout
- [ ] Configurar share links conforme necessário (veja Compartilhamento)
- [ ] Se usar exportação verificada: definir
EXPORT_ALLOWED_IDENTITIESe configurar email provider (veja Export) - [ ] Se usar edição protegida: definir
EDIT_POLICY_ENABLED=true,EDIT_ALLOWED_IDENTITIES,EDIT_TOKEN_SECRETe configurar email provider (veja Edição Protegida)