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:
2026-06-29 09:30:50 -04:00
parent 5373105368
commit 130187c7be
23 changed files with 504 additions and 105 deletions
+20
View File
@@ -0,0 +1,20 @@
package auth
import (
"net/http"
"time"
)
// SetSessionCookie writes the session cookie with secure defaults.
// Shared by password login, registration, and WebAuthn login.
func SetSessionCookie(w http.ResponseWriter, cookieName, token string, duration time.Duration) {
http.SetCookie(w, &http.Cookie{
Name: cookieName,
Value: token,
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
MaxAge: int(duration.Seconds()),
})
}
+1 -9
View File
@@ -293,13 +293,5 @@ func (h *Handler) getProfile(ctx context.Context, userID string) (UserProfile, e
}
func (h *Handler) setSessionCookie(w http.ResponseWriter, token string) {
http.SetCookie(w, &http.Cookie{
Name: h.cfg.Session.CookieName,
Value: token,
Path: "/",
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteStrictMode,
MaxAge: int(h.cfg.Session.Duration.Seconds()),
})
SetSessionCookie(w, h.cfg.Session.CookieName, token, h.cfg.Session.Duration)
}
+2 -8
View File
@@ -9,6 +9,7 @@ import (
"log/slog"
"net/http"
"sync"
"time"
"git.dustin.coffee/hobokenchicken/dumpsterChat/internal/config"
"git.dustin.coffee/hobokenchicken/dumpsterChat/internal/middleware"
@@ -331,14 +332,7 @@ func (h *WebAuthnHandler) LoginFinish(w http.ResponseWriter, r *http.Request) {
return
}
http.SetCookie(w, &http.Cookie{
Name: "dumpster_session",
Value: token,
Path: "/",
HttpOnly: true,
MaxAge: 86400 * 30, // 30 days
SameSite: http.SameSiteStrictMode,
})
SetSessionCookie(w, "dumpster_session", token, 30*24*time.Hour)
// Clear challenge cookie
http.SetCookie(w, &http.Cookie{