fix: Implement WebSocket origin checking in production#3273
fix: Implement WebSocket origin checking in production#3273salignatmoandal wants to merge 3 commits intosuperplanehq:mainfrom
Conversation
Signed-off-by: Mawen Salignat-Moandal <mwnslgt@gmail.com>
90cfb6b to
cf89f62
Compare
| origin := strings.TrimSpace(r.Header.Get("Origin")) | ||
| if origin == "" { | ||
| return false | ||
| } |
There was a problem hiding this comment.
Missing Origin header blocks non-browser WebSocket clients
Medium Severity
When allowedOrigins is configured (production), connections without an Origin header are rejected. The gorilla/websocket default behavior is to accept connections when the Origin header is absent, because non-browser clients (CLI tools, server-to-server) typically don't send it. The Origin header is a browser-only CSRF mechanism, so rejecting its absence blocks legitimate programmatic clients without any security benefit — non-browser clients can trivially forge the header anyway.
|
Hey @salignatmoandal 🙌 I like to idea of this PR. I would like to see some small code cleanup before we merge it. 1/ Lets create a new file dedicated to this A closure could work well for this use case. e.g.: // pkg/server/websocket_origin_checker.go
func WebsocketOriginChecker() func(*http.Request) bool {
var allowedOrigins
// calculate allowed origins here ...
return func(r *http.Request) bool {
// verify here
}
}
// pkg/server/server.go
server := &Server{
upgrader: &websocket.Upgrader{
CheckOrigin: WebsocketOriginChecker()
}
} |
Extract websocket origin validation to a dedicated helper and wire it via CheckOrigin closure. Use BASE_URL origin in production while keeping development unrestricted.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| return false | ||
| } | ||
|
|
||
| return origin == allowedOrigin |
There was a problem hiding this comment.
Case-sensitive origin comparison breaks with non-lowercase BASE_URL
Low Severity
The origin comparison origin == allowedOrigin is case-sensitive, but RFC 6454 requires ASCII case-insensitive origin comparison, and gorilla/websocket's own default checkSameOrigin uses equalASCIIFold. Since Go's url.Parse does not normalize scheme or host to lowercase, a BASE_URL like HTTPS://App.Example.com would produce an allowedOrigin with uppercase characters, while browsers always send lowercase Origin headers — causing all WebSocket connections to be rejected in production.
|
Yep, I followed that approach. I moved the WebSocket origin logic to a dedicated file, and server.go now only wires I also simplified the config to rely on |
… regression tests
|
You have used all of your free Bugbot PR reviews. To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial. |


What
In production, WebSocket connections are now restricted to allowed origins only. In development, all origins are still accepted (no change for local dev).
Why
Previously, the server accepted WebSocket connections from any origin. That could allow a third-party site to open a WebSocket to your SuperPlane instance using the user's cookies. Restricting origins in production reduces that risk and aligns with common security practice for WebSockets.
How
allowedWebSocketOrigins()builds the list of allowed origins:APP_ENV=development): returns no list → all origins accepted (unchanged behavior).WEBSOCKET_ALLOWED_ORIGINSif set (comma-separated), otherwise derives a single origin fromBASE_URL(scheme + host).CheckOrigincallback now allows a connection only when:Originheader is in the allowed list.Configuration
BASE_URL: In production, the origin derived from this URL is allowed (e.g.https://app.example.com→https://app.example.com).WEBSOCKET_ALLOWED_ORIGINS(optional): Comma-separated list of origins when you need multiple (e.g.https://app.example.com,https://dashboard.example.com).APP_ENV=development.