From d8768afb5e0e04bfd15c3838d3edf67cb2794d29 Mon Sep 17 00:00:00 2001 From: ddemydenko Date: Tue, 14 Oct 2025 18:17:45 +0200 Subject: [PATCH 1/2] - Added isOctalFormat with regex to detect octal IPv4 representations - Updated isPrivate to normalize octal-like IPs - Made isLoopback IPv6-mapped detection case-insensitive - Improved loopback matching to include zero-padded IPv6 forms --- lib/ip.js | 15 +++++++--- test/api-test.js | 76 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/lib/ip.js b/lib/ip.js index 9022443..64d96f6 100644 --- a/lib/ip.js +++ b/lib/ip.js @@ -84,6 +84,7 @@ ip.toString = function (buff, offset, length) { const ipv4Regex = /^(\d{1,3}\.){3,3}\d{1,3}$/; const ipv6Regex = /^(::)?(((\d{1,3}\.){3}(\d{1,3}){1})?([0-9a-f]){0,4}:{0,2}){1,8}(::)?$/i; +const octalRegex = /^0[0-7]+(\.[0-7]+){0,3}$/; ip.isV4Format = function (ip) { return ipv4Regex.test(ip); @@ -93,6 +94,10 @@ ip.isV6Format = function (ip) { return ipv6Regex.test(ip); }; +ip.isOctalFormat = function (ip) { + return octalRegex.test(ip); +}; + function _normalizeFamily(family) { if (family === 4) { return 'ipv4'; @@ -312,7 +317,7 @@ ip.isPrivate = function (addr) { } // ensure the ipv4 address is valid - if (!ip.isV6Format(addr)) { + if (!ip.isV6Format(addr) || ip.isOctalFormat(addr)) { const ipl = ip.normalizeToLong(addr); if (ipl < 0) { throw new Error('invalid ipv4 address'); @@ -343,12 +348,14 @@ ip.isLoopback = function (addr) { addr = ip.fromLong(Number(addr)); } - return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/ - .test(addr) + return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/i.test(addr) + // covers 127.* short forms like 127.1 or 127.0.1 + || /^127(\.|$)/.test(addr) || /^0177\./.test(addr) || /^0x7f\./i.test(addr) || /^fe80::1$/i.test(addr) - || /^::1$/.test(addr) + // ::1, also covers padding zeros 0:0:0:0:0:0:0:1, 000:0:0000::01 + || /^(0*:)*0*1$/i.test(addr) || /^::$/.test(addr); }; diff --git a/test/api-test.js b/test/api-test.js index 0db838d..fe6d82f 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -279,6 +279,10 @@ describe('IP library for node.js', () => { assert.equal(ip.normalizeToLong('010.0.0.01'), 134217729); }); + it('should correctly handle octal notation "01200034567"', () => { + assert.equal(ip.normalizeToLong('01200034567'), 167786871); + }); + // Testing invalid inputs it('should return -1 for an invalid address "256.100.50.25"', () => { assert.equal(ip.normalizeToLong('256.100.50.25'), -1); @@ -378,10 +382,22 @@ describe('IP library for node.js', () => { }); describe('isLoopback() method', () => { - describe('127.0.0.1', () => { - it('should respond with true', () => { + describe('127.x loopback range', () => { + it('should return true for 127.0.0.1', () => { assert.ok(ip.isLoopback('127.0.0.1')); }); + + it('should return true for 127.1', () => { + assert.ok(ip.isLoopback('127.1')); + }); + + it('should return true for 127.0.1', () => { + assert.ok(ip.isLoopback('127.0.1')); + }); + + it('should return true for 127.255.255.255 (end of loopback range)', () => { + assert.ok(ip.isLoopback('127.255.255.255')); + }); }); describe('127.8.8.8', () => { @@ -402,7 +418,19 @@ describe('IP library for node.js', () => { }); }); - describe('::1', () => { + describe('::1 loopback range', () => { + it('should return true for ::1', () => { + assert.ok(ip.isLoopback('::1')); + }); + + it('should return true for 0:0:0:0:0:0:0:1', () => { + assert.ok(ip.isLoopback('0:0:0:0:0:0:0:1')); + }); + + it('should return true for 000:0:0000::01', () => { + assert.ok(ip.isLoopback('000:0:0000::01')); + }); + it('should respond with true', () => { assert.ok(ip.isLoopback('::1')); }); @@ -506,4 +534,46 @@ describe('IP library for node.js', () => { it('should return false for "192.168.1.1"', () => { assert.equal(ip.isLoopback('192.168.1.1'), false); }); + + describe('isOctalFormat() method', () => { + it('should return true for "01200034567"', () => { + assert.equal(ip.isOctalFormat('01200034567'), true); + }); + + it('should return true for "0177.0.0.1"', () => { + assert.equal(ip.isOctalFormat('0177.0.0.1'), true); + }); + + it('should return true for "0177.0.1"', () => { + assert.equal(ip.isOctalFormat('0177.0.1'), true); + }); + + it('should return true for "0177.1"', () => { + assert.equal(ip.isOctalFormat('0177.1'), true); + }); + + it('should return true for "010.0.0.01"', () => { + assert.equal(ip.isOctalFormat('010.0.0.01'), true); + }); + + it('should return true for "019.0.0.1"', () => { + assert.equal(ip.isOctalFormat('019.0.0.1'), false); + }); + }); + + describe('isPublic (CVE-2024-29415 cases)', () => { + const nonPublicCases = [ + '127.1', // valid ip4 loopback + '01200034567', // valid octal format + '012.1.2.3', // leading-zero octal fragments + '000:0:0000::01', // zero-padding IPv6 representation + '::fFFf:127.0.0.1' // IPv6 case insensitive case + ]; + + nonPublicCases.forEach((ipAddr) => + it(`${ipAddr} should be non-public`, () => { + assert.equal(ip.isPublic(ipAddr), false); + }) + ); + }); }); From daf21f3f79b6ee67139d1fafc135c21d7878ba60 Mon Sep 17 00:00:00 2001 From: ddemydenko Date: Tue, 14 Oct 2025 18:46:57 +0200 Subject: [PATCH 2/2] - Improved loopback matching to handle octal format --- lib/ip.js | 3 ++- test/api-test.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ip.js b/lib/ip.js index 64d96f6..9d63cb4 100644 --- a/lib/ip.js +++ b/lib/ip.js @@ -345,7 +345,8 @@ ip.isPublic = function (addr) { ip.isLoopback = function (addr) { // If addr is an IPv4 address in long integer form (no dots and no colons), convert it if (!/\./.test(addr) && !/:/.test(addr)) { - addr = ip.fromLong(Number(addr)); + const num = ip.isOctalFormat(addr) ? parseInt(addr, 8) : parseInt(addr, 10); + addr = ip.fromLong(num >>> 0); } return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/i.test(addr) diff --git a/test/api-test.js b/test/api-test.js index fe6d82f..ccc021a 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -565,6 +565,7 @@ describe('IP library for node.js', () => { const nonPublicCases = [ '127.1', // valid ip4 loopback '01200034567', // valid octal format + '017700000001', // valid octal format '012.1.2.3', // leading-zero octal fragments '000:0:0000::01', // zero-padding IPv6 representation '::fFFf:127.0.0.1' // IPv6 case insensitive case