diff --git a/Server/api/middleware.go b/Server/api/middleware.go index 6d3d2f47..7b1de13a 100644 --- a/Server/api/middleware.go +++ b/Server/api/middleware.go @@ -211,15 +211,20 @@ func clientIPWithProxies(r *http.Request, trustedCIDRs []string) string { } // Prefer X-Real-IP when coming from a trusted proxy. + // BUG-112: Validate extracted IP to prevent spoofed rate-limit keys. if xri := strings.TrimSpace(r.Header.Get("X-Real-IP")); xri != "" { - return xri + if net.ParseIP(xri) != nil { + return xri + } } // Fall back to the leftmost (client) entry in X-Forwarded-For. if xff := r.Header.Get("X-Forwarded-For"); xff != "" { parts := strings.SplitN(xff, ",", 2) if client := strings.TrimSpace(parts[0]); client != "" { - return client + if net.ParseIP(client) != nil { + return client + } } } diff --git a/Server/api/upload_handler.go b/Server/api/upload_handler.go index 12eff50b..6dea4034 100644 --- a/Server/api/upload_handler.go +++ b/Server/api/upload_handler.go @@ -57,6 +57,22 @@ func sanitizeUploadFilename(name string) string { return name } +// isUnsafeInlineMIME returns true for MIME types that could execute active +// content (scripts, markup) if served inline under the OwnCord origin. +func isUnsafeInlineMIME(mimeType string) bool { + // Normalize: take the base type before any parameters (e.g. "text/html; charset=utf-8"). + base := strings.SplitN(mimeType, ";", 2)[0] + base = strings.TrimSpace(strings.ToLower(base)) + switch base { + case "text/html", "application/xhtml+xml", + "image/svg+xml", "text/xml", "application/xml", + "application/pdf", + "text/xsl", "text/xslt": + return true + } + return false +} + // MountUploadRoutes registers upload and file-serving endpoints. // allowedOrigins controls the Access-Control-Allow-Origin header on served files. func MountUploadRoutes(r chi.Router, database *db.DB, store *storage.Storage, limiter *auth.RateLimiter, allowedOrigins []string) { @@ -267,7 +283,13 @@ func handleServeFile(database *db.DB, store *storage.Storage, allowedOrigins []s // Set headers before ServeContent to ensure correct MIME type. w.Header().Set("Content-Type", aa.MimeType) - w.Header().Set("Content-Disposition", mime.FormatMediaType("inline", map[string]string{"filename": aa.Filename})) + // BUG-118: Force download for MIME types that could execute content + // under the OwnCord origin (HTML, SVG, XML, PDF). + disposition := "inline" + if isUnsafeInlineMIME(aa.MimeType) { + disposition = "attachment" + } + w.Header().Set("Content-Disposition", mime.FormatMediaType(disposition, map[string]string{"filename": aa.Filename})) w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d, immutable", fileCacheMaxAgeSeconds)) // CORS: allow webview to read the response body using configured origins. if origin := r.Header.Get("Origin"); origin != "" {