Skip to content

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; includeSubDomains

O 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: *.

ConfigComportamento
*Qualquer origem aceita (⚠️ warning logado se auth habilitado)
https://app.com,https://admin.app.comSomente 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): retorna 204
  • Origens não-wildcard adicionam Vary: Origin

Rate limiting

Habilitado via RATE_LIMIT=true. Sliding window por IP, aplicado após autenticação.

CategoriaRotasLimite defaultVariável
readGET, HEAD, OPTIONS120/minRATE_LIMIT_READ
writePOST, PUT, DELETE60/minRATE_LIMIT_WRITE
generate/generate*, /output20/minRATE_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:

json
{
  "error": "rate_limit_exceeded",
  "message": "Too many requests. Try again later.",
  "retryAfter": 60
}

Headers incluídos:

  • X-RateLimit-Limit — limite configurado
  • X-RateLimit-Remaining — requests restantes na janela
  • X-RateLimit-Reset — timestamp de reset
  • Retry-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 JSON10 MB
Upload multipart50 MB
Output geradoMAX_OUTPUT_SIZE (default 200 MB, clamped 1 KB – 2 GB)

Middleware de erros

Exceções não tratadas são capturadas pelo middleware global:

  • FormatException400 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:

json
{
  "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:

json
{"type":"auth_failed","ts":"2026-04-06T12:00:00.000Z","ip":"1.2.3.4","extra":{"reason":"expired"}}
EventoQuando
auth_failedFalha de autenticação
rate_limit_exceededRate limit atingido
token_reuse_detectedRefresh token revogado reutilizado
family_revokedFamília de tokens revogada
share_createdShare link criado
share_accessedShare acessado com sucesso
share_code_failedCódigo de acesso incorreto
share_code_lockoutShare excluído após 5 tentativas
share_ip_bannedIP banido por fail2ban
share_revokedShare revogado manualmente
share_expired_accessTentativa de acesso a share expirado
export_verify_requestedOTP de exportação solicitado
export_otp_sentOTP enviado por email
export_otp_failedCódigo OTP incorreto
export_otp_expiredCódigo OTP expirado
export_identity_rejectedEmail não permitido pela ExportPolicy
export_completedExportação verificada concluída
edit_verify_requestedOTP de edição solicitado
edit_identity_rejectedEmail não permitido pela EditPolicy
edit_otp_sentOTP de edição enviado por email
edit_otp_failedFalha 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_SECRET com valor aleatório longo
  • [ ] Definir CORS_ORIGINS com origens específicas (não usar *)
  • [ ] Habilitar RATE_LIMIT=true
  • [ ] Definir ALLOWED_HOSTS para datasources externos
  • [ ] Definir ENCRYPTION_KEY para criptografia de conexões e assinatura
  • [ ] Usar STORAGE_BACKEND=sqlite ou postgres (não usar memory em prod)
  • [ ] Definir TOKEN_STORE_PATH para 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_IDENTITIES e configurar email provider (veja Export)
  • [ ] Se usar edição protegida: definir EDIT_POLICY_ENABLED=true, EDIT_ALLOWED_IDENTITIES, EDIT_TOKEN_SECRET e configurar email provider (veja Edição Protegida)

Sulfite do 🇧🇷 para o mundo © 2026 Rafael S. Pinheiro