security: remediate all P0-P2 audit findings (12 tasks)
P0 fixes: - WebSocket origin checking (reject untrusted origins) - WS session token moved from URL query param to first message frame - WebAuthn login cookie now uses Secure flag via shared SetSessionCookie - PostgreSQL sslmode configurable via POSTGRES_SSLMODE env (default: require) - Fixed DatabaseDSN to use real password instead of masked placeholder P1 fixes: - Per-IP rate limiting middleware (auth: 5 req/s, invites: 2 req/s) - Security headers on all responses (CSP, X-Frame-Options, nosniff, etc.) - WS broadcasts scoped to server members (prevents cross-server data leak) - Webhook tokens stored as SHA-256 hashes (not plaintext) P2 fixes: - CSRF protection via Origin header validation on state-changing requests - MANAGE_CHANNELS permission enforced on channel update/delete - Upload validation: 25MB limit, extension allowlist, server-side MIME check - File serve: path traversal protection + Content-Disposition: attachment Files: 23 changed, +499/-100. Builds clean (go build, go vet, npm build). Zero CVEs (govulncheck, npm audit).
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CSRFProtect returns middleware that validates the Origin header on
|
||||
// state-changing methods (POST, PUT, PATCH, DELETE). Requests from origins
|
||||
// not in the trustedOrigins list are rejected with 403. Non-browser clients
|
||||
// (curl, bots) that send no Origin or Referer header are allowed through,
|
||||
// since they are not subject to CSRF.
|
||||
func CSRFProtect(trustedOrigins []string) func(http.Handler) http.Handler {
|
||||
originSet := make(map[string]struct{}, len(trustedOrigins))
|
||||
for _, o := range trustedOrigins {
|
||||
originSet[o] = struct{}{}
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "POST", "PUT", "PATCH", "DELETE":
|
||||
origin := r.Header.Get("Origin")
|
||||
if origin == "" {
|
||||
// Fallback: extract origin from Referer
|
||||
referer := r.Header.Get("Referer")
|
||||
if referer != "" {
|
||||
for _, o := range trustedOrigins {
|
||||
if strings.HasPrefix(referer, o) {
|
||||
origin = o
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if origin != "" {
|
||||
if _, ok := originSet[origin]; !ok {
|
||||
http.Error(w, `{"error":"forbidden origin"}`, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
// If both Origin and Referer are empty, allow through
|
||||
// (non-browser client like curl, bots, mobile apps)
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user