fix(metrics): reject non-loopback callers on metrics endpoints (#2222)

This commit is contained in:
Hampus authored and GitHub committed 2026-08-31 00:23:00 +02:00
1 parent 0a920def2b
commit 50a17b6263
6 files changed
+231 -22

No files matched your search

+24
View File
@@ -316,6 +316,30 @@ export function isPublicIpAddress(ip: string): boolean {
return isPublicIpv6Address(parsed.normalized);
}
function isLoopbackIpv4Address(address: string): boolean {
const octets = parseIpv4Octets(address);
if (!octets) {
return false;
}
return isIpv4InCidr(octets, 0x7f000000, 8);
}
export function isLoopbackIpAddress(ip: string): boolean {
const parsed = parseIpAddress(ip);
if (!parsed) {
return false;
}
if (parsed.family === 'ipv4') {
return isLoopbackIpv4Address(parsed.normalized);
}
const mappedIpv4 = getIpv4MappedIpv6(parsed.normalized);
if (mappedIpv4) {
return isLoopbackIpv4Address(mappedIpv4);
}
const groups = expandIpv6ToGroups(parsed.normalized);
return groups.length === 8 && groups.slice(0, 7).every((group) => group === '0000') && groups[7] === '0001';
}
export function isSameIpDecisionMatch(left: string | null | undefined, right: string | null | undefined): boolean {
if (!left || !right) {
return false;
@@ -4,6 +4,7 @@ import {
getIpNetworkKey,
getSameIpDecisionKey,
getSubnet,
isLoopbackIpAddress,
isPublicIpAddress,
isSameIpDecisionMatch,
isValidIp,
@@ -119,6 +120,36 @@ describe('maskIpForDisplay', () => {
});
});
describe('isLoopbackIpAddress', () => {
it('accepts IPv4 loopback addresses', () => {
expect(isLoopbackIpAddress('127.0.0.1')).toBe(true);
expect(isLoopbackIpAddress('127.0.0.2')).toBe(true);
expect(isLoopbackIpAddress('127.255.255.254')).toBe(true);
});
it('accepts IPv6 loopback and its IPv4-mapped form', () => {
expect(isLoopbackIpAddress('::1')).toBe(true);
expect(isLoopbackIpAddress('[::1]')).toBe(true);
expect(isLoopbackIpAddress('0:0:0:0:0:0:0:1')).toBe(true);
expect(isLoopbackIpAddress('::ffff:127.0.0.1')).toBe(true);
expect(isLoopbackIpAddress('::ffff:7f00:1')).toBe(true);
});
it('rejects non-loopback addresses', () => {
expect(isLoopbackIpAddress('8.8.8.8')).toBe(false);
expect(isLoopbackIpAddress('10.0.0.5')).toBe(false);
expect(isLoopbackIpAddress('172.18.0.4')).toBe(false);
expect(isLoopbackIpAddress('192.168.1.1')).toBe(false);
expect(isLoopbackIpAddress('128.0.0.1')).toBe(false);
expect(isLoopbackIpAddress('126.255.255.255')).toBe(false);
expect(isLoopbackIpAddress('::2')).toBe(false);
expect(isLoopbackIpAddress('fe80::1')).toBe(false);
expect(isLoopbackIpAddress('::ffff:8.8.8.8')).toBe(false);
});
it('rejects invalid values', () => {
expect(isLoopbackIpAddress('not-an-ip')).toBe(false);
expect(isLoopbackIpAddress('')).toBe(false);
});
});
describe('isPublicIpAddress', () => {
it('accepts public unicast addresses', () => {
expect(isPublicIpAddress('8.8.8.8')).toBe(true);