Files
kandev/apps/backend/internal/gateway/websocket/origin_test.go
T
João Salavisa 3746a30126 fix: enforce origin validation on websocket upgrades (#1698)
* fix: enforce origin validation on websocket upgrades

The gateway /ws upgrader hardcoded CheckOrigin to true, so any web page
could open a socket to the local backend and drive session/shell actions
(cross-site WebSocket hijacking → RCE on the host).

Both upgraders now share checkWebSocketOrigin: no Origin header is
allowed (non-browser clients), same-hostname and loopback↔loopback
origins are allowed, everything else is rejected. Replaces the terminal
handler's prefix-based check, which accepted origins like
http://localhost.attacker.tld.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor: share origin trust policy between cors and websocket checks

Review feedback on #1698: the CORS middleware and the WebSocket upgrader
carried two copies of the same origin policy, which could silently
diverge. Both now delegate to httpmw.AllowedOrigin, which also rejects
URL components that never appear in a browser Origin header (userinfo,
path, query, fragment, opaque data).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-15 01:13:46 +01:00

41 lines
1.0 KiB
Go

package websocket
import (
"net/http"
"testing"
)
// The exhaustive origin policy table lives in internal/common/httpmw; this
// covers the upgrade-specific wrapper semantics.
func TestCheckWebSocketOrigin(t *testing.T) {
tests := []struct {
name string
origin string
host string
want bool
}{
{"no origin allows non-browser clients", "", "example.com", true},
{"same hostname", "https://example.com", "example.com:8080", true},
{"loopback cross port", "http://localhost:3000", "127.0.0.1:8080", true},
{"foreign origin", "https://evil.com", "example.com", false},
{"malformed origin", "not-a-url", "example.com", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &http.Request{
Header: http.Header{},
Host: tt.host,
}
if tt.origin != "" {
r.Header.Set("Origin", tt.origin)
}
if got := checkWebSocketOrigin(r); got != tt.want {
t.Errorf("checkWebSocketOrigin(origin=%q, host=%q) = %v, want %v",
tt.origin, tt.host, got, tt.want)
}
})
}
}