From b3dac9b324224d3b6522a2c859c150095e587567 Mon Sep 17 00:00:00 2001 From: jakeaturner Date: Wed, 20 May 2026 05:39:39 +0000 Subject: [PATCH] fix(security): match IPv6 SSRF patterns against unbracketed hostnames --- admin/app/validators/common.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/admin/app/validators/common.ts b/admin/app/validators/common.ts index 5c386ff..f98aa85 100644 --- a/admin/app/validators/common.ts +++ b/admin/app/validators/common.ts @@ -15,15 +15,18 @@ export function assertNotPrivateUrl(urlString: string): void { const parsed = new URL(urlString) const hostname = parsed.hostname.toLowerCase() + // `URL.hostname` strips the surrounding brackets from IPv6 literals + // (e.g. `http://[::1]/` → hostname `::1`), so IPv6 patterns must match + // the unbracketed form. const blockedPatterns = [ /^localhost$/, /^127\.\d+\.\d+\.\d+$/, /^0\.0\.0\.0$/, /^169\.254\.\d+\.\d+$/, // Link-local / cloud metadata - /^\[::1\]$/, - /^\[?fe80:/i, // IPv6 link-local - /^\[::ffff:/i, // IPv4-mapped IPv6 (e.g. [::ffff:7f00:1] = 127.0.0.1) - /^\[::\]$/, // IPv6 all-zeros (equivalent to 0.0.0.0) + /^::1$/, // IPv6 loopback + /^fe80:/i, // IPv6 link-local + /^::ffff:/i, // IPv4-mapped IPv6 (e.g. ::ffff:7f00:1 = 127.0.0.1) + /^::$/, // IPv6 all-zeros (equivalent to 0.0.0.0) ] if (blockedPatterns.some((re) => re.test(hostname))) {