fix(web): match the WebSocket origin on scheme and port, not only host

The live-update socket accepted any Origin whose host name matched, but
cookies do not separate ports or schemes, so a page served by another
service on the same host name could open it with the admin's session.
Only http(s) origins are accepted now (https when the panel terminates
TLS itself), and the port must match whenever the Host header has one.
This commit is contained in:
MHSanaei
2026-10-31 09:06:41 +00:00
parent ce532e8df7
commit b1108a9e81
2 changed files with 63 additions and 2 deletions
+21 -2
View File
@@ -34,7 +34,12 @@ func checkSameOrigin(r *http.Request) bool {
if err != nil || u.Hostname() == "" {
return false
}
host, _, err := net.SplitHostPort(r.Host)
// Cookies ignore scheme and port, so both must match too; behind a TLS-terminating
// proxy the request arrives as http and the browser's https origin still passes.
if u.Scheme != "https" && (u.Scheme != "http" || r.TLS != nil) {
return false
}
host, port, err := net.SplitHostPort(r.Host)
if err != nil {
// IPv6 literals without a port arrive as "[::1]"; net.SplitHostPort
// fails in that case while url.Hostname() returns the address without
@@ -44,7 +49,21 @@ func checkSameOrigin(r *http.Request) bool {
host = host[1 : len(host)-1]
}
}
return strings.EqualFold(u.Hostname(), host)
if !strings.EqualFold(u.Hostname(), host) {
return false
}
return port == "" || port == originPort(u)
}
// originPort is the Origin's port, or the default a browser leaves out for its scheme.
func originPort(u *url.URL) string {
if p := u.Port(); p != "" {
return p
}
if u.Scheme == "https" {
return "443"
}
return "80"
}
// WebSocketController handles the HTTP→WebSocket upgrade for real-time updates.