ip-address: parsing and validating IPv4 and IPv6 in JavaScript
💻 a library for parsing and manipulating IPv4 and IPv6 addresses in JavaScript
At a glance
- What is it?
- ip-address is a zero dependency TypeScript library for parsing, validating and manipulating IPv4 and IPv6 addresses, with subnet membership checks, correct and canonical form conversions, Teredo and 6to4 decoding and support for the tricky address forms that cause SSRF filter bypasses. It deliberately rejects the inet_aton shorthand and leading zero octets.
- Who is it for?
- Adopt ip-address if you are writing JavaScript or TypeScript that has to validate user supplied IP addresses, compute subnet membership or convert between address representations, and you want one library that covers IPv4 and IPv6 with the same API. Do not adopt it as an SSRF defence on its own, because the repository says plainly that its classifiers are not an SSRF defence, and remember that it parses rather than resolves, so a hostname is your problem.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Two address classes behind one API
ip-address is a library for validating and manipulating IPv4 and IPv6 addresses in JavaScript and TypeScript, and it ships two entry classes, Address4 and Address6, with a parallel static and instance API on each.
The static methods are the entry point for validation and construction, and the instance methods are where the manipulation lives. Address4.isValid takes a string and returns a boolean, and the same exists on Address6. The constructors parse, and they throw an AddressError on invalid input, so the split between isValid and new is the split between a check and a conversion.
import { Address4, Address6 } from 'ip-address';
// Validation
Address4.isValid('192.168.1.1'); // true
Address6.isValid('2001:db8::1'); // true
Address6.isValid('not an address'); // false
// Parsing (throws AddressError on invalid input)
const v4 = new Address4('192.168.1.1/24');
const v6 = new Address6('2001:db8::1/64');The install is a single npm install of the ip-address package, and the package description in the manifest is a library for parsing IPv4 and IPv6 IP addresses in node and the browser. The engines field requires Node 12 or newer, and the published files list is just dist, so the whole package is the compiled output with no extras.
The manifest shows the build is plain tsc with a clean step, the main entry is dist/ip-address.js and the types are dist/ip-address.d.ts, and the package is marked sideEffects false, which lets a bundler drop it entirely when nothing is imported from it. The licence is MIT and the version is 10.7.4.
Subnets, ranges and the strictness distinction that catches people
The subnet API is the part most applications need, and there is a distinction in it that the readme calls out specifically because it is a genuine footgun.
Membership and range queries are straightforward. isInSubnet answers whether one address falls inside another, and startAddress and endAddress give the bounds of a subnet's range:
// Subnet membership
const host = new Address4('192.168.1.42');
const network = new Address4('192.168.1.0/24');
host.isInSubnet(network); // true
// Subnet range
network.startAddress().correctForm(); // '192.168.1.0'
network.endAddress().correctForm(); // '192.168.1.255'Now the trap. isValid accepts CIDR notation with host bits set, so 192.168.1.5/24 is a valid host-with-subnet, but it is not a network address. The readme shows how to check for the strict case by comparing the correct form against the start address:
// Strict network-address check (host bits must be zero).
// isValid() accepts CIDRs with host bits set — '192.168.1.5/24' is a valid
// host-with-subnet, but it isn't a network address.
const cidr = new Address4('192.168.1.5/24');
Address4.isValid('192.168.1.5/24'); // true
cidr.correctForm() === cidr.startAddress().correctForm(); // falseThe static method documentation says the same thing in prose, and adds that you can use networkForm() for the strict check instead of doing the comparison yourself.
That is a small API decision with a large consequence for security code. A filter that treats any address with a prefix as a blocklist entry is not doing what it looks like, because 192.168.1.5/24 and 192.168.1.0/24 are the same block but only one of them equals its own start address. The library gives you both behaviours and tells you which is which, rather than choosing for you, which is the right call for a library that cannot know whether you are validating a host or a network.
Special properties, tunnels and numeric forms
Beyond the basics, the library covers the address categories that show up in security and network code, and the classification methods are named after the RFCs they come from.
The special property checks are private (RFC 1918) for IPv4 and ULA (RFC 4193) for IPv6, plus loopback, link-local, multicast, broadcast, unspecified, CGNAT, documentation, Teredo, 6to4 and v4-in-v6. The readme's example shows how they read:
// Address properties
const link = new Address6('fe80::1');
link.isLinkLocal(); // true
link.isMulticast(); // false
link.isLoopback(); // false
new Address4('192.168.1.1').isPrivate(); // true (RFC 1918)
new Address6('fc00::1').isULA(); // true (RFC 4193)Tunneling protocols get their own decoding because the address alone carries the endpoint. inspectTeredo decodes the IPv4 client embedded in a 2001::/32 address, and inspect6to4 decodes the address embedded in bits 16 to 47 of a 2002::/16 address. Teredo is documented as a tunneling protocol that encodes an IPv4 endpoint, port and flags inside the IPv6 address, and 6to4 as one that embeds a full 32-bit IPv4 address in the second and third groups.
The numeric and byte representations cover the conversions you need when talking to something other than JavaScript:
// Numeric and byte representations
v4.bigInt(); // 3232235777n
v4.toArray(); // [192, 168, 1, 1]
v6.canonicalForm(); // '2001:0db8:0000:0000:0000:0000:0000:0001'And there is a URL helper, Address6.fromURL, which parses the host and the port out of a URL. The readme shows it used on an IPv6 literal in brackets:
// Parse host + port from a URL
Address6.fromURL('http://[2001:db8::1]:8080/').port; // 8080The feature list also names the reverse DNS direction, in-addr.arpa and ip6.arpa, alongside canonical and correct form, hex, binary, decimal and byte array conversions.
Why inet_aton forms and leading zeros are refused
The parsing strictness is a security decision, and the readme links a specific section of the repository's SECURITY.md about classifiers not being an SSRF defence. It is worth reading that note before you use this library for anything security related.
The rule is that the inet_aton forms, which are 2130706433, 0x7f000001 and 127.1, are rejected by design, and so are leading-zero octets. Both of those are forms that different parsers read differently.
That is the whole problem. 127.1 means 127.0.0.1 to a system that implements the old BSD inet_aton shorthand, and means nothing much to a parser that expects dotted quad. If your filter accepts 127.1 and the component that later resolves it accepts 127.0.0.1, you have a bypass. The same argument applies to 0177.0.0.1 versus 127.0.0.1, and to 2130706433, which is 127.0.0.1 in decimal. Every one of those is a different string for the same address, and a validation layer that accepts one form and a consumer that understands another is how an SSRF filter gets bypassed.
So the library is deliberately strict, and the readme is explicit that this is by design. The important caveat is in the linked SECURITY.md heading, that classifiers are not an SSRF defence. Validation of the string is not the same as checking that the address is safe to connect to, and a library that validates addresses is not going to stop you from fetching a link that resolves to a private range. That distinction between parsing and policy is the real lesson, and the library is careful enough to put it in its own security document.
Two forms of IPv6 text, and the vocabulary around them
The readme has a terminology section for the terms that cause confusion, and it is genuinely useful because the two main output forms are easy to mix up.
Correct form is the shortest valid representation, per RFC 5952: leading zeros stripped, the longest run of zero groups collapsed to a double colon, and hex digits lowercased, so 2001:db8::1. The readme says this is what most software displays.
Canonical form is the fully expanded representation: all 8 groups, each padded to 4 hex digits, with no collapsing, so 2001:0db8:0000:0000:0000:0000:0000:0001. It is the one to use for sorting and byte-exact comparison.
The other terms in the section are worth having in the vocabulary. Subnet is the network portion expressed as a CIDR prefix length, with startAddress and endAddress giving the range bounds. Zone is the IPv6 scope identifier appended after a percent sign, used to disambiguate link-local addresses across interfaces, for example fe80::1%eth0. v4-in-v6 is the mixed notation embedding an IPv4 address as the last 32 bits, as in::ffff:192.168.0.1, used for IPv4-mapped IPv6 addresses.
A library with a terminology section is a library whose users are going to be confused by it, which is a good sign. The distinction between the two forms in particular shows up in practice: if you are comparing addresses for equality, comparing the correct form of two addresses is fine, but if you are sorting or deduplicating on a canonical key, the expanded form is the one that will not surprise you when the same address arrives with different spellings.
The static constructors are also broader than the two main classes. Address4 has fromAddressAndMask for a dotted-decimal mask, fromAddressAndWildcardMask for the Cisco style inverse mask, and fromWildcard for a pattern with trailing star octets, each throwing AddressError on a non-contiguous mask,
Editorial conclusion
Adopt ip-address if you are writing JavaScript or TypeScript that has to validate user supplied IP addresses, compute subnet membership or convert between address representations, and you want one library that covers IPv4 and IPv6 with the same API. Do not adopt it as an SSRF defence on its own, because the repository says plainly that its classifiers are not an SSRF defence, and remember that it parses rather than resolves, so a hostname is your problem. Before you commit, read the SECURITY.md note on rejected address forms, decide whether your use case needs the wildcard mask and reverse DNS helpers, and check that the strictness on leading zero octets matches what your callers send.
Frequently asked questions
What does the ip-address library do?
It parses, validates and manipulates IPv4 and IPv6 addresses in JavaScript and TypeScript. There is an Address4 class and an Address6 class with parallel static and instance APIs, covering subnet membership, range queries, special property checks, Teredo and 6to4 decoding, and conversions to canonical form, byte arrays and BigInt.
How do I install ip-address?
With npm install ip-address. It is written in TypeScript with full type definitions, usable from both CommonJS and ESM, has zero runtime dependencies, and runs in Node.js and the browser. The published package contains only the dist directory.
Why does ip-address reject forms like 127.1 or 0177.0.0.1?
Because different parsers read those inet_aton and leading-zero forms differently, and a validator that accepts one spelling while a consumer understands another is how an SSRF filter gets bypassed. The readme says these forms are rejected by design and points at a section of SECURITY.md.
How do I check that an address is a network address rather than a host with a subnet?
isValid accepts CIDR notation with host bits set, so 192.168.1.5/24 is considered valid. For a strict check, compare correctForm() against startAddress().correctForm(), or use networkForm(). The readme also notes that isValid is not an SSRF defence.
What is the difference between correct form and canonical form in ip-address?
Correct form is the shortest valid representation per RFC 5952, with leading zeros stripped, the longest run of zero groups collapsed and hex digits lowercased, which is what most software displays. Canonical form is the fully expanded version with all 8 groups padded to 4 hex digits, useful for sorting and byte-exact comparison.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/beaugunderson-ip-address)Was this useful?