fix(security): corrige achados da varredura — render, brute-force, upload, WYSIWYG, cookie, sessões

1. /api/render: cap de 512 KiB na entrada (amplificação ~2× medida);
   decodeJSON agora amarra http.MaxBytesReader ao ResponseWriter (413
   real ao cliente em todos os endpoints JSON)
2. Login com rate-limit por IP em memória (5 falhas/15 min → 429, acerto
   reseta, RemoteAddr puro — X-F-F não é confiável) + log de falha com IP
3. Upload: MaxBytesReader no corpo inteiro (64 MiB + 64 KiB) antes do
   parse do multipart; quota global de 2 GiB (MaxFilesTotalBytes) em AddFile
4. sanitizeRich neutraliza href/src com javascript:/vbscript:/data:
   (case/whitespace-insensível); link legítimo preservado
5. Cookie Secure passa a ser configuração explícita (NOTES_SECURE_COOKIE)
   — X-Forwarded-Proto é spoofável; documentado em .env.example e compose
6. Cookie agora carrega id de sessão opaco (crypto/rand 32B, TTL 30d,
   revogação no logout e no restart); token mestre só vale via Bearer —
   furo pego pelo próprio teste novo (token no cookie era aceito)
   Testes: rate-limit, revogação, canais separados, render 413, upload cap
   sw v16
This commit is contained in:
lgcosta committed 2026-11-05 18:58:10 -03:00
1 parent 1b9ce995ba
commit 6e0ec29888
20 files changed
+427 -53

No files matched your search

+14 -7
View File
@@ -364,14 +364,21 @@ curl -sI -H "Authorization: Bearer $T" $B/api/files/static/<id> | grep -i dispos
## Segurança
- Token verificado em **tempo constante**; nunca é registrado nos logs.
- Sessão do navegador em cookie **HttpOnly + SameSite=Lax** (mitiga CSRF); o token em
`localStorage` foi preterido de propósito por ser exposto a XSS.
- Token verificado em **tempo constante**; nunca é registrado nos logs. **Login tem rate-limit por
IP** (5 tentativas erradas em 15 min → 429; acerto reseta; log de falha com IP).
- Sessão do navegador em cookie **HttpOnly + SameSite=Lax** contendo um **id de sessão opaco**
(`crypto/rand`, 32 bytes) — o token mestre nunca trafega em cookie. Logout **revoga** a sessão no
servidor; reiniciar o processo revoga todas (relogin). O token mestre vale apenas via
`Authorization: Bearer` (scripts/CLI).
- **Cookie `Secure` é decisão de configuração** (`NOTES_SECURE_COOKIE=true` no `.env`), nunca de
`X-Forwarded-Proto` do cliente (spoofável). Ative quando o acesso é sempre por HTTPS.
- CSP restritiva (`default-src 'self'`), `X-Frame-Options: DENY`, `nosniff`; preview de HTML roda
em iframe `sandbox` sem mesma origem; Markdown renderiza com HTML cru escapado.
- Limite de 2 MiB por nota; corpo HTTP limitado a 4 MiB.
- Rode atrás de TLS (reverse proxy) em produção — o cookie marca `Secure` automaticamente quando
a requisição chega por HTTPS/`X-Forwarded-Proto`. O modelo é **token único compartilhado**
em iframe `sandbox` sem mesma origem; Markdown renderiza com HTML cru escapado; o editor
WYSIWYG neutraliza `href/src` com esquemas perigosos (`javascript:`, `vbscript:`, `data:`).
- Limite de 2 MiB por nota; corpo HTTP limitado a 4 MiB; `/api/render` aceita no máximo 512 KiB de
entrada (a conversão amplifica ~2×); upload limitado a 64 MiB **no corpo inteiro** e a
**2 GiB de total** de anexos por instância (quota contra esgotamento de disco).
- Rode atrás de TLS (reverse proxy) em produção. O modelo é **token único compartilhado**
(sem usuários múltiplos); a imagem final é `scratch` non-root (uid 65534) — sem shell para
`exec` de depuração, por design.
- Preferências do editor e abas abertas ficam em `localStorage` (dados não sensíveis, por