diff --git a/server/src/__tests__/invite-test-resolution-route.test.ts b/server/src/__tests__/invite-test-resolution-route.test.ts index 1376a6e24f4..63857948be9 100644 --- a/server/src/__tests__/invite-test-resolution-route.test.ts +++ b/server/src/__tests__/invite-test-resolution-route.test.ts @@ -87,6 +87,8 @@ describe("GET /invites/:token/test-resolution", () => { ["localhost", "http://localhost:3100/api/health", "127.0.0.1"], ["IPv4 loopback", "http://127.0.0.1:3100/api/health", "127.0.0.1"], ["IPv6 loopback", "http://[::1]:3100/api/health", "::1"], + ["IPv4-mapped IPv6 loopback hex", "http://[::ffff:7f00:1]/api/health", "::ffff:7f00:1"], + ["IPv4-mapped IPv6 RFC1918 hex", "http://[::ffff:c0a8:101]/api/health", "::ffff:c0a8:101"], ["RFC1918 10/8", "http://10.0.0.5/api/health", "10.0.0.5"], ["RFC1918 172.16/12", "http://172.16.10.5/api/health", "172.16.10.5"], ["RFC1918 192.168/16", "http://192.168.1.10/api/health", "192.168.1.10"], diff --git a/server/src/routes/access.ts b/server/src/routes/access.ts index bf9fbd48629..c4f414d3d35 100644 --- a/server/src/routes/access.ts +++ b/server/src/routes/access.ts @@ -2166,10 +2166,24 @@ function isPrivateOrReservedIpv4(address: string) { return false; } +function parseMappedIpv4Hex(address: string) { + const match = address.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/); + if (!match) return null; + const hi = Number.parseInt(match[1]!, 16); + const lo = Number.parseInt(match[2]!, 16); + if (!Number.isInteger(hi) || !Number.isInteger(lo)) return null; + return `${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff}`; +} + function isPrivateOrReservedIpv6(address: string) { const lower = address.toLowerCase(); - const mappedIpv4 = lower.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/); - if (mappedIpv4?.[1]) return isPrivateOrReservedIpv4(mappedIpv4[1]); + if (lower.startsWith("::ffff:")) { + const mappedIpv4 = lower.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/); + if (mappedIpv4?.[1]) return isPrivateOrReservedIpv4(mappedIpv4[1]); + const mappedIpv4Hex = parseMappedIpv4Hex(lower); + if (mappedIpv4Hex) return isPrivateOrReservedIpv4(mappedIpv4Hex); + return true; + } if (lower === "::" || lower === "::1") return true; if (lower.startsWith("fc") || lower.startsWith("fd")) return true; if (/^fe[89ab]/.test(lower)) return true;