From 89d3eb3b0d1480e70127c676553b3b5a32aa43d4 Mon Sep 17 00:00:00 2001 From: Ayush7614 Date: Wed, 2 Sep 2026 14:13:26 +0530 Subject: [PATCH] fix(agent-endpoint): correctly handle bracketed IPv6 hosts with port in allowed list AGENT_ENDPOINT_ALLOWED_HOSTS is a list of hosts optionally with a port. For IPv6 the host is bracketed as [::1]:8080. The previous normalization did host.replace(/^\[/, "").replace(/\]$/, ""), which stripped the opening bracket but left the closing bracket when a port followed: "[::1]:8080" became "::1]:8080". The check in endpoint.ts did a different strip, producing "::1:8080", so the two sides never matched and a named IPv6 endpoint was always refused. Similarly, endpoint.ts derived host as url.host with a one-sided replace, which for Bun's URL.hostname="[::1]" produced a mismatched form versus the stored entry. Normalize both sides consistently: store the host as hostname (without brackets) plus optional :port, and derive the same from URL.hostname/ URL.port. This makes [::1]:8080 pin that port (and [::1] cover any port, per "host without port covers any port"), and fixes the bracket stripping for the allowed-host path. --- server/src/agents/endpoint.ts | 2 +- server/src/config.ts | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/server/src/agents/endpoint.ts b/server/src/agents/endpoint.ts index 1a7656b37..39c979d4c 100644 --- a/server/src/agents/endpoint.ts +++ b/server/src/agents/endpoint.ts @@ -49,7 +49,7 @@ function namedAsAllowed( return false; } const hostname = url.hostname.toLowerCase().replace(/^\[|\]$/g, ""); - const host = url.host.toLowerCase().replace(/^\[/, "").replace(/\]/, ""); + const host = url.port ? `${hostname}:${url.port}` : hostname; return allowedHosts.has(host) || allowedHosts.has(hostname); } diff --git a/server/src/config.ts b/server/src/config.ts index 1c235f9c3..09c925986 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -634,11 +634,25 @@ function agentEndpointAllowedHosts( `AGENT_ENDPOINT_ALLOWED_HOSTS entry "${entry}" must name one host. Patterns are not accepted: list each address instead.`, ); } - hosts.add(host.replace(/^\[/, "").replace(/\]$/, "")); + hosts.add(normalizeAllowedHost(host)); } return hosts; } +function normalizeAllowedHost(host: string): string { + // IPv6 is bracketed as [host] or [host]:port. Strip the brackets and keep the port. + if (host.startsWith("[")) { + const close = host.indexOf("]"); + if (close === -1) return host.replace(/^\[/, "").replace(/\]$/, ""); + const ipv6 = host.slice(1, close).toLowerCase(); + const rest = host.slice(close + 1); + if (!rest) return ipv6; + if (rest.startsWith(":")) return `${ipv6}${rest.toLowerCase()}`; + return `${ipv6}${rest.toLowerCase()}`; + } + return host; +} + function privateHostsAllowed(environment: Environment): boolean { if (optional(environment, "AGENT_COMPUTER_ALLOW_PRIVATE_HOSTS") !== "true") { return false;