3746a30126
* 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>
41 lines
1.0 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|