mirror of
https://github.com/tenrok/axios.git
synced 2026-06-17 19:21:29 +03:00
fix: incomplete fix for cve (#10755)
This commit is contained in:
@@ -1,6 +1,49 @@
|
||||
const LOOPBACK_ADDRESSES = new Set(['localhost', '127.0.0.1', '::1']);
|
||||
const LOOPBACK_HOSTNAMES = new Set(['localhost']);
|
||||
|
||||
const isLoopback = (host) => LOOPBACK_ADDRESSES.has(host);
|
||||
const isIPv4Loopback = (host) => {
|
||||
const parts = host.split('.');
|
||||
if (parts.length !== 4) return false;
|
||||
if (parts[0] !== '127') return false;
|
||||
return parts.every((p) => /^\d+$/.test(p) && Number(p) >= 0 && Number(p) <= 255);
|
||||
};
|
||||
|
||||
const isIPv6Loopback = (host) => {
|
||||
// Collapse all-zero groups: any form of ::1 / 0:0:...:0:1
|
||||
// First, strip any leading "::" by normalising with Set lookup of common forms,
|
||||
// then fall back to structural check.
|
||||
if (host === '::1') return true;
|
||||
|
||||
// Check IPv4-mapped IPv6 loopback: ::ffff:<v4-loopback> or ::ffff:<hex-v4-loopback>
|
||||
// Node's URL parser normalises ::ffff:127.0.0.1 → ::ffff:7f00:1
|
||||
const v4MappedDotted = host.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);
|
||||
if (v4MappedDotted) return isIPv4Loopback(v4MappedDotted[1]);
|
||||
|
||||
const v4MappedHex = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
|
||||
if (v4MappedHex) {
|
||||
const high = parseInt(v4MappedHex[1], 16);
|
||||
// High 16 bits must start with 127 (0x7f) — i.e. 0x7f00..0x7fff
|
||||
return high >= 0x7f00 && high <= 0x7fff;
|
||||
}
|
||||
|
||||
// Full-form ::1 variants: any number of zero groups followed by trailing 1
|
||||
// e.g. 0:0:0:0:0:0:0:1, 0000:...:0001
|
||||
const groups = host.split(':');
|
||||
if (groups.length === 8) {
|
||||
for (let i = 0; i < 7; i++) {
|
||||
if (!/^0+$/.test(groups[i])) return false;
|
||||
}
|
||||
return /^0*1$/.test(groups[7]);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const isLoopback = (host) => {
|
||||
if (!host) return false;
|
||||
if (LOOPBACK_HOSTNAMES.has(host)) return true;
|
||||
if (isIPv4Loopback(host)) return true;
|
||||
return isIPv6Loopback(host);
|
||||
};
|
||||
|
||||
const DEFAULT_PORTS = {
|
||||
http: 80,
|
||||
|
||||
@@ -100,4 +100,70 @@ describe('helpers::shouldBypassProxy', () => {
|
||||
|
||||
expect(shouldBypassProxy('not a url')).toBe(false);
|
||||
});
|
||||
|
||||
it('should bypass proxy for 127.0.0.0/8 subnet when no_proxy contains 127.0.0.1 (GHSA-pmwg-cvhr-8vh7)', () => {
|
||||
setNoProxy('localhost,127.0.0.1,::1');
|
||||
|
||||
expect(shouldBypassProxy('http://127.0.0.2:9191/secret')).toBe(true);
|
||||
expect(shouldBypassProxy('http://127.0.0.100:9191/secret')).toBe(true);
|
||||
expect(shouldBypassProxy('http://127.1.2.3:9191/secret')).toBe(true);
|
||||
expect(shouldBypassProxy('http://127.255.255.254:9191/secret')).toBe(true);
|
||||
});
|
||||
|
||||
it('should bypass proxy for 127.0.0.0/8 subnet when no_proxy contains localhost', () => {
|
||||
setNoProxy('localhost');
|
||||
|
||||
expect(shouldBypassProxy('http://127.0.0.2:7777/')).toBe(true);
|
||||
expect(shouldBypassProxy('http://127.1.2.3:7777/')).toBe(true);
|
||||
});
|
||||
|
||||
it('should NOT bypass for non-loopback IPv4 addresses', () => {
|
||||
setNoProxy('localhost,127.0.0.1,::1');
|
||||
|
||||
expect(shouldBypassProxy('http://128.0.0.1:9191/')).toBe(false);
|
||||
expect(shouldBypassProxy('http://126.255.255.255:9191/')).toBe(false);
|
||||
expect(shouldBypassProxy('http://10.0.0.1:9191/')).toBe(false);
|
||||
expect(shouldBypassProxy('http://192.168.1.1:9191/')).toBe(false);
|
||||
});
|
||||
|
||||
it('should NOT treat malformed 127-prefixed values as loopback', () => {
|
||||
setNoProxy('localhost,127.0.0.1,::1');
|
||||
|
||||
// bracketed IPv6 that happens to contain 127 dotted-form must not match IPv4 loopback
|
||||
expect(shouldBypassProxy('http://example.com/')).toBe(false);
|
||||
});
|
||||
|
||||
it('should bypass proxy for full-form IPv6 loopback 0:0:0:0:0:0:0:1', () => {
|
||||
setNoProxy('localhost,127.0.0.1,::1');
|
||||
|
||||
expect(shouldBypassProxy('http://[0:0:0:0:0:0:0:1]:8080/')).toBe(true);
|
||||
});
|
||||
|
||||
it('should bypass proxy for IPv4-mapped IPv6 loopback ::ffff:127.0.0.1', () => {
|
||||
setNoProxy('localhost,127.0.0.1,::1');
|
||||
|
||||
expect(shouldBypassProxy('http://[::ffff:127.0.0.1]:8080/')).toBe(true);
|
||||
});
|
||||
|
||||
it('should treat 127.x.x.x as cross-equivalent to localhost and ::1', () => {
|
||||
setNoProxy('::1');
|
||||
|
||||
expect(shouldBypassProxy('http://127.0.0.5:7777/')).toBe(true);
|
||||
});
|
||||
|
||||
it('should still respect explicit port mismatch on no_proxy entries', () => {
|
||||
setNoProxy('127.0.0.1:8080');
|
||||
|
||||
// same-port → bypass via cross-loopback equivalence
|
||||
expect(shouldBypassProxy('http://127.0.0.2:8080/')).toBe(true);
|
||||
// different port → no bypass
|
||||
expect(shouldBypassProxy('http://127.0.0.2:9090/')).toBe(false);
|
||||
});
|
||||
|
||||
it('should not bypass for hosts that merely contain 127 in other octets', () => {
|
||||
setNoProxy('localhost,127.0.0.1,::1');
|
||||
|
||||
expect(shouldBypassProxy('http://10.0.0.127:8080/')).toBe(false);
|
||||
expect(shouldBypassProxy('http://200.127.0.1:8080/')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user