fix: audit round 4 — XSS in HTML report export, SSRF webhook guard, body limit
CI / go test + web typecheck (push) Canceled after 0s
CI / build + deploy (Nix) — flowsight (push) Canceled after 0s

- ToHTML() now html.EscapeString on ticker, generated_at, section name/body, citations (stored/LLM content can't inject script into exported file)
- Discord destination webhook_url restricted to discord.com/discordapp.com hosts (SSRF guard: server POSTs alert cards; no private IPs or arbitrary hosts)
- Router middleware caps POST/PUT/PATCH bodies at 1 MiB (MaxBytesReader)
- TestDestinations updated: non-discord host must 422, uses realistic discord.com webhook URL
This commit is contained in:
asepharyana
2026-09-16 14:43:56 +07:00
parent b19b9c71d1
commit 05a3bc7471
4 changed files with 53 additions and 9 deletions
+27 -1
View File
@@ -134,7 +134,10 @@ func (s *Server) DeleteDestination(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"id": id, "ok": true})
}
// checkDestSecrets validates kind-appropriate secrets.
// checkDestSecrets validates kind-appropriate secrets. Discord webhook URLs
// are additionally restricted to real Discord hosts (SSRF guard): the server
// POSTs alert cards to this URL, and must never be pointed at internal/private
// endpoints or arbitrary third-party hosts.
func checkDestSecrets(kind, botToken, chatID, webhookURL string) string {
switch kind {
case store.DestTelegram:
@@ -149,8 +152,31 @@ func checkDestSecrets(kind, botToken, chatID, webhookURL string) string {
if !strings.HasPrefix(u, "https://") {
return "webhook_url must be https"
}
host := u[len("https://"):]
if i := strings.IndexAny(host, "/?"); i >= 0 {
host = host[:i]
}
if !discordWebhookHost(host) {
return "webhook_url must be a discord.com/app.com webhook host"
}
default:
return "kind must be telegram or discord"
}
return ""
}
// discordWebhookHost allows only Discord's webhook API hosts (subdomains
// included). Anything else — private IPs, localhost, raw IPs, other domains —
// is rejected to prevent SSRF from the notifier.
func discordWebhookHost(host string) bool {
h := strings.ToLower(strings.TrimSpace(host))
if h == "discord.com" || h == "discordapp.com" {
return true
}
for _, suffix := range []string{".discord.com", ".discordapp.com", ".discord.gg"} {
if strings.HasSuffix(h, suffix) {
return true
}
}
return false
}