Skip to content

Commit 742b47a

Browse files
authored
fix: use Node.js net lib and reject malformed addresses (#1)
merge indutny#144 eggjs/security#94 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced new IP validation functions: `isValid`, `normalizeStrict`, `isValidAndPrivate`, `normalizeLax`, and `isValidAndPublic`. - **Documentation** - Updated README with new installation instructions, security fix note, and license information. - **Refactor** - Updated IP address manipulation functions for better validation and normalization. - **Chores** - Updated Node.js versions in CI workflow and upgraded GitHub Actions dependencies. - Renamed package from "ip" to "@eggjs/ip" and updated relevant URLs. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 3b0994a commit 742b47a

File tree

7 files changed

+202
-2721
lines changed

7 files changed

+202
-2721
lines changed

.github/workflows/release.yml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
branches: [ master ]
6+
7+
jobs:
8+
release:
9+
name: Node.js
10+
uses: eggjs/github-actions/.github/workflows/node-release.yml@master
11+
secrets:
12+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
13+
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}

.github/workflows/test.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ jobs:
55
runs-on: ubuntu-latest
66
strategy:
77
matrix:
8-
node: [ 12, 14, 16, 18 ]
8+
node: [ 14, 16, 18, 20, 22 ]
99
name: Node ${{ matrix.node }} sample
1010
steps:
11-
- uses: actions/checkout@v3
11+
- uses: actions/checkout@v4
1212
- name: Setup node
13-
uses: actions/setup-node@v3
13+
uses: actions/setup-node@v4
1414
with:
1515
node-version: ${{ matrix.node }}
16-
- run: npm ci
16+
- run: npm install
1717
- run: npm test

README.md

+20-10
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
1-
# IP
2-
[![](https://badge.fury.io/js/ip.svg)](https://www.npmjs.com/package/ip)
1+
# IP
2+
3+
[![](https://badge.fury.io/js/@eggjs/ip.svg)](https://www.npmjs.com/package/@eggjs/ip)
34

45
IP address utilities for node.js
56

6-
## Installation
7+
Security fix fork, merge https://github.com/indutny/node-ip/pull/144
78

8-
### npm
9-
```shell
10-
npm install ip
11-
```
9+
## Installation
1210

13-
### git
11+
### npm
1412

1513
```shell
16-
git clone https://github.com/indutny/node-ip.git
14+
npm install ip
1715
```
1816

1917
## Usage
18+
2019
Get your ip address, compare ip addresses, validate ip addresses, etc.
2120

2221
```js
@@ -34,6 +33,7 @@ ip.or('192.168.1.134', '0.0.0.255') // 192.168.1.255
3433
ip.isPrivate('127.0.0.1') // true
3534
ip.isV4Format('127.0.0.1'); // true
3635
ip.isV6Format('::ffff:127.0.0.1'); // true
36+
ip.isValid('127.0.0.1'); // true
3737

3838
// operate on buffers in-place
3939
var buf = new Buffer(128);
@@ -58,16 +58,25 @@ ip.cidrSubnet('192.168.1.134/26')
5858
// range checking
5959
ip.cidrSubnet('192.168.1.134/26').contains('192.168.1.190') // true
6060

61-
6261
// ipv4 long conversion
6362
ip.toLong('127.0.0.1'); // 2130706433
6463
ip.fromLong(2130706433); // '127.0.0.1'
64+
65+
// malformed addresses and normalization
66+
ip.normalizeStrict('0::01'); // '::1'
67+
ip.isPrivate('0x7f.1'); // throw error
68+
ip.isValidAndPrivate('0x7f.1'); // false
69+
ip.normalizeStrict('0x7f.1'); // throw error
70+
var normalized = ip.normalizeLax('0x7f.1'); // 127.0.0.1
71+
ip.isPrivate(normalized); // true
6572
```
6673

6774
### License
6875

76+
```txt
6977
This software is licensed under the MIT License.
7078
79+
Copyright (c) 2024-present eggjs and other contributors.
7180
Copyright Fedor Indutny, 2012.
7281
7382
Permission is hereby granted, free of charge, to any person obtaining a
@@ -88,3 +97,4 @@ NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
8897
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
8998
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
9099
USE OR OTHER DEALINGS IN THE SOFTWARE.
100+
```

lib/ip.js

+57-31
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const ip = exports;
22
const { Buffer } = require('buffer');
33
const os = require('os');
4+
const net = require('net');
45

56
ip.toBuffer = function (ip, buff, offset) {
67
offset = ~~offset;
@@ -82,15 +83,28 @@ ip.toString = function (buff, offset, length) {
8283
return result;
8384
};
8485

85-
const ipv4Regex = /^(\d{1,3}\.){3,3}\d{1,3}$/;
86-
const ipv6Regex = /^(::)?(((\d{1,3}\.){3}(\d{1,3}){1})?([0-9a-f]){0,4}:{0,2}){1,8}(::)?$/i;
86+
ip.isV4Format = net.isIPv4;
8787

88-
ip.isV4Format = function (ip) {
89-
return ipv4Regex.test(ip);
88+
ip.isV6Format = net.isIPv6;
89+
90+
ip.isValid = function (addr) {
91+
return net.isIP(addr) !== 0;
9092
};
9193

92-
ip.isV6Format = function (ip) {
93-
return ipv6Regex.test(ip);
94+
ip.normalizeStrict = function (addr) {
95+
let family;
96+
switch (net.isIP(addr)) {
97+
case 4:
98+
family = 'ipv4';
99+
break;
100+
case 6:
101+
family = 'ipv6';
102+
break;
103+
default:
104+
throw new Error(`Invalid ip address: ${addr}`);
105+
}
106+
const { address } = new net.SocketAddress({ address: addr, family });
107+
return address;
94108
};
95109

96110
function _normalizeFamily(family) {
@@ -306,26 +320,13 @@ ip.isEqual = function (a, b) {
306320
};
307321

308322
ip.isPrivate = function (addr) {
309-
// check loopback addresses first
310-
if (ip.isLoopback(addr)) {
311-
return true;
312-
}
313-
314-
// ensure the ipv4 address is valid
315-
if (!ip.isV6Format(addr)) {
316-
const ipl = ip.normalizeToLong(addr);
317-
if (ipl < 0) {
318-
throw new Error('invalid ipv4 address');
319-
}
320-
// normalize the address for the private range checks that follow
321-
addr = ip.fromLong(ipl);
322-
}
323+
addr = ip.normalizeStrict(addr);
323324

324325
// check private ranges
325-
return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
326+
return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
327+
|| /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
326328
|| /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
327-
|| /^(::f{4}:)?172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})$/i
328-
.test(addr)
329+
|| /^(::f{4}:)?172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
329330
|| /^(::f{4}:)?169\.254\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr)
330331
|| /^f[cd][0-9a-f]{2}:/i.test(addr)
331332
|| /^fe80:/i.test(addr)
@@ -337,16 +338,26 @@ ip.isPublic = function (addr) {
337338
return !ip.isPrivate(addr);
338339
};
339340

340-
ip.isLoopback = function (addr) {
341-
// If addr is an IPv4 address in long integer form (no dots and no colons), convert it
342-
if (!/\./.test(addr) && !/:/.test(addr)) {
343-
addr = ip.fromLong(Number(addr));
341+
ip.isValidAndPrivate = function (addr) {
342+
try {
343+
return ip.isPrivate(addr);
344+
} catch {
345+
return false;
346+
}
347+
};
348+
349+
ip.isValidAndPublic = function (addr) {
350+
try {
351+
return ip.isPublic(addr);
352+
} catch {
353+
return false;
344354
}
355+
};
356+
357+
ip.isLoopback = function (addr) {
358+
addr = ip.normalizeStrict(addr);
345359

346-
return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/
347-
.test(addr)
348-
|| /^0177\./.test(addr)
349-
|| /^0x7f\./i.test(addr)
360+
return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/.test(addr)
350361
|| /^fe80::1$/i.test(addr)
351362
|| /^::1$/.test(addr)
352363
|| /^::$/.test(addr);
@@ -443,6 +454,8 @@ ip.fromLong = function (ipl) {
443454
};
444455

445456
ip.normalizeToLong = function (addr) {
457+
if (typeof addr !== 'string') return -1;
458+
446459
const parts = addr.split('.').map(part => {
447460
// Handle hexadecimal format
448461
if (part.startsWith('0x') || part.startsWith('0X')) {
@@ -469,6 +482,7 @@ ip.normalizeToLong = function (addr) {
469482

470483
switch (n) {
471484
case 1:
485+
if (parts[0] > 0xffffffff) return -1;
472486
val = parts[0];
473487
break;
474488
case 2:
@@ -489,3 +503,15 @@ ip.normalizeToLong = function (addr) {
489503

490504
return val >>> 0;
491505
};
506+
507+
ip.normalizeLax = function(addr) {
508+
if (ip.isV6Format(addr)) {
509+
const { address } = new net.SocketAddress({ address: addr, family: 'ipv6' });
510+
return address;
511+
}
512+
const ipl = ip.normalizeToLong(addr);
513+
if (ipl < 0) {
514+
throw Error(`Invalid ip address: ${addr}`);
515+
}
516+
return ip.fromLong(ipl);
517+
};

0 commit comments

Comments
 (0)