{"id":"CVE-2026-69198","aliases":["CLSA-2026-1788790498","CLSA-2026-1790604982","CLSA-2026-1793097608","CLSA-2026-1793181094","DEBIAN-CVE-2026-69198","EUVD-2026-52422","GHSA-4xrf-jv44-h6hh","RHSA-2026:49401","RHSA-2026:50826","UBUNTU-CVE-2026-69198"],"status":"published","title":"ip-address: a CIDR suffix on the parsed address suppresses special-use classification and can bypass SSRF and trust-boundary checks","description":"ip-address is a library for parsing and manipulating IPv4 and IPv6 addresses in JavaScript. From 10.1.1 until 10.2.2, every special-use classification method is built on isInSubnet, which short-circuits to false whenever the address's own subnet mask is shorter than the reference range's mask. That mask comes verbatim from the CIDR suffix on the parsed input, so appending a suffix such as /0 suppresses classification entirely: isLoopback(), isPrivate(), isLinkLocal(), isCGNAT(), isMulticast(), isUnspecified(), isBroadcast(), isULA(), and getType() all report an internal address as unremarkable, while correctForm() and address still return the real internal target. An application that builds a network trust-boundary decision on these checks, for example a filter intended to block Server-Side Request Forgery, or SSRF, may therefore treat an internal target as external and allow the request. The underlying bit comparison is correct, and mask(n) already returns the first n bits of the full parsed address independently of subnetMask; the defect is solely that the containment guard sits in the classification path. This issue is fixed in version 10.2.2.","severity":{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:H/SI:N/SA:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.0048,"percentile":0.41749,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-20","CWE-918"],"published":"2026-08-03T19:59:05Z","modified":"2026-10-28T13:45:02Z","fixes":[{"ecosystem":"npm","package":"ip-address","fixed_in":["10.2.2"],"sources":["ghsa","osv"]},{"ecosystem":"TuxCare:npm","package":"ip-address","fixed_in":["10.1.1-tuxcare.3","10.1.0-tuxcare.4"],"sources":["osv"]},{"ecosystem":"Debian:14","package":"node-ip-address","fixed_in":["10.3.1-1"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"ip-address","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"10.1.1"},{"last_affected":"10.2.1"},{"fixed":"10.2.2"}]}]},{"source":"osv","ecosystem":"TuxCare:npm","package":"ip-address","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"6.4.0-tuxcare.0"},{"fixed":"6.4.0-tuxcare.2"}]}],"versions":["6.4.0","6.4.0-tuxcare.1"],"bundled":2},{"source":"osv","ecosystem":"TuxCare:npm","package":"ip-address","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"7.1.0-tuxcare.0"},{"fixed":"7.1.0-tuxcare.1"}]}],"versions":["7.1.0"],"bundled":2},{"source":"osv","ecosystem":"TuxCare:npm","package":"ip-address","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"10.1.1-tuxcare.0"},{"fixed":"10.1.1-tuxcare.3"}]}],"versions":["10.1.1","10.1.1-tuxcare.1","10.1.1-tuxcare.2"]},{"source":"osv","ecosystem":"TuxCare:npm","package":"ip-address","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"10.1.0-tuxcare.0"},{"fixed":"10.1.0-tuxcare.4"}]}],"versions":["10.1.0","10.1.0-tuxcare.1","10.1.0-tuxcare.2","10.1.0-tuxcare.3"]},{"source":"osv","ecosystem":"Debian:14","package":"node-ip-address","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"10.3.1-1"}]}],"versions":["10.2.0-1","10.2.0-2","8.1.0-2","8.1.0-3"]},{"source":"osv","ecosystem":"npm","package":"ip-address","ranges":[{"type":"SEMVER","events":[{"introduced":"10.1.1"},{"fixed":"10.2.2"}]}]},{"source":"osv","ecosystem":"Red Hat:hummingbird:1","package":"grafana12.4","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:12.4.6-0.3.hum1"}]}],"bundled":3},{"source":"osv","ecosystem":"Red Hat:hummingbird:1","package":"grafana13.1","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:13.1.1-0.5.2.hum1"}]}],"bundled":10},{"source":"osv","ecosystem":"Ubuntu:20.04:LTS","package":"node-ip-address","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.3.0-1"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"node-ip-address","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.4.0-1","8.1.0-2"]},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"node-ip-address","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["8.1.0-2"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"node-ip-address","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["8.1.0-3","10.2.0-2","10.3.1-1","10.4.0-1"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"node-ip-address","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["8.1.0-2","8.1.0-3"]}],"products":[{"vendor":"beaugunderson","product":"ip-address"}],"references":[{"url":"https://github.com/beaugunderson/ip-address/security/advisories/GHSA-4xrf-jv44-h6hh","tags":["x_refsource_CONFIRM","exploit","advisory","web","article","report"]},{"url":"https://github.com/beaugunderson/ip-address/commit/488fe9bc7c35363b4b090494fc38c266d217740d","tags":["x_refsource_MISC","fix","web","article","report"]},{"url":"https://github.com/beaugunderson/ip-address/releases/tag/v10.2.2","tags":["x_refsource_MISC","web","article","report"]},{"url":"https://github.com/advisories/GHSA-4xrf-jv44-h6hh"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/69xxx/CVE-2026-69198.json","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-69198","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-69198","tags":["advisory"]},{"url":"https://github.com/beaugunderson/ip-address","tags":["package"]},{"url":"https://access.redhat.com/errata/RHSA-2026:49401","tags":["advisory"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-18446","tags":["report"]},{"url":"https://access.redhat.com/security/updates/classification/","tags":["article"]},{"url":"https://images.redhat.com/","tags":["article"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-67319","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-69198","tags":["report"]},{"url":"https://security.access.redhat.com/data/csaf/v2/advisories/2026/rhsa-2026_49401.json","tags":["advisory"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2509801","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-18446","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-18446","tags":["advisory"]},{"url":"https://cna.openjsf.org/security-advisories.html","tags":["article"]},{"url":"https://github.com/fastify/fast-uri/security/advisories/GHSA-7p8r-x3mc-p8w7","tags":["article"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2510023","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-67319","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-67319","tags":["advisory"]},{"url":"https://github.com/axios/axios/security/advisories/GHSA-7q8q-rj6j-mhjq","tags":["article"]},{"url":"https://www.vulncheck.com/advisories/axios-before-prototype-pollution-via-nested-option-objects","tags":["article"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2510803","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-69198","tags":["advisory","report"]},{"url":"https://access.redhat.com/errata/RHSA-2026:50826","tags":["advisory"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-67313","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-67314","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-67315","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-67316","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-67317","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-67318","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-69152","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-69153","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-69192","tags":["report"]},{"url":"https://security.access.redhat.com/data/csaf/v2/advisories/2026/rhsa-2026_50826.json","tags":["advisory"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-67313","tags":["advisory"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2510030","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-67314","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-67314","tags":["advisory"]},{"url":"https://github.com/axios/axios/security/advisories/GHSA-xj6q-8x83-jv6g","tags":["article"]},{"url":"https://www.vulncheck.com/advisories/axios-before-prototype-pollution-via-auth-subfields","tags":["article"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2510019","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-67315","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-67315","tags":["advisory"]},{"url":"https://github.com/axios/axios/security/advisories/GHSA-f4gw-2p7v-4548","tags":["article"]},{"url":"https://www.vulncheck.com/advisories/axios-before-no-proxy-bypass-via","tags":["article"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2510009","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-67316","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-67316","tags":["advisory"]},{"url":"https://github.com/axios/axios/security/advisories/GHSA-mmx7-hfxf-jppx","tags":["article"]},{"url":"https://www.vulncheck.com/advisories/axios-before-prototype-pollution-via-bodyless-methods","tags":["article"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2510031","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-67317","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-67317","tags":["advisory"]},{"url":"https://github.com/axios/axios/security/advisories/GHSA-jqh4-m9w3-8hp9","tags":["article"]},{"url":"https://www.vulncheck.com/advisories/axios-before-maxbodylength-bypass-via-readablestream","tags":["article"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2509989","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-67318","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-67318","tags":["advisory"]},{"url":"https://github.com/axios/axios/security/advisories/GHSA-mwf2-3pr3-8698","tags":["article"]},{"url":"https://www.vulncheck.com/advisories/axios-before-maxbodylength-bypass-via-http-2","tags":["article"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2510722","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-69152","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-69152","tags":["advisory"]},{"url":"https://github.com/juliangruber/brace-expansion/commit/139d015104e71433ad52a41d19467c48ecbb2c7d","tags":["article"]},{"url":"https://github.com/juliangruber/brace-expansion/commit/1e30c930238d7162802d88a94189182def178dac","tags":["article"]},{"url":"https://github.com/juliangruber/brace-expansion/commit/688a99eeaab02627c2b89ba8ba4821fecfa659cf","tags":["article"]},{"url":"https://github.com/juliangruber/brace-expansion/commit/cb4b9e47cc2ec777c14b2b4492fb431a56f6a031","tags":["article"]},{"url":"https://github.com/juliangruber/brace-expansion/security/advisories/GHSA-rgw5-rvv9-x895","tags":["article"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2510719","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-69153","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-69153","tags":["advisory"]},{"url":"https://github.com/postcss/postcss/commit/7beca139e70f9075c6b19700fcb00dd8033e5da8","tags":["article"]},{"url":"https://github.com/postcss/postcss/releases/tag/8.5.19","tags":["article"]},{"url":"https://github.com/postcss/postcss/security/advisories/GHSA-fxqj-rqcc-2cmp","tags":["article"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2510801","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-69192","tags":["advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-69192","tags":["advisory"]},{"url":"https://github.com/beaugunderson/ip-address/commit/56368cb3d66c73ba0ee9b6b834fd31b22c2fd71e","tags":["article"]},{"url":"https://github.com/beaugunderson/ip-address/releases/tag/v10.3.1","tags":["article"]},{"url":"https://github.com/beaugunderson/ip-address/security/advisories/GHSA-mwp4-54f8-5fhr","tags":["article"]},{"url":"https://ubuntu.com/security/CVE-2026-69198","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-69198","title":"ip-address: a CIDR suffix on the parsed address suppresses special-use classification and can bypass SSRF and trust-boundary checks","description":"ip-address is a library for parsing and manipulating IPv4 and IPv6 addresses in JavaScript. From 10.1.1 until 10.2.2, every special-use classification method is built on isInSubnet, which short-circuits to false whenever the address's own subnet mask is shorter than the reference range's mask. That mask comes verbatim from the CIDR suffix on the parsed input, so appending a suffix such as /0 suppresses classification entirely: isLoopback(), isPrivate(), isLinkLocal(), isCGNAT(), isMulticast(), isUnspecified(), isBroadcast(), isULA(), and getType() all report an internal address as unremarkable, while correctForm() and address still return the real internal target. An application that builds a network trust-boundary decision on these checks, for example a filter intended to block Server-Side Request Forgery, or SSRF, may therefore treat an internal target as external and allow the request. The underlying bit comparison is correct, and mask(n) already returns the first n bits of the full parsed address independently of subnetMask; the defect is solely that the containment guard sits in the classification path. This issue is fixed in version 10.2.2.","status":"published","published":"2026-08-03T19:59:05.731Z","modified":"2026-08-04T17:21:59.584Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:H/SI:N/SA:N","level":"medium","assessor":"cna"}],"cwes":["CWE-20","CWE-918"],"updated_at":"2026-11-02T13:50:27.582139Z"}],"euvd":[{"id":"EUVD-2026-52422","description":"ip-address is a library for parsing and manipulating IPv4 and IPv6 addresses in JavaScript. From 10.1.1 until 10.2.2, every special-use classification method is built on isInSubnet, which short-circuits to false whenever the address's own subnet mask is shorter than the reference range's mask. That mask comes verbatim from the CIDR suffix on the parsed input, so appending a suffix such as /0 suppresses classification entirely: isLoopback(), isPrivate(), isLinkLocal(), isCGNAT(), isMulticast(), isUnspecified(), isBroadcast(), isULA(), and getType() all report an internal address as unremarkable, while correctForm() and address still return the real internal target. An application that builds a network trust-boundary decision on these checks, for example a filter intended to block Server-Side Request Forgery, or SSRF, may therefore treat an internal target as external and allow the request. The underlying bit comparison is correct, and mask(n) already returns the first n bits of the full parsed address independently of subnetMask; the defect is solely that the containment guard sits in the classification path. This issue is fixed in version 10.2.2.","status":"published","published":"2026-08-03T19:59:05Z","modified":"2026-08-04T17:21:59Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:H/SI:N/SA:N","level":"medium"}],"updated_at":"2026-11-02T18:02:22.965583Z"}],"ghsa":[{"id":"GHSA-4xrf-jv44-h6hh","title":"ip-address: a CIDR suffix on the parsed address suppresses special-use classification and can bypass SSRF and trust-boundary checks","description":"### Summary\n\nEvery special-use classification method is built on `isInSubnet`, which short-circuits to `false` whenever the address's own subnet mask is *shorter* than the reference range's mask. That mask comes verbatim from the CIDR suffix on the parsed input, so appending a suffix such as `/0` suppresses classification entirely: `isLoopback()`, `isPrivate()`, `isLinkLocal()`, `isCGNAT()`, `isMulticast()`, `isUnspecified()`, `isBroadcast()`, `isULA()`, and `getType()` all report an internal address as unremarkable, while `correctForm()` and `address` still return the real internal target.\n\nAn application that builds a network trust-boundary decision on these checks (for example a filter intended to block Server-Side Request Forgery, or SSRF) may therefore treat an internal target as external and allow the request. SSRF is an attack in which a user-supplied address coaxes the server into making a request to an internal destination the user could not otherwise reach, such as a loopback service or a cloud metadata endpoint.\n\n### Details\n\n`isInSubnet` in `src/common.ts` opens with a guard that compares the two prefix lengths:\n\n```js\nexport function isInSubnet(this, address) {\n  if (this.subnetMask \u003c address.subnetMask) {\n    return false;                                  // \u003c-- reached before any bit comparison\n  }\n\n  if (this.mask(address.subnetMask) === address.mask()) {\n    return true;\n  }\n\n  return false;\n}\n```\n\nThat guard is correct for the question `isInSubnet` is named for — whether one *network* is contained in another, where a `/0` network genuinely is not inside a `/8`. It is wrong for classification, which asks a question about the address itself and must not depend on the prefix the caller happened to write. `/0` is shorter than every reference prefix in the special-use tables (loopback `/8`, link-local `/16`, CGNAT `/10`, ULA `/7`, multicast `/4`), so for a classification call the bit comparison is never reached and the method returns `false`.\n\nThe underlying bit comparison is correct, and `mask(n)` already returns the first `n` bits of the full parsed address independently of `subnetMask` — the defect is solely that the containment guard sits in the classification path. Host bits are retained through parsing, so `correctForm()` still yields the real target and the address remains fully usable for connecting.\n\n`/0` is the universal case because it is shorter than every reference prefix, but any suffix shorter than the specific range being tested has the same effect: `10.0.0.5/7` defeats `isPrivate()` for `10.0.0.0/8`.\n\n### Affected versions\n\n`\u003e= 10.1.1, \u003c= 10.2.1`. The `is*` classification API was introduced for `Address4` in 10.1.1 and extended to `Address6` in 10.2.0; releases before 10.1.1 do not expose it and are not affected through this vector. The containment guard itself is much older, but `isInSubnet` alone is a subnet-containment predicate whose behavior here is correct.\n\nThis also defeats the fix released in 10.2.1 for GHSA-22jq-vg5j-6vgg: that release classifies IPv4-mapped and NAT64 addresses by their embedded IPv4 address, but the normalization is reached through `isInSubnet`, so `::ffff:127.0.0.1/0` reverts to being reported as non-internal.\n\n### Impact\n\nEvery classifier is affected on both `Address4` and `Address6`. The sole exception is `Address6.isLinkLocal()` for *native* `fe80::/10` addresses, which compares raw bits directly; its IPv4-mapped path is still affected.\n\n| Address | Reported as | Actually points at |\n|---|---|---|\n| `127.0.0.1/0` | not loopback | loopback (`127.0.0.0/8`) |\n| `10.0.0.1/0`, `10.0.0.5/7` | not private | RFC 1918 `10/8` |\n| `172.16.5.5/0` | not private | RFC 1918 `172.16/12` |\n| `192.168.1.1/0` | not private | RFC 1918 `192.168/16` |\n| `169.254.169.254/0` | not link-local | link-local / cloud metadata (IMDS) |\n| `100.64.0.1/0` | not CGNAT | CGNAT `100.64/10` |\n| `0.0.0.0/0`, `255.255.255.255/0` | not unspecified / not broadcast | unspecified / broadcast |\n| `::1/0` | not loopback | IPv6 loopback |\n| `fc00::1/0` | not ULA, not private | IPv6 ULA `fc00::/7` |\n| `ff02::1/0` | not multicast | IPv6 multicast |\n| `::ffff:127.0.0.1/0` | not loopback | loopback, via IPv4-mapped |\n| `::ffff:169.254.169.254/0` | not link-local | IMDS, via IPv4-mapped |\n| `64:ff9b::7f00:1/0` | not loopback | loopback, via NAT64 |\n\n`getType()` returns `Global unicast` for all of the IPv6 cases above, and `getScope()` follows it.\n\n### Reachability\n\nA CIDR suffix is not legal in a URL host, so this is not reachable through the most common SSRF shape. `new URL('http://127.0.0.1/0')` parses `hostname` as `127.0.0.1` and `pathname` as `/0`, and a guard that classifies the extracted hostname is unaffected. Exploitation requires an application that accepts a bare address string that may carry a suffix and passes it to the constructor before classifying — for example an allow/deny field, a webhook target, or a proxy destination taken as a plain host rather than parsed out of a URL.\n\n### Proof of concept\n\n`npm i ip-address@10.2.1`, then:\n\n```js\nconst { Address4, Address6 } = require('ip-address');\n\n// true =\u003e block as internal, false =\u003e allow outbound\nfunction isBlocked(host) {\n  try {\n    const a = new Address4(host);\n    return a.isPrivate() || a.isLoopback() || a.isLinkLocal() || a.isCGNAT()\n        || a.isMulticast() || a.isUnspecified() || a.isBroadcast();\n  } catch {}\n  try {\n    const a = new Address6(host);\n    return a.isPrivate() || a.isLoopback() || a.isLinkLocal() || a.isULA()\n        || a.isMulticast() || a.isUnspecified();\n  } catch {}\n  return false;\n}\n\nfor (const h of ['127.0.0.1', '10.0.0.1', '::1',\n                 '127.0.0.1/0', '10.0.0.5/7', '169.254.169.254/0',\n                 '::1/0', '::ffff:127.0.0.1/0', '64:ff9b::7f00:1/0']) {\n  console.log(isBlocked(h) ? 'BLOCK ' : 'ALLOW ', h, '-\u003e', new (h.includes(':') ? Address6 : Address4)(h).correctForm());\n}\n```\n\nOn affected versions every suffixed internal target is allowed, and `correctForm()` shows the request would reach the real internal address:\n\n```\nBLOCK  127.0.0.1 -\u003e 127.0.0.1\nBLOCK  10.0.0.1 -\u003e 10.0.0.1\nBLOCK  ::1 -\u003e ::1\nALLOW  127.0.0.1/0 -\u003e 127.0.0.1\nALLOW  10.0.0.5/7 -\u003e 10.0.0.5\nALLOW  169.254.169.254/0 -\u003e 169.254.169.254\nALLOW  ::1/0 -\u003e ::1\nALLOW  ::ffff:127.0.0.1/0 -\u003e ::ffff:7f00:1\nALLOW  64:ff9b::7f00:1/0 -\u003e 64:ff9b::7f00:1\n```\n\nThe first three lines are blocked as expected; the same destinations with a CIDR suffix are allowed through.\n\n### Remediation\n\nUpgrade to the patched release. In the fix, classification no longer consults the address's own prefix: a new `isHostInSubnet()` compares the address's host bits against the reference range only, and every classifier (`isLoopback`, `isPrivate`, `isLinkLocal`, `isCGNAT`, `isMulticast`, `isUnspecified`, `isBroadcast`, `isULA`, `isMapped4`, `isTeredo`, `is6to4`, `isDocumentation`, `getType`, and the IPv4-mapped/NAT64 normalization behind `embeddedIPv4`) uses it. `isInSubnet` keeps its subnet-containment semantics unchanged, including the guard that a wider network is not contained in a narrower one. After upgrading, `new Address4('127.0.0.1/0').isLoopback()` returns `true`.\n\nIf you cannot upgrade immediately, strip the suffix before classifying by re-parsing `addressMinusSuffix`:\n\n```js\nconst parsed = new Address4(userInput);\nconst host = new Address4(parsed.addressMinusSuffix);   // classify this one\n```\n\n### A note on SSRF defense\n\nThese methods are address classifiers, not a complete SSRF defense. Regardless of this fix, a robust SSRF guard must resolve the hostname and validate the *resolved* IP against the socket it connects to, and account for DNS rebinding and redirects. Treat these checks as one layer, not the only one.\n\n### Credit\n\nReported by @hi-im-glitchless.","status":"published","published":"2026-08-03T19:59:33Z","modified":"2026-08-03T19:59:34Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:H/SI:N/SA:N","level":"medium"}],"cwes":["CWE-20","CWE-918"],"updated_at":"2026-11-02T14:40:55.903445Z"}],"nvd":[{"id":"CVE-2026-69198","description":"ip-address is a library for parsing and manipulating IPv4 and IPv6 addresses in JavaScript. From 10.1.1 until 10.2.2, every special-use classification method is built on isInSubnet, which short-circuits to false whenever the address's own subnet mask is shorter than the reference range's mask. That mask comes verbatim from the CIDR suffix on the parsed input, so appending a suffix such as /0 suppresses classification entirely: isLoopback(), isPrivate(), isLinkLocal(), isCGNAT(), isMulticast(), isUnspecified(), isBroadcast(), isULA(), and getType() all report an internal address as unremarkable, while correctForm() and address still return the real internal target. An application that builds a network trust-boundary decision on these checks, for example a filter intended to block Server-Side Request Forgery, or SSRF, may therefore treat an internal target as external and allow the request. The underlying bit comparison is correct, and mask(n) already returns the first n bits of the full parsed address independently of subnetMask; the defect is solely that the containment guard sits in the classification path. This issue is fixed in version 10.2.2.","status":"published","published":"2026-08-03T20:17:30.107Z","modified":"2026-10-23T21:10:00.157Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:H/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"medium","assessor":"secondary"}],"cwes":["CWE-20","CWE-918"],"updated_at":"2026-11-02T14:12:34.643497Z"}],"osv":[{"id":"CVE-2026-69198","title":"ip-address: a CIDR suffix on the parsed address suppresses special-use classification and can bypass SSRF and trust-boundary checks","description":"ip-address is a library for parsing and manipulating IPv4 and IPv6 addresses in JavaScript. From 10.1.1 until 10.2.2, every special-use classification method is built on isInSubnet, which short-circuits to false whenever the address's own subnet mask is shorter than the reference range's mask. That mask comes verbatim from the CIDR suffix on the parsed input, so appending a suffix such as /0 suppresses classification entirely: isLoopback(), isPrivate(), isLinkLocal(), isCGNAT(), isMulticast(), isUnspecified(), isBroadcast(), isULA(), and getType() all report an internal address as unremarkable, while correctForm() and address still return the real internal target. An application that builds a network trust-boundary decision on these checks, for example a filter intended to block Server-Side Request Forgery, or SSRF, may therefore treat an internal target as external and allow the request. The underlying bit comparison is correct, and mask(n) already returns the first n bits of the full parsed address independently of subnetMask; the defect is solely that the containment guard sits in the classification path. This issue is fixed in version 10.2.2.","status":"published","published":"2026-08-03T19:59:05.731Z","modified":"2026-10-28T02:31:09.973706763Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:H/SI:N/SA:N","level":"medium"}],"cwes":["CWE-20","CWE-918"],"updated_at":"2026-11-02T15:45:15.164658Z"},{"id":"DEBIAN-CVE-2026-69198","description":"ip-address is a library for parsing and manipulating IPv4 and IPv6 addresses in JavaScript. From 10.1.1 until 10.2.2, every special-use classification method is built on isInSubnet, which short-circuits to false whenever the address's own subnet mask is shorter than the reference range's mask. That mask comes verbatim from the CIDR suffix on the parsed input, so appending a suffix such as /0 suppresses classification entirely: isLoopback(), isPrivate(), isLinkLocal(), isCGNAT(), isMulticast(), isUnspecified(), isBroadcast(), isULA(), and getType() all report an internal address as unremarkable, while correctForm() and address still return the real internal target. An application that builds a network trust-boundary decision on these checks, for example a filter intended to block Server-Side Request Forgery, or SSRF, may therefore treat an internal target as external and allow the request. The underlying bit comparison is correct, and mask(n) already returns the first n bits of the full parsed address independently of subnetMask; the defect is solely that the containment guard sits in the classification path. This issue is fixed in version 10.2.2.","status":"published","published":"2026-08-03T20:17:30.107Z","modified":"2026-10-24T09:47:35.494810306Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:H/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"medium"}],"updated_at":"2026-11-02T15:46:02.094661Z"},{"id":"GHSA-4xrf-jv44-h6hh","title":"ip-address: a CIDR suffix on the parsed address suppresses special-use classification and can bypass SSRF and trust-boundary checks","description":"### Summary\n\nEvery special-use classification method is built on `isInSubnet`, which short-circuits to `false` whenever the address's own subnet mask is *shorter* than the reference range's mask. That mask comes verbatim from the CIDR suffix on the parsed input, so appending a suffix such as `/0` suppresses classification entirely: `isLoopback()`, `isPrivate()`, `isLinkLocal()`, `isCGNAT()`, `isMulticast()`, `isUnspecified()`, `isBroadcast()`, `isULA()`, and `getType()` all report an internal address as unremarkable, while `correctForm()` and `address` still return the real internal target.\n\nAn application that builds a network trust-boundary decision on these checks (for example a filter intended to block Server-Side Request Forgery, or SSRF) may therefore treat an internal target as external and allow the request. SSRF is an attack in which a user-supplied address coaxes the server into making a request to an internal destination the user could not otherwise reach, such as a loopback service or a cloud metadata endpoint.\n\n### Details\n\n`isInSubnet` in `src/common.ts` opens with a guard that compares the two prefix lengths:\n\n```js\nexport function isInSubnet(this, address) {\n  if (this.subnetMask \u003c address.subnetMask) {\n    return false;                                  // \u003c-- reached before any bit comparison\n  }\n\n  if (this.mask(address.subnetMask) === address.mask()) {\n    return true;\n  }\n\n  return false;\n}\n```\n\nThat guard is correct for the question `isInSubnet` is named for — whether one *network* is contained in another, where a `/0` network genuinely is not inside a `/8`. It is wrong for classification, which asks a question about the address itself and must not depend on the prefix the caller happened to write. `/0` is shorter than every reference prefix in the special-use tables (loopback `/8`, link-local `/16`, CGNAT `/10`, ULA `/7`, multicast `/4`), so for a classification call the bit comparison is never reached and the method returns `false`.\n\nThe underlying bit comparison is correct, and `mask(n)` already returns the first `n` bits of the full parsed address independently of `subnetMask` — the defect is solely that the containment guard sits in the classification path. Host bits are retained through parsing, so `correctForm()` still yields the real target and the address remains fully usable for connecting.\n\n`/0` is the universal case because it is shorter than every reference prefix, but any suffix shorter than the specific range being tested has the same effect: `10.0.0.5/7` defeats `isPrivate()` for `10.0.0.0/8`.\n\n### Affected versions\n\n`\u003e= 10.1.1, \u003c= 10.2.1`. The `is*` classification API was introduced for `Address4` in 10.1.1 and extended to `Address6` in 10.2.0; releases before 10.1.1 do not expose it and are not affected through this vector. The containment guard itself is much older, but `isInSubnet` alone is a subnet-containment predicate whose behavior here is correct.\n\nThis also defeats the fix released in 10.2.1 for GHSA-22jq-vg5j-6vgg: that release classifies IPv4-mapped and NAT64 addresses by their embedded IPv4 address, but the normalization is reached through `isInSubnet`, so `::ffff:127.0.0.1/0` reverts to being reported as non-internal.\n\n### Impact\n\nEvery classifier is affected on both `Address4` and `Address6`. The sole exception is `Address6.isLinkLocal()` for *native* `fe80::/10` addresses, which compares raw bits directly; its IPv4-mapped path is still affected.\n\n| Address | Reported as | Actually points at |\n|---|---|---|\n| `127.0.0.1/0` | not loopback | loopback (`127.0.0.0/8`) |\n| `10.0.0.1/0`, `10.0.0.5/7` | not private | RFC 1918 `10/8` |\n| `172.16.5.5/0` | not private | RFC 1918 `172.16/12` |\n| `192.168.1.1/0` | not private | RFC 1918 `192.168/16` |\n| `169.254.169.254/0` | not link-local | link-local / cloud metadata (IMDS) |\n| `100.64.0.1/0` | not CGNAT | CGNAT `100.64/10` |\n| `0.0.0.0/0`, `255.255.255.255/0` | not unspecified / not broadcast | unspecified / broadcast |\n| `::1/0` | not loopback | IPv6 loopback |\n| `fc00::1/0` | not ULA, not private | IPv6 ULA `fc00::/7` |\n| `ff02::1/0` | not multicast | IPv6 multicast |\n| `::ffff:127.0.0.1/0` | not loopback | loopback, via IPv4-mapped |\n| `::ffff:169.254.169.254/0` | not link-local | IMDS, via IPv4-mapped |\n| `64:ff9b::7f00:1/0` | not loopback | loopback, via NAT64 |\n\n`getType()` returns `Global unicast` for all of the IPv6 cases above, and `getScope()` follows it.\n\n### Reachability\n\nA CIDR suffix is not legal in a URL host, so this is not reachable through the most common SSRF shape. `new URL('http://127.0.0.1/0')` parses `hostname` as `127.0.0.1` and `pathname` as `/0`, and a guard that classifies the extracted hostname is unaffected. Exploitation requires an application that accepts a bare address string that may carry a suffix and passes it to the constructor before classifying — for example an allow/deny field, a webhook target, or a proxy destination taken as a plain host rather than parsed out of a URL.\n\n### Proof of concept\n\n`npm i ip-address@10.2.1`, then:\n\n```js\nconst { Address4, Address6 } = require('ip-address');\n\n// true =\u003e block as internal, false =\u003e allow outbound\nfunction isBlocked(host) {\n  try {\n    const a = new Address4(host);\n    return a.isPrivate() || a.isLoopback() || a.isLinkLocal() || a.isCGNAT()\n        || a.isMulticast() || a.isUnspecified() || a.isBroadcast();\n  } catch {}\n  try {\n    const a = new Address6(host);\n    return a.isPrivate() || a.isLoopback() || a.isLinkLocal() || a.isULA()\n        || a.isMulticast() || a.isUnspecified();\n  } catch {}\n  return false;\n}\n\nfor (const h of ['127.0.0.1', '10.0.0.1', '::1',\n                 '127.0.0.1/0', '10.0.0.5/7', '169.254.169.254/0',\n                 '::1/0', '::ffff:127.0.0.1/0', '64:ff9b::7f00:1/0']) {\n  console.log(isBlocked(h) ? 'BLOCK ' : 'ALLOW ', h, '-\u003e', new (h.includes(':') ? Address6 : Address4)(h).correctForm());\n}\n```\n\nOn affected versions every suffixed internal target is allowed, and `correctForm()` shows the request would reach the real internal address:\n\n```\nBLOCK  127.0.0.1 -\u003e 127.0.0.1\nBLOCK  10.0.0.1 -\u003e 10.0.0.1\nBLOCK  ::1 -\u003e ::1\nALLOW  127.0.0.1/0 -\u003e 127.0.0.1\nALLOW  10.0.0.5/7 -\u003e 10.0.0.5\nALLOW  169.254.169.254/0 -\u003e 169.254.169.254\nALLOW  ::1/0 -\u003e ::1\nALLOW  ::ffff:127.0.0.1/0 -\u003e ::ffff:7f00:1\nALLOW  64:ff9b::7f00:1/0 -\u003e 64:ff9b::7f00:1\n```\n\nThe first three lines are blocked as expected; the same destinations with a CIDR suffix are allowed through.\n\n### Remediation\n\nUpgrade to the patched release. In the fix, classification no longer consults the address's own prefix: a new `isHostInSubnet()` compares the address's host bits against the reference range only, and every classifier (`isLoopback`, `isPrivate`, `isLinkLocal`, `isCGNAT`, `isMulticast`, `isUnspecified`, `isBroadcast`, `isULA`, `isMapped4`, `isTeredo`, `is6to4`, `isDocumentation`, `getType`, and the IPv4-mapped/NAT64 normalization behind `embeddedIPv4`) uses it. `isInSubnet` keeps its subnet-containment semantics unchanged, including the guard that a wider network is not contained in a narrower one. After upgrading, `new Address4('127.0.0.1/0').isLoopback()` returns `true`.\n\nIf you cannot upgrade immediately, strip the suffix before classifying by re-parsing `addressMinusSuffix`:\n\n```js\nconst parsed = new Address4(userInput);\nconst host = new Address4(parsed.addressMinusSuffix);   // classify this one\n```\n\n### A note on SSRF defense\n\nThese methods are address classifiers, not a complete SSRF defense. Regardless of this fix, a robust SSRF guard must resolve the hostname and validate the *resolved* IP against the socket it connects to, and account for DNS rebinding and redirects. Treat these checks as one layer, not the only one.\n\n### Credit\n\nReported by @hi-im-glitchless.","status":"published","published":"2026-08-03T19:59:33Z","modified":"2026-09-10T03:50:53.339579998Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:H/SI:N/SA:N","level":"medium"}],"cwes":["CWE-20","CWE-918"],"updated_at":"2026-11-02T15:46:37.528068Z"},{"id":"RHSA-2026:49401","title":"Red Hat Security Advisory: Red Hat Hardened Images RPMs Security Update","status":"published","published":"2026-08-04T10:26:38Z","modified":"2026-10-20T10:10:11Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N","level":"high"}],"updated_at":"2026-11-02T15:53:20.984762Z"},{"id":"RHSA-2026:50826","title":"Red Hat Security Advisory: Red Hat Hardened Images RPMs Security Update","status":"published","published":"2026-08-06T10:14:09Z","modified":"2026-10-20T10:10:31Z","cvss":[{"version":"3.1","score":8.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","level":"high"}],"updated_at":"2026-11-02T15:53:20.984762Z"},{"id":"UBUNTU-CVE-2026-69198","description":"ip-address is a library for parsing and manipulating IPv4 and IPv6 addresses in JavaScript. From 10.1.1 until 10.2.2, every special-use classification method is built on isInSubnet, which short-circuits to false whenever the address's own subnet mask is shorter than the reference range's mask. That mask comes verbatim from the CIDR suffix on the parsed input, so appending a suffix such as /0 suppresses classification entirely: isLoopback(), isPrivate(), isLinkLocal(), isCGNAT(), isMulticast(), isUnspecified(), isBroadcast(), isULA(), and getType() all report an internal address as unremarkable, while correctForm() and address still return the real internal target. An application that builds a network trust-boundary decision on these checks, for example a filter intended to block Server-Side Request Forgery, or SSRF, may therefore treat an internal target as external and allow the request. The underlying bit comparison is correct, and mask(n) already returns the first n bits of the full parsed address independently of subnetMask; the defect is solely that the containment guard sits in the classification path. This issue is fixed in version 10.2.2.","status":"published","published":"2026-08-03T20:17:00Z","modified":"2026-10-17T12:59:31.944795747Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:N/VA:N/SC:H/SI:N/SA:N","level":"medium"}],"updated_at":"2026-11-02T16:56:57.159276Z"},{"id":"CLSA-2026-1788790498","title":"TuxCare security update for ip-address (2 CVEs)","description":"TuxCare rebuilt ip-address to address 2 CVEs. Fixed in 6.4.0-tuxcare.2.","status":"published","published":"2026-09-07T14:14:58Z","modified":"2026-10-15T09:48:03.136654145Z","updated_at":"2026-11-02T15:43:47.938828Z"},{"id":"CLSA-2026-1790604982","title":"TuxCare security update for ip-address (2 CVEs)","description":"TuxCare rebuilt ip-address to address 2 CVEs. Fixed in 7.1.0-tuxcare.1.","status":"published","published":"2026-09-28T14:16:22Z","modified":"2026-10-15T09:48:03.246747061Z","updated_at":"2026-11-02T15:43:48.65893Z"},{"id":"CLSA-2026-1793097608","title":"TuxCare security update for ip-address (1 CVE)","description":"TuxCare rebuilt ip-address to address 1 CVE. Fixed in 10.1.1-tuxcare.3.","status":"published","published":"2026-10-27T10:40:08Z","modified":"2026-10-27T13:30:09.26027733Z","updated_at":"2026-11-02T15:43:55.34846Z"},{"id":"CLSA-2026-1793181094","title":"TuxCare security update for ip-address (1 CVE)","description":"TuxCare rebuilt ip-address to address 1 CVE. Fixed in 10.1.0-tuxcare.4.","status":"published","published":"2026-10-28T09:51:34Z","modified":"2026-10-28T13:45:02.686852888Z","updated_at":"2026-11-02T15:43:55.34846Z"}]}}
