From a32167addb4f6ea755ba9a7a8934c1c9e7527035 Mon Sep 17 00:00:00 2001 From: Nitesh Date: Sun, 2 Aug 2026 16:24:47 +0530 Subject: [PATCH] fix: normalize IPv6 hostnames in preferredAuthUrl loopback filter The preferredAuthUrl function previously compared raw URL hostnames against bracketed strings like '[::1]' and '[::ffff:7f...'. Depending on URL parser implementations and IPv6 formats, bracket presence and IPv6 string representations (such as ::ffff:127.0.0.1 or expanded zero segments) could bypass the loopback check. This commit introduces an isLoopbackHost helper that strips outer brackets and checks IPv4/IPv6 loopback, unspecified, IPv4-mapped, and IPv4-compatible IP forms, preventing loopback URLs from being selected as authentication endpoints. --- sdk/typescript/src/auth.ts | 58 ++++++++++++++++++++++------ sdk/typescript/tests-ts/auth.test.ts | 2 + 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/sdk/typescript/src/auth.ts b/sdk/typescript/src/auth.ts index c8e75547..774f9b17 100644 --- a/sdk/typescript/src/auth.ts +++ b/sdk/typescript/src/auth.ts @@ -465,18 +465,8 @@ function preferredAuthUrl(value: string): string | null { )) { const url = match[0].replace(/[.,;:!?)\]}]+$/, ""); try { - const hostname = new URL(url).hostname.toLowerCase().replace(/\.$/, ""); - if ( - hostname !== "localhost" && - !hostname.endsWith(".localhost") && - !(isIP(hostname) === 4 && hostname.startsWith("127.")) && - hostname !== "0.0.0.0" && - hostname !== "[::1]" && - hostname !== "[::]" && - hostname !== "[::ffff:0:0]" && - !hostname.startsWith("[::ffff:7f") && - !hostname.startsWith("[::7f") - ) { + const hostname = new URL(url).hostname; + if (!isLoopbackHost(hostname)) { return url; } } catch { @@ -486,6 +476,50 @@ function preferredAuthUrl(value: string): string | null { return null; } +function isLoopbackHost(hostname: string): boolean { + const host = hostname + .toLowerCase() + .replace(/\.$/, "") + .replace(/^\[|\]$/g, ""); + if (host === "localhost" || host.endsWith(".localhost")) { + return true; + } + const ipVersion = isIP(host); + if (ipVersion === 4) { + return host === "0.0.0.0" || host.startsWith("127."); + } + if (ipVersion === 6) { + if ( + host === "::1" || + host === "0:0:0:0:0:0:0:1" || + host === "::" || + host === "0:0:0:0:0:0:0:0" + ) { + return true; + } + if ( + host === "::ffff:0.0.0.0" || + host === "::ffff:0:0" || + host === "0:0:0:0:0:ffff:0:0" + ) { + return true; + } + if ( + host.startsWith("::ffff:127.") || + host.startsWith("::127.") || + host.startsWith("0:0:0:0:0:ffff:127.") || + host.startsWith("0:0:0:0:0:0:127.") || + host.startsWith("::ffff:7f") || + host.startsWith("::7f") || + host.startsWith("0:0:0:0:0:ffff:7f") || + host.startsWith("0:0:0:0:0:0:7f") + ) { + return true; + } + } + return false; +} + function userCodeFromOutput(value: string): string | null { const output = plainTerminalText(value); return ( diff --git a/sdk/typescript/tests-ts/auth.test.ts b/sdk/typescript/tests-ts/auth.test.ts index 08a2e434..7621b59a 100644 --- a/sdk/typescript/tests-ts/auth.test.ts +++ b/sdk/typescript/tests-ts/auth.test.ts @@ -56,6 +56,8 @@ if (args.join(" ") === "login --with-api-key") { console.error("Listening on http://[::1]:1455."); console.error("Listening on http://[::]:1455."); console.error("Listening on http://[::ffff:127.0.0.1]:1455."); + console.error("Listening on http://[::ffff:7f00:1]:1455."); + console.error("Listening on http://[0:0:0:0:0:0:0:1]:1455."); console.error("Listening on http://[::ffff:0.0.0.0]:1455."); console.error("Listening on http://[::127.0.0.1]:1455."); console.error("Open \\u001b[32mhttps://127.auth.example.test/device\\u001b[0m");