
Gepflegter Fork von node-ip, bei dem der ungepatchte SSRF-Sicherheitshinweis (CVE-2024-29415) behoben wurde.
Gepflegter Fork von ip — IP-Adress-Dienstprogramme für Node.js — mit behobener ungepatchter SSRF-Warnung.
Das Originalpaket hat ~38 Mio. Downloads/Monat, seit Februar 2024 kein Release mehr und eine Warnung mit hohem Schweregrad (CVE-2024-29415 / GHSA-2p57-rm9w-gvfp), die jede veröffentlichte Version betrifft und npm audit bei jeder Installation fehlschlagen lässt. Dieser Fork behebt die Ursache, anstatt Regexes zu patchen.
npm install @bybrave/ip2
// CommonJS — drop-in
const ip = require('@bybrave/ip2');
// ESM — named imports
import { isPrivate, isPublic, cidrSubnet } from '@bybrave/ip2';
// TypeScript types built in
Die ursprünglichen isPrivate() / isPublic() / isLoopback() führten Regexes über den rohen Eingabestring aus. Jede Schreibweise einer Adresse, die die Regexes nicht vorhersahen — oktale oder hexadezimale Oktette, abgekürztes IPv4, unkomprimiertes IPv6 — wurde stillschweigend als öffentlich eingestuft:
// original [email protected] — all of these are loopback/private, all reported PUBLIC:
ip.isPublic('127.1') // true 💥
ip.isPublic('012.1.2.3') // true 💥 (octal 10.1.2.3)
ip.isPublic('01200034567') // true 💥 (integer 10.0.14.119)
ip.isPublic('000:0:0000::01') // true 💥 (that's ::1)
Wenn diese Antworten ausgehende Anfragen absichern, ist das ein SSRF-Leck (CVE-2024-29415, unvollständige Behebung von CVE-2023-42282).
Dieser Fork:
net.isIP, plus IPv6-Zone-IDs). Die Klassifizierung läuft auf Bytes, niemals auf Strings.127.1, 0x7f.1, 0177.0.0.1, 2130706433) werfen einen Fehler, anstatt geraten zu werden. Fail closed, nicht open. Wenn Sie Legacy-Notationen akzeptieren müssen, normalisieren Sie zuerst explizit: ip.fromLong(ip.normalizeToLong(addr)).isPrivate() bedeutet jetzt „nicht global erreichbar" — dieselbe Konvention wie bei Pythons ipaddress. Das fügt CGNAT (100.64/10), TEST-NETs, Benchmarking, Multicast, Reserved, ULA, Dokumentationsbereiche und mehr hinzu — siehe die Tabelle unten.::ffff:a.b.c.d (v4-mapped), 64:ff9b::a.b.c.d (NAT64) und 2002:xxxx:xxxx:: (6to4) sind privat/öffentlich entsprechend der darin enthaltenen IPv4-Adresse.// @bybrave/ip2
ip.isPublic('127.1') // throws Error: Invalid ip address: 127.1
ip.isPrivate('000:0:0000::01') // true (parsed as ::1)
ip.isLoopback('::fFFf:127.0.0.1')// true
ip.isPublic('64:ff9b::8.8.8.8') // true (NAT64 of a public address)
ip.isPrivate('100.64.0.1') // true (CGNAT — original said public)
[email protected]| Behoben | Ursprüngliches Problem |
|---|---|
| CVE-2024-29415 / GHSA-2p57-rm9w-gvfp — SSRF über nicht-kanonische Adressen | #136, #150, #153, #158 |
isPrivate() verpasst CGNAT, TEST-NETs, Benchmarking, Multicast, Reserved, Dokumentationsbereiche | — |
cidrSubnet(...).contains() gibt false für IPv4-gemappte IPv6-Adressen zurück | #104 |
isV6Format() gibt true für IPv4-Adressen zurück | #67 |
isV4Format() akzeptiert Oktette ≥ 256 | #105 |
address('public') gab private Adressen zurück und umgekehrt (Filter war invertiert) | — |
loopback('ipv6') gab fe80::1 zurück, was link-local und nicht loopback ist → jetzt ::1 | — |
cidr()/cidrSubnet() erzeugten stillschweigend Müll bei ungültigen Präfixlängen (/33, /x) → werfen jetzt einen Fehler | — |
address(name) stürzte mit TypeError bei unbekannten Interface-Namen ab → gibt jetzt undefined zurück | — |
cidrSubnet()/subnet() lieferten falsche Ergebnisse für IPv6 (/48 enthielt ein benachbartes /48) → echte IPv6-Subnetz-Mathematik | #89, #110 |
Bundling für den Browser schlug bei os fehl (webpack 5) → browser-Feld mappt es weg | #127, #132 |
TypeScript-Typen integriert (kein @types/ip nötig), ESM-Named-Exports | — |
- const ip = require('ip');
+ const ip = require('@bybrave/ip2');
Die API-Oberfläche ist dieselbe; die Verhaltensunterschiede sind die Sicherheits- und Korrektheitskorrekturen — siehe die Breaking Changes unten, bevor Sie upgraden.
Version 3.0.0, weil Striktheit die Lösung ist — wenn Sie dieser Bibliothek nicht-kanonische Strings übergeben haben, gab v2 Ihnen falsche Antworten, keine kompatiblen:
isPrivate / isPublic / isLoopback / toBuffer / toLong werfen einen Fehler bei fehlerhafter oder nicht-kanonischer Eingabe (oktale/hexadezimale Oktette, 127.1, Integer-Strings). Verwenden Sie zuerst normalizeToLong(), wenn Sie Legacy-Notationen akzeptieren müssen.isLoopback('fe80::1') ist jetzt false (link-local ≠ loopback; weiterhin isPrivate). isLoopback('::') ist jetzt false (unspecified ≠ loopback; weiterhin isPrivate).loopback('ipv6') gibt '::1' statt 'fe80::1' zurück.isPrivate() gibt true für mehr Bereiche zurück (CGNAT, TEST-NETs, Multicast, …) — es bedeutet jetzt „nicht global erreichbar".address('public') gibt jetzt tatsächlich öffentliche Adressen zurück. Wenn Sie sich auf das invertierte Verhalten verlassen haben, um eine LAN-Adresse zu erhalten, verwenden Sie address('private') — das ist, was es Ihnen gegeben hat.Alles, was das Original exportiert, plus isValid():
ip.address('public', 'ipv6') // address of a network interface
ip.isPrivate('10.0.0.1') // true — not globally reachable
ip.isPublic('8.8.8.8') // true
ip.isLoopback('127.8.8.8') // true
ip.isValid('300.1.2.3') // false (never throws)
ip.isV4Format('192.168.0.1') // true
ip.isV6Format('2001:db8::1') // true
ip.isEqual('::ffff:7f00:1', '127.0.0.1') // true
ip.toBuffer('127.0.0.1') // Buffer([127, 0, 0, 1])
ip.toString(buf, offset, length) // '127.0.0.1'
ip.toLong('127.0.0.1') // 2130706433
ip.fromLong(2130706433) // '127.0.0.1'
ip.normalizeToLong('127.1') // 2130706433 (lenient, -1 on error)
ip.fromPrefixLen(24) // '255.255.255.0'
ip.mask('192.168.1.134', '255.255.255.0') // '192.168.1.0'
ip.cidr('192.168.1.134/26') // '192.168.1.128'
ip.not('255.255.255.0') // '0.0.0.255'
ip.or('0.0.0.255', '192.168.1.10') // '192.168.1.255'