fix(connection): judge an entry's explicit port from the parsed URL, not the raw string

WHATWG trimming strips stray whitespace before parsing, so 'host:port ' passed
the load assert while the raw-string port regex read it as port-less —
broadening an exact-port grant to every port on that hostname. The explicit-
port judgment now reads URL parses under both special schemes (:80/:443 stay
explicit), and the load assert refuses whitespace outright.
This commit is contained in:
creatixchu
2026-07-28 16:19:31 +08:00
parent b9cbe2f029
commit 34518cb012
2 changed files with 29 additions and 4 deletions

View File

@@ -45,16 +45,29 @@ function parseAuthority(authority: string): URL | undefined {
* `host:port`) and nothing else. WHATWG parsing would quietly read a hostname
* out of `harness.internal/path` or `user@harness.internal` — a typo must fail
* the load loudly instead of authorizing its hostname or being ignored until
* requests 403. The delimiter test refuses every URL part beyond the authority
* (path, backslash path, query, fragment, userinfo); IPv6 brackets use none of
* requests 403. The character test refuses every URL part beyond the authority
* (path, backslash path, query, fragment, userinfo) and all whitespace, which
* WHATWG trimming would otherwise strip silently; IPv6 brackets use none of
* them.
* @param entry - the configured value, verbatim.
*/
export function assertTrustedAuthority(entry: string): void {
if (parseAuthority(entry) !== undefined && !/[/\\?#@]/.test(entry)) return
if (parseAuthority(entry) !== undefined && !/[/\\?#@\s]/.test(entry)) return
throw new Error(`client-connection: trustedHosts entry ${JSON.stringify(entry)} is not a bare host[:port] authority`)
}
/**
* Whether the parsed authority carries an explicit port: judged from URL
* parses under both special schemes (their default ports differ, so `:80` and
* `:443` still count as explicit), never from the raw string, where WHATWG
* trimming of stray whitespace would misread `host:port ` as port-less and
* broaden an exact-port grant to every port.
*/
function hasExplicitPort(entry: string, entryUrl: URL): boolean {
// An authority that parsed under http cannot fail under https.
return entryUrl.port !== '' || new URL(`https://${entry}`).port !== ''
}
/**
* Whether the request authority matches a `trustedHosts` entry. An entry with
* an explicit port matches that exact authority; a port-less entry matches the
@@ -66,7 +79,7 @@ function isTrustedAuthority(hostUrl: URL, trustedHosts: readonly string[]): bool
return trustedHosts.some((entry) => {
const entryUrl = parseAuthority(entry)
if (entryUrl === undefined) return false
return /:\d+$/.test(entry)
return hasExplicitPort(entry, entryUrl)
? entryUrl.host === hostUrl.host
: entryUrl.hostname === hostUrl.hostname
})