package main import ( "fmt" "log" "net/http" "os" "path/filepath" "strings" ) const maxAdminRequestBody = 8 << 20 func writeInternalError(w http.ResponseWriter, operation string, err error) { if err != nil { log.Printf("%s: %v", operation, err) } http.Error(w, "internal server error", http.StatusInternalServerError) } func writeBadGatewayError(w http.ResponseWriter, operation string, err error) { if err != nil { log.Printf("%s: %v", operation, err) } http.Error(w, "managed server request failed", http.StatusBadGateway) } // securePanelHandler applies baseline browser protections and a global request // body ceiling. Endpoint-specific handlers may impose a smaller limit. func securePanelHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Frame-Options", "DENY") w.Header().Set("Referrer-Policy", "no-referrer") w.Header().Set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), payment=()") w.Header().Set("Cross-Origin-Opener-Policy", "same-origin") w.Header().Set("Content-Security-Policy", "default-src 'self'; base-uri 'none'; frame-ancestors 'none'; object-src 'none'; form-action 'self'; img-src 'self' data:; connect-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'") if strings.HasPrefix(r.URL.Path, "/api/") || r.URL.Path == "/" || r.URL.Path == "/index.html" { w.Header().Set("Cache-Control", "no-store") } if r.Body != nil && r.Method != http.MethodGet && r.Method != http.MethodHead { r.Body = http.MaxBytesReader(w, r.Body, maxAdminRequestBody) } next.ServeHTTP(w, r) }) } // writeFileAtomic replaces a sensitive configuration file without leaving a // partially written file behind after a crash or interrupted request. func writeFileAtomic(path string, data []byte, mode os.FileMode) error { dir := filepath.Dir(path) tmp, err := os.CreateTemp(dir, ".dragoncore-*.tmp") if err != nil { return err } tmpName := tmp.Name() defer os.Remove(tmpName) if err := tmp.Chmod(mode); err != nil { tmp.Close() return err } if _, err := tmp.Write(data); err != nil { tmp.Close() return err } if err := tmp.Sync(); err != nil { tmp.Close() return err } if err := tmp.Close(); err != nil { return err } if err := os.Rename(tmpName, path); err != nil { return fmt.Errorf("replace %s: %w", path, err) } return nil }