Strip path components from CORS AllowOrigins

Change-Id: I28019e6649a644188a79f0321486cf9d375bd271
diff --git a/config/config.go b/config/config.go
index d685708..9347994 100644
--- a/config/config.go
+++ b/config/config.go
@@ -2,6 +2,7 @@
 
 import (
 	"fmt"
+	"net/url"
 	"os"
 	"path/filepath"
 	"strconv"
@@ -306,12 +307,12 @@
 		}
 	}
 
-	// AllowOrigins defaults to the Server value (with trailing slash
-	// stripped to form a proper origin). This avoids duplicating the
-	// server URL string and keeps CORS in sync with the deployment.
+	// AllowOrigins defaults to the Server value. This avoids duplicating
+	// the server URL string and keeps CORS in sync with the deployment.
 	if config.AllowOrigins == "" {
-		config.AllowOrigins = strings.TrimRight(config.Server, "/")
+		config.AllowOrigins = config.Server
 	}
+	config.AllowOrigins = normalizeOrigins(config.AllowOrigins)
 
 	if config.Port == 0 {
 		config.Port = defaultPort
@@ -321,6 +322,23 @@
 	}
 }
 
+// normalizeOrigins takes a comma-separated list of origin URLs and strips
+// any path components, returning only scheme + host (+ port when present).
+// The CORS middleware requires bare origins without paths; URLs like
+// "https://example.com/instance/test" are pruned to "https://example.com".
+func normalizeOrigins(raw string) string {
+	parts := strings.Split(raw, ",")
+	for i, part := range parts {
+		part = strings.TrimSpace(part)
+		if u, err := url.Parse(part); err == nil && u.Host != "" {
+			parts[i] = u.Scheme + "://" + u.Host
+		} else {
+			parts[i] = strings.TrimRight(part, "/")
+		}
+	}
+	return strings.Join(parts, ",")
+}
+
 // ApplyEnvOverrides overrides configuration fields from environment variables.
 // All environment variables are uppercase and prefixed with KORAL_MAPPER_.
 // Non-empty environment values override any previously loaded config values.
diff --git a/config/config_test.go b/config/config_test.go
index 0881157..a7d121f 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -1494,6 +1494,33 @@
 		"KORAL_MAPPER_ALLOW_ORIGINS env var should override YAML value")
 }
 
+func TestAllowOriginsDerivedFromServerWithPath(t *testing.T) {
+	cfg := &MappingConfig{
+		Server: "https://korap.ids-mannheim.de/instance/test",
+	}
+	ApplyDefaults(cfg)
+	assert.Equal(t, "https://korap.ids-mannheim.de", cfg.AllowOrigins,
+		"AllowOrigins should be pruned to host-level origin when Server contains a path")
+}
+
+func TestAllowOriginsExplicitWithPathsPruned(t *testing.T) {
+	cfg := &MappingConfig{
+		AllowOrigins: "https://korap.ids-mannheim.de/instance/test,https://other.example.com/app",
+	}
+	ApplyDefaults(cfg)
+	assert.Equal(t, "https://korap.ids-mannheim.de,https://other.example.com", cfg.AllowOrigins,
+		"explicit AllowOrigins entries should be pruned to host-level origins")
+}
+
+func TestAllowOriginsWithPort(t *testing.T) {
+	cfg := &MappingConfig{
+		Server: "https://korap.ids-mannheim.de:8080/instance/test",
+	}
+	ApplyDefaults(cfg)
+	assert.Equal(t, "https://korap.ids-mannheim.de:8080", cfg.AllowOrigins,
+		"AllowOrigins should preserve port but strip path")
+}
+
 func TestSanitizeFilePathRejectsOutsideBase(t *testing.T) {
 	// Set base to a specific directory and verify paths outside are rejected
 	tmpDir, err := os.MkdirTemp("", "koral-base-*")