fix(plugins): parse IPv6 hosts correctly in the websocket allowlist

isHostAllowed cut the host at the last colon, which mangles an IPv6 literal:
"[::1]:8080" became "[::1]" and "[::1]" became "[:". A plugin manifest could
therefore never allow an IPv6 host. It now uses net.SplitHostPort, falling back to
unwrapping the brackets when there is no port.
This commit is contained in:
Deluan 2026-08-19 23:43:51 -04:00
parent 7e5fc1c8a6
commit dafd57ca25
2 changed files with 21 additions and 2 deletions

View File

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"maps"
"net"
"net/http"
"net/url"
"strings"
@ -245,8 +246,11 @@ func (s *webSocketServiceImpl) getConnection(connectionID string) (*wsConnection
func (s *webSocketServiceImpl) isHostAllowed(host string) bool {
// Strip port from host if present
hostWithoutPort := host
if idx := strings.LastIndex(host, ":"); idx != -1 {
hostWithoutPort = host[:idx]
if h, _, err := net.SplitHostPort(host); err == nil {
hostWithoutPort = h
} else if h, ok := strings.CutPrefix(host, "["); ok {
// Bracketed IPv6 literal with no port
hostWithoutPort = strings.TrimSuffix(h, "]")
}
for _, pattern := range s.requiredHosts {

View File

@ -614,3 +614,18 @@ func findWebSocketService(m *Manager, pluginName string) *webSocketServiceImpl {
}
return nil
}
var _ = Describe("isHostAllowed", func() {
DescribeTable("strips the port before matching",
func(allowed []string, host string, expected bool) {
svc := &webSocketServiceImpl{requiredHosts: allowed}
Expect(svc.isHostAllowed(host)).To(Equal(expected))
},
Entry("hostname with port", []string{"example.com"}, "example.com:8080", true),
Entry("hostname without port", []string{"example.com"}, "example.com", true),
Entry("IPv4 with port", []string{"127.0.0.1"}, "127.0.0.1:4533", true),
Entry("IPv6 with port", []string{"::1"}, "[::1]:8080", true),
Entry("IPv6 without port", []string{"::1"}, "[::1]", true),
Entry("IPv6 not in the list", []string{"::2"}, "[::1]:8080", false),
)
})