fix: validate WebSocket Origin header against allow-list - #17
Conversation
CheckOrigin previously accepted upgrade requests from any Origin unconditionally, allowing arbitrary third-party sites to open WebSocket connections to the relay from a visitor's browser. Adds isAllowedOrigin(), matching against the same domain patterns already trusted in guestURL() (onrender.com, railway.app, ngrok, fly.dev) plus localhost for local dev. Non-browser clients (CLI, curl) don't send an Origin header at all, so empty Origin is allowed through unconditionally. Uses url.Parse + exact-host/suffix matching rather than a plain substring check, to avoid lookalike-domain bypasses (e.g. 'evilrender.com' or 'render.com.attacker.net' would incorrectly match a naive strings.Contains(origin, "render.com") check). Note: guestURL()'s own domain detection still uses the older loose substring match — out of scope for this PR since it's display-only, not a security boundary, but flagging as a candidate follow-up for consistency. Adds TestIsAllowedOrigin covering allowed domains, localhost, non-browser (empty) origin, and three spoofing attempts. Fixes VishalRaut2106#13
Lowercase the parsed hostname before comparison (defensive; browsers already normalize Origin casing, but this removes the assumption). Log a warning when Origin fails to parse, for debugging visibility. Other review suggestions (scheme/port validation, exporting the function, caching parsed URLs) were considered and intentionally not applied — see PR comment for reasoning.
|
Thanks for the review! Addressed the two genuinely useful points (case-insensitive hostname comparison, logging on parse failure) in the latest commit. On the rest:
Let me know if you'd like any of the deferred ones addressed anyway. |
There was a problem hiding this comment.
Pull request overview
Tightens the server’s WebSocket security boundary by validating the Origin header against an allow-list, replacing the previous unconditional CheckOrigin acceptance.
Changes:
- Add
isAllowedOriginand wire it into the WebSocket upgraderCheckOrigincallback. - Add unit tests covering allow-listed and disallowed Origin values.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| cmd/server/main.go | Introduces Origin allow-list validation and applies it to WebSocket upgrade checks. |
| cmd/server/main_test.go | Adds test coverage for the new Origin allow-list logic. |
Comments suppressed due to low confidence (1)
cmd/server/main.go:123
guestURLusesstrings.Contains(reqHost, "render.com"), which is broader than intended and doesn’t match theonrender.comallow-list used byisAllowedOrigin. It can also treat hosts likeevilrender.comas “trusted” and switch to https. Consider normalizing the host (strip port, lower-case) and using exact/suffix matching foronrender.com(and the other allowed domains) instead of substring matching.
func guestURL(code string, reqHost string) string {
scheme := "http"
if strings.Contains(reqHost, "render.com") || strings.Contains(reqHost, "railway.app") || strings.Contains(reqHost, "ngrok") || strings.Contains(reqHost, "fly.dev") {
scheme = "https"
} else if strings.Contains(reqHost, "localhost") {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
Hey @HimanshuPathak2725 , great work on this! The code and tests look solid. One quick request: I recently set up a custom domain for the project (gatekeeper.vishalraut.me). Could you please add "vishalraut.me" to the allowed domains list in isAllowedOrigin() and the tests? Once that's added, I'll merge this right away! |
|
Thanks! Added support for the new Ran:
Both pass successfully. |
Feedback on Code ChangesBugs and Security Issues
Performance Bottlenecks
Idiomatic Go Conventions
Robust Error Handling
Additional Improvements
Summary of Actions
By addressing these points, the code will be more secure, performant, idiomatic, and robust. |
VishalRaut2106
left a comment
There was a problem hiding this comment.
Hey @HimanshuPathak2725 , great work
VishalRaut2106
left a comment
There was a problem hiding this comment.
Hey Himanshu, thanks for wrapping up Finding #3!
The Ping/Pong heartbeat implementation is super clean, and putting a hard 32KB read limit on the WebSockets completely shuts down the OOM/DoS vector. Really appreciate the rigorous unit tests as well.
Merging this in now—thanks again for the massive security improvements across the board!
|
@VishalRaut2106 Labels ig.....!! |
Type of change
Checklist
go build ./...passes locallygo vet ./...passes locallyno user-facing/API change.