Replaced one-pager with multiple pages and fixed security bugs

This commit is contained in:
Jan Meinl
2026-05-16 21:10:55 +02:00
parent 802906f9d4
commit 68034dea7d
25 changed files with 2311 additions and 1217 deletions
+80 -1
View File
@@ -6,7 +6,9 @@ import (
"errors"
"flag"
"log"
"net"
"net/http"
"net/url"
"os"
"os/signal"
"path/filepath"
@@ -142,9 +144,11 @@ func main() {
registerRuleRoutes(mux)
registerWSRoutes(mux)
handler := withSecurityHeaders(withLog(withCSRF(withCORS(mux))))
server := &http.Server{
Addr: cfg.Addr,
Handler: withLog(withCORS(mux)),
Handler: handler,
ReadHeaderTimeout: 10 * time.Second,
}
@@ -171,6 +175,22 @@ func withLog(next http.Handler) http.Handler {
})
}
func withSecurityHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h := w.Header()
h.Set("X-Content-Type-Options", "nosniff")
h.Set("X-Frame-Options", "DENY")
h.Set("Referrer-Policy", "no-referrer")
h.Set("Permissions-Policy", "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()")
h.Set("Cross-Origin-Resource-Policy", "same-site")
h.Set("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'")
if r.TLS != nil || strings.EqualFold(r.Header.Get("X-Forwarded-Proto"), "https") {
h.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
}
next.ServeHTTP(w, r)
})
}
func originAllowed(origin string) bool {
if origin == "" {
return false
@@ -186,6 +206,65 @@ func originAllowed(origin string) bool {
return false
}
func sameOriginRequest(r *http.Request) bool {
origin := r.Header.Get("Origin")
if origin == "" {
// Fall back to Referer
ref := r.Header.Get("Referer")
if ref == "" {
// No origin info: only safe methods allowed. State-changing must have Origin.
return false
}
u, err := url.Parse(ref)
if err != nil {
return false
}
origin = u.Scheme + "://" + u.Host
}
u, err := url.Parse(origin)
if err != nil {
return false
}
originHost, _, _ := net.SplitHostPort(u.Host)
if originHost == "" {
originHost = u.Host
}
reqHost, _, _ := net.SplitHostPort(r.Host)
if reqHost == "" {
reqHost = r.Host
}
return strings.EqualFold(originHost, reqHost)
}
func isStateChanging(method string) bool {
switch method {
case http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete:
return true
}
return false
}
func withCSRF(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !isStateChanging(r.Method) {
next.ServeHTTP(w, r)
return
}
origin := r.Header.Get("Origin")
// Allow if Origin matches a configured CORS origin.
if origin != "" && originAllowed(origin) {
next.ServeHTTP(w, r)
return
}
// Allow same-origin requests (Origin or Referer host matches request Host).
if sameOriginRequest(r) {
next.ServeHTTP(w, r)
return
}
writeError(w, http.StatusForbidden, "csrf_forbidden")
})
}
func withCORS(next http.Handler) http.Handler {
if len(corsOrigins) == 0 {
return next