gateway: add CORS allow-list for the public REST surface
Tests · Go / test (push) Successful in 1m42s
Tests · Go / test (pull_request) Successful in 1m45s
Tests · Integration / integration (pull_request) Successful in 1m36s

Adds a `GATEWAY_PUBLIC_HTTP_CORS_ALLOWED_ORIGINS` env-driven allow-list
on the public REST server so the dev UI on https://www.galaxy.lan can
call https://api.galaxy.lan without the browser blocking the
cross-origin response. Defaults to empty (no CORS) so the production
posture stays closed.

The middleware mounts before route classification and anti-abuse, so
OPTIONS preflights never charge against per-class rate-limit buckets.

`tools/dev-deploy/docker-compose.yml` opts the dev gateway into a
single allowed origin (`https://www.galaxy.lan`); local-dev keeps the
defaults because Vite proxies through the same origin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-15 07:58:14 +02:00
parent 7bce67462c
commit 1855e43699
6 changed files with 220 additions and 0 deletions
+22
View File
@@ -40,6 +40,12 @@ const (
// the keep-alive idle timeout for the public REST listener.
publicHTTPIdleTimeoutEnvVar = "GATEWAY_PUBLIC_HTTP_IDLE_TIMEOUT"
// publicHTTPCORSAllowedOriginsEnvVar names the environment variable that
// configures the comma-separated list of browser origins permitted to
// call the public REST surface. An empty value disables CORS entirely;
// requests without an Origin header still pass through normally.
publicHTTPCORSAllowedOriginsEnvVar = "GATEWAY_PUBLIC_HTTP_CORS_ALLOWED_ORIGINS"
// publicAuthUpstreamTimeoutEnvVar names the environment variable that
// configures the timeout budget used for public auth upstream calls.
publicAuthUpstreamTimeoutEnvVar = "GATEWAY_PUBLIC_AUTH_UPSTREAM_TIMEOUT"
@@ -457,6 +463,12 @@ type PublicHTTPConfig struct {
// AntiAbuse configures the public REST anti-abuse middleware.
AntiAbuse PublicHTTPAntiAbuseConfig
// CORSAllowedOrigins is the exact-match list of browser origins
// permitted to call the public REST surface. Empty disables CORS:
// requests without an Origin header continue to work, cross-origin
// requests are subject to the browser's default same-origin policy.
CORSAllowedOrigins []string
}
// BackendConfig describes the consolidated backend service the gateway
@@ -814,6 +826,16 @@ func LoadFromEnv() (Config, error) {
}
cfg.PublicHTTP.AuthUpstreamTimeout = publicAuthUpstreamTimeout
if v, ok := os.LookupEnv(publicHTTPCORSAllowedOriginsEnvVar); ok {
origins := make([]string, 0)
for part := range strings.SplitSeq(v, ",") {
if trimmed := strings.TrimSpace(part); trimmed != "" {
origins = append(origins, trimmed)
}
}
cfg.PublicHTTP.CORSAllowedOrigins = origins
}
if v, ok := os.LookupEnv(backendHTTPURLEnvVar); ok {
cfg.Backend.HTTPBaseURL = v
}