Files
notas-online/internal/api/render.go
T
lgcosta 6e0ec29888 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
2026-11-05 18:58:10 -03:00

45 lines
1.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package api
import (
"net/http"
"strings"
)
const maxBody = 4 << 20 // folga sobre o limite de conteúdo da store (2 MiB)
// maxRenderInput limita o tamanho do markdown aceito pelo endpoint de render:
// conversão goldmark é O(n)–O(n²) por extensão; sem cap, 1 MiB perverso de
// entrada produz ~2 MiB de HTML (amplificação medida) e consome CPU.
const maxRenderInput = 512 << 10 // 512 KiB
type renderInput struct {
Content string `json:"content"`
Format string `json:"format"`
}
// handleRender converte Markdown em HTML seguro (raw HTML do markdown é
// escapado pelo goldmark; a nota em si é sempre renderizada via innerHTML).
func (s *Server) handleRender(w http.ResponseWriter, r *http.Request) {
var in renderInput
if err := decodeJSON(w, r, &in, maxBody); err != nil {
writeError(w, http.StatusBadRequest, "corpo JSON inválido")
return
}
if len(in.Content) > maxRenderInput {
writeError(w, http.StatusRequestEntityTooLarge, "conteúdo para render excede 512 KiB")
return
}
switch in.Format {
case "html":
// O preview de HTML usa iframe com sandbox no cliente; não renderiza aqui.
writeError(w, http.StatusBadRequest, "formato html usa preview em iframe")
return
}
var buf strings.Builder
if err := s.md.Convert([]byte(in.Content), &buf); err != nil {
writeError(w, http.StatusInternalServerError, "falha ao renderizar markdown")
return
}
writeJSON(w, http.StatusOK, map[string]string{"html": buf.String()})
}