Repository navigation
Comparing changes
Open a pull request
base repository: beaugunderson/ip-address
base: v10.4.0
head repository: beaugunderson/ip-address
compare: v10.7.3
- 20 commits
- 21 files changed
- 1 contributor
Commits on Aug 10, 2026
-
Honor the fromURL graceful-failure contract for non-IPv6 hosts (#218)
Address6.fromURL() is documented and typed to return `{ error, address: null, port: null }` when a URL can't be parsed, but that contract was only implemented on the regex-miss path. The URL host character class `[0-9a-f:.]` is a superset of valid IPv6, so any host made only of hex digits, colons and dots matched the regex and was handed straight to `new Address6(host)`, whose AddressError propagated out uncaught. IPv4 literals are the visible case — `http://127.0.0.1/`, `http://169.254.169.254/`, `http://0.0.0.0/` all threw — but so did `[:::]`, `[1:2:3:4:5:6:7:8:9]` and `[::ffff:999.1.1.1]`. A plain hostname like `example.com` avoided the throw only because letters outside a-f break the regex match first. Catch the constructor and return the same error object the regex-miss path returns, so the function never throws for any input. Reported by @zikk090 via GHSA-mxvh-v779-f36j. Declining that as a security advisory: an uncaught synchronous throw from a parser on the caller's own stack is a correctness bug, not a denial of service.Configuration menu - View commit details
-
Copy full SHA for d478737 - Browse repository at this point
Copy the full SHA d478737View commit details -
Correct the documentation where it disagreed with the library (#219)
An audit of every documented claim against the built library. All 40 worked examples (18 in the README block, 22 JSDoc @example) already passed; the inaccuracies were in prose and in generated signatures. Generated signatures (scripts/build-readme.ts): - Six signatures declared required parameters that carry defaults, two of them contradicting their own prose in the same bullet ("The default prefix is the well-known prefix 64:ff9b::/96" beside a signature saying you must pass it). TypeDoc sets isOptional inconsistently for defaulted parameters, so treat a default as optional too. - TypeScript `this` annotations rendered as arguments, so `isInSubnet(address)` read as taking two. Filter them out, and route function-valued properties through the same parameter rules. Prose: - Address4.reverseForm() was documented as returning ip6.arpa form. It returns in-addr.arpa, as the @PARAM two lines below already said. - 6to4 embeds a 32-bit IPv4 address in bits 16-47, not "the second 16 bits". - "Parses all standard IPv4 and IPv6 notations" claimed more than the library does: the inet_aton forms are rejected by design, which SECURITY.md already says. - group() and groupForV6() return HTML and said nothing about it. These are the surfaces GHSA-v2v4-37r5-5v8g was about. - AddressError.parseMessage had no description at all, though GHSA-v2v4-37r5-5v8g describes rendering it as HTML as "its documented purpose". - Address4's byte-array methods documented neither their throw conditions nor that they reject the signed bytes Address6 folds. The 11.0.0 tripwire in test/common-test.ts owns settling that split; this only writes down where it stands. - isHostInSubnet pointed at {@link common.isHostInSubnet}, a module the reference doesn't cover. Inlined instead. - Weekly download figures were stale (~66M against 85.7M actual). Badges and source links now use main, which is the default branch, so they no longer lean on GitHub's renamed-branch redirect. test/readme-test.ts guards the generator fix: no signature may expose a `this` parameter, defaulted parameters must render optional, and the HTML-returning methods must say so. All three fail against the previous README.
Configuration menu - View commit details
-
Copy full SHA for 9fd1110 - Browse repository at this point
Copy the full SHA 9fd1110View commit details -
Configuration menu - View commit details
-
Copy full SHA for ef98e0a - Browse repository at this point
Copy the full SHA ef98e0aView commit details
Commits on Aug 29, 2026
-
isPrivate() covered ULAs and IPv4-mapped / NAT64 well-known addresses that embed an RFC 1918 address, but nothing in the RFC 8215 local-use range 64:ff9b:1::/48, so 64:ff9b:1:7f00:0:100:: (127.0.0.1 under a /48 NAT64 prefix) read as non-internal to every classifier. The range cannot be handled by decoding an embedded IPv4 address the way the well-known prefix is: an operator picks a prefix of any RFC 6052 length inside the /48, so the same bits decode differently under a /48 and a /96 deployment. The whole range is not globally reachable per the IANA special-purpose registry (Python's ipaddress agrees, is_private is True), so isPrivate() reports it private as a whole.
Configuration menu - View commit details
-
Copy full SHA for ab3dc88 - Browse repository at this point
Copy the full SHA ab3dc88View commit details -
isLinkLocal() compared the first 64 bits against fe80:0:0:0, so it only recognized fe80::/64 and returned false for fe81::1, febf::1, fe80:0:0:1::1 and the rest of the /10 that RFC 4291 §2.4 assigns to link-local unicast. getType() and getScope() already matched fe80::/10 via the TYPES table, so the library disagreed with itself on the same address. Use the same subnet test the rest of the library uses. Also add 2001::/32 to the TYPES table so getType() reports 'Teredo' for the addresses isTeredo() already returns true for.
Configuration menu - View commit details
-
Copy full SHA for d03e960 - Browse repository at this point
Copy the full SHA d03e960View commit details -
Configuration menu - View commit details
-
Copy full SHA for 38b02d7 - Browse repository at this point
Copy the full SHA 38b02d7View commit details -
Add isGlobal() and pin the classifiers to the IANA special-purpose re…
…gistries (#224) * Add isGlobal() and pin the classifiers to the IANA special-purpose registries A guard written as an OR of named classifiers covers only the ranges it names, and the IANA registries hold more ranges than there are names. A differential sweep of every registry block boundary against Python's ipaddress found eight ranges no classifier caught: 0.0.0.0/8, the IETF protocol assignments in 192.0.0.0/24 and 2001::/23, the three IPv4 documentation blocks, 198.18.0.0/15, 240.0.0.0/4, 100::/64 and 100:0:0:1::/64, and 3fff::/20. isGlobal() on both classes answers from a table that mirrors the registry row for row, taking the most specific entry's Globally Reachable column, treating multicast as not global, and answering for the embedded IPv4 address of mapped and NAT64 well-known forms. SECURITY and the README point guards at it. For parity with the IPv6 side, Address4 gains isDocumentation(), isBenchmarking() and isReserved(); Address6.isDocumentation() covers 3fff::/20 and Address6 gains isBenchmarking(); getType() learns Benchmarking, Discard-only and the second Documentation block. test/data/iana-corpus.json is generated by scripts/gen-iana-corpus.py from the registry CSVs checked in beside it: every block's first, last, neighboring and middle addresses plus their mapped and NAT64 forms, each carrying the registry's answer and Python's. The suite asserts isGlobal() matches the registry on all 592 and the named classifiers match Python, with the four rows where Python 3.14 and the registry disagree listed as tripwires. * Gate Address6.isGlobal() on the 2000::/3 global unicast allocation The special-purpose registry lists exceptions, but the IANA IPv6 Address Space Registry says only 2000::/3 is allocated for global unicast at all, so the deprecated site-local fec0::/10, the deprecated IPv4-compatible ::/96, and unallocated space such as 4000::/3 have nowhere to route. isGlobal() answered true for all of them; a survey of recent SSRF advisories found fec0::/10 named as a deny-list gap (GHSA-w98g-5w9p-p3rc). The address-space registry CSV joins the fixtures and its block boundaries join the corpus (730 probes). getType() names the two deprecated ranges.
Configuration menu - View commit details
-
Copy full SHA for fb12583 - Browse repository at this point
Copy the full SHA fb12583View commit details -
Configuration menu - View commit details
-
Copy full SHA for 42c1f8b - Browse repository at this point
Copy the full SHA 42c1f8bView commit details -
Add offset() and nextNetwork(), accept prefix-length ip6.arpa names, …
…correct the IPv6 end-address docs (#225) offset(n) returns the address n after (or before) this one with the same subnet mask; nextNetwork() returns the network after endAddress(). Both take a number or bigint, reject non-integers, and throw when the result leaves the address space. Address6.fromArpa() accepts a name of 1 to 32 nibbles and returns the network it delegates with a mask of four bits per nibble, so fromArpa(x.reverseForm()) round-trips for any prefix; a full 32-nibble name still yields a /128. The root dot is optional. endAddress() and endAddressExclusive() on Address6 no longer describe a broadcast address: IPv6 has none, the last address is assignable, and the exclusive form drops exactly one address rather than the RFC 2526 reserved subnet-anycast block.
Configuration menu - View commit details
-
Copy full SHA for 87fae23 - Browse repository at this point
Copy the full SHA 87fae23View commit details -
Configuration menu - View commit details
-
Copy full SHA for 2b7cab5 - Browse repository at this point
Copy the full SHA 2b7cab5View commit details -
Bump js-yaml and brace-expansion in the lockfile (#226)
Dev-only transitive dependencies flagged by Dependabot (#131) and npm audit: js-yaml 4.3.0 to 4.3.2 (quadratic CPU in !!omap resolution) and brace-expansion 1.1.16 / 2.1.2 / 5.0.8 to 1.1.18 / 2.1.4 / 5.0.9. Every dependent already accepted the patched ranges, so only the lockfile changes; every resolved URL stays on registry.npmjs.org with an integrity hash. npm audit reports 0 vulnerabilities.
Configuration menu - View commit details
-
Copy full SHA for 4c2184a - Browse repository at this point
Copy the full SHA 4c2184aView commit details
Commits on Sep 15, 2026
-
Report an address of the other family as not contained
isHostInSubnet() compared masked binary strings with no family check. Address4 pads to 32 bits and Address6 to 128, so whenever the leading bits agreed the strings were equal: a00::1 read as inside 10.0.0.0/8 and 32.0.0.1 as inside 2000::/3. isInSubnet() delegates to it and inherited the answer. An address of one family is never inside a network of the other, so both now return false when the widths differ. Callers that mean to compare across families convert first with Address6.fromAddress4(), to4(), or toAddress4Nat64().
Configuration menu - View commit details
-
Copy full SHA for 1343629 - Browse repository at this point
Copy the full SHA 1343629View commit details -
Reject an address longer than the family allows before parsing it
The bad-character diagnostic wraps every offending character in a <span class="parse-error">, so an N-byte string of punctuation cost about 106N bytes and a synchronous String.replace over all of it: 8 MiB of '!' took 529 ms and 895 MB inside Address6.isValid(), which built the diagnostic only to discard it. At 16 MiB the replace exceeds V8's maximum string length and throws a RangeError in place of the AddressError callers catch; at 32 MiB V8 aborts the process outright with "Fatal JavaScript invalid size error" (node 24.19.0). An IPv6 address minus its suffix and zone is at most 45 characters (six four-digit groups, six colons, a dotted quad), and CPython's ipaddress module draws the same line, so anything longer is rejected before parse() runs. Address4 gets the same treatment at 15 characters.
Configuration menu - View commit details
-
Copy full SHA for 469ead1 - Browse repository at this point
Copy the full SHA 469ead1View commit details -
isHostInSubnet() compared masked binary strings with no family check. Address4 pads to 32 bits and Address6 to 128, so whenever the leading bits agreed the strings were equal: a00::1 read as inside 10.0.0.0/8 and 32.0.0.1 as inside 2000::/3. isInSubnet() delegates to it and inherited the answer. An address of one family is never inside a network of the other, so both now return false when the widths differ. Callers that mean to compare across families convert first with Address6.fromAddress4(), to4(), or toAddress4Nat64().
Configuration menu - View commit details
-
Copy full SHA for 13b6155 - Browse repository at this point
Copy the full SHA 13b6155View commit details -
The bad-character diagnostic wraps every offending character in a <span class="parse-error">, so an N-byte string of punctuation cost about 106N bytes and a synchronous String.replace over all of it: 8 MiB of '!' took 529 ms and 895 MB inside Address6.isValid(), which built the diagnostic only to discard it. At 16 MiB the replace exceeds V8's maximum string length and throws a RangeError in place of the AddressError callers catch; at 32 MiB V8 aborts the process outright with "Fatal JavaScript invalid size error" (node 24.19.0). An IPv6 address minus its suffix and zone is at most 45 characters (six four-digit groups, six colons, a dotted quad), and CPython's ipaddress module draws the same line, so anything longer is rejected before parse() runs. Address4 gets the same treatment at 15 characters. # Conflicts: # README.md
Configuration menu - View commit details
-
Copy full SHA for 8b34a21 - Browse repository at this point
Copy the full SHA 8b34a21View commit details -
Configuration menu - View commit details
-
Copy full SHA for f0c25df - Browse repository at this point
Copy the full SHA f0c25dfView commit details -
Accept an arpa suffix in any case and without the root dot in fromArpa (
#227) Address4.fromArpa() stripped `.in-addr.arpa` only when a root dot followed it, so `1.0.0.127.in-addr.arpa` threw while `1.0.0.127.in-addr.arpa.` parsed; Address6.fromArpa() already accepted both. Neither family accepted an uppercase suffix, though DNS names are case-insensitive. Both now strip the suffix in any case, with or without the root dot. Address4.fromArpa() also gets its own tests, including the reverseForm round trip for a host and a network.
Configuration menu - View commit details
-
Copy full SHA for 4dfe8e5 - Browse repository at this point
Copy the full SHA 4dfe8e5View commit details -
Configuration menu - View commit details
-
Copy full SHA for 974b48d - Browse repository at this point
Copy the full SHA 974b48dView commit details
Commits on Oct 1, 2026
-
Reject an in-addr.arpa name longer than 32 characters before splittin…
…g it (#228) Address4.fromArpa() split, reversed and joined its whole argument before handing the result to the constructor, so the constructor's 15-character limit ran only after work proportional to the input: about 8 ms per MB (node 24.19.0), against under 0.01 ms for the same string in Address6.fromArpa(), whose nibble grammar rejects it first. The longest name the constructor accepts is a 15-character address, a "/32" prefix length on its last octet (the RFC 2317 classless form) and ".in-addr.arpa.": 32 characters. Anything longer is rejected on entry. Every name that parsed before still parses; a longer one throws AddressError as it did, with a message naming the 32-character limit.
Configuration menu - View commit details
-
Copy full SHA for 09b8072 - Browse repository at this point
Copy the full SHA 09b8072View commit details -
Configuration menu - View commit details
-
Copy full SHA for c7f838e - Browse repository at this point
Copy the full SHA c7f838eView commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff v10.4.0...v10.7.3