Both origin allowlists compile config entries with regexp.MustCompile and match with MatchString, which is a substring match unless the operator remembers to anchor:
auth/cors.go:45 → compileAllowedCORSOrigins, used by AllowOriginFunc
api/stream/stream.go:183 → compileAllowedWebSocketOrigins, used by Upgrader.CheckOrigin
So a natural-looking value matches any hostname that merely contains it:
$ GOTIFY_SERVER_CORS_ALLOWORIGINS='https://gotify.example.com' gotify serve
$ curl -sD- -o/dev/null -H 'Origin: https://gotify.example.com' localhost:8792/version
Access-Control-Allow-Origin: https://gotify.example.com
$ curl -sD- -o/dev/null -H 'Origin: https://gotify.example.com.evil.net' localhost:8792/version
Access-Control-Allow-Origin: https://gotify.example.com.evil.net # <-- allowed
$ curl -sD- -o/dev/null -H 'Origin: http://evil.net' localhost:8792/version
(no ACAO header)
Impact is limited — AllowCredentials is never set, so the cookie is not sent cross-origin, and an attacker still needs a token to read anything — but the allowlist does not mean what an operator reading the env var name would assume, and the same footgun applies to GOTIFY_SERVER_STREAM_ALLOWEDORIGINS on the websocket.
Options, in rough order of preference:
- Anchor automatically: wrap each entry as
^(?:…)$ before compiling (breaks any existing config that relies on a trailing .*, so it would need a release note).
- Keep the regex but warn at startup when an entry is not anchored.
- Document the anchoring requirement in the config docs, at minimum.
Happy to send a PR for whichever you prefer.
Both origin allowlists compile config entries with
regexp.MustCompileand match withMatchString, which is a substring match unless the operator remembers to anchor:auth/cors.go:45→compileAllowedCORSOrigins, used byAllowOriginFuncapi/stream/stream.go:183→compileAllowedWebSocketOrigins, used byUpgrader.CheckOriginSo a natural-looking value matches any hostname that merely contains it:
Impact is limited —
AllowCredentialsis never set, so the cookie is not sent cross-origin, and an attacker still needs a token to read anything — but the allowlist does not mean what an operator reading the env var name would assume, and the same footgun applies toGOTIFY_SERVER_STREAM_ALLOWEDORIGINSon the websocket.Options, in rough order of preference:
^(?:…)$before compiling (breaks any existing config that relies on a trailing.*, so it would need a release note).Happy to send a PR for whichever you prefer.