# IPv6 in PAC files: isInNet, isInNetEx and what actually works

Source: https://findproxyforurl.net/articles/ipv6-in-pac-files/ · updated 2026-10-06

Most PAC files were written for IPv4, and their helper functions were too. On dual-stack networks that leads to quiet surprises. IPv6 literals go to the proxy, isInNet is never true for them, and the IPv6 functions exist only in some engines.

## Three facts to start with

1. **`isInNet` is IPv4 only.** `isInNet(host, "fd00::", "ffff::")` is never true
   ([PAC-K014](https://findproxyforurl.net/rules/isinnet-with-ipv6/)).
2. **The `*Ex` functions are not portable.** `isInNetEx`, `dnsResolveEx`, `myIpAddressEx`,
   `isResolvableEx` and `sortIpAddressList` come from Microsoft's IPv6 extensions. Chromium
   implements them (source: code, verified 2026-10-04). Firefox does not have them, so a call throws
   and the request goes direct ([PAC-K003](https://findproxyforurl.net/rules/ex-functions-not-portable/); see the
   [function availability table](https://findproxyforurl.net/functions/#matrix)).
3. **`FindProxyForURLEx` is ignored by browsers.** Chromium and Firefox only call
   `FindProxyForURL` (source: code, verified 2026-10-04). A file that only defines the Ex entry point
   does nothing in a browser ([PAC-K004](https://findproxyforurl.net/rules/findproxyforurlex-only/)).

## The usual symptom

The PAC sends `127.0.0.0/8` and the RFC 1918 ranges direct, but has nothing for `::1`, link-local or
unique-local addresses. An IPv6 literal fails the IPv4 regular expression and takes the default
route, usually the proxy, where the connection fails. The tickets read "works with the IP, not with
the name" or the reverse ([PAC-C012](https://findproxyforurl.net/rules/ipv6-not-handled/)).

## Portable handling

String tests work in every engine and need no DNS:

```js
function FindProxyForURL(url, host) {
  host = host.toLowerCase();
  // IPv6 loopback, link-local and unique-local literals go direct
  if (host == "::1" || shExpMatch(host, "fe80:*") ||
      shExpMatch(host, "fc*:*") || shExpMatch(host, "fd*:*")) {
    return "DIRECT";
  }
  // ... IPv4 and name rules ...
  return "PROXY proxy.corp.example:8080";
}
```

Whether engines pass IPv6 literals in `host` with or without brackets has not been confirmed in our
lab for every engine yet (source: expert, unverified). Test with your client, or add both forms.

If you really need `isInNetEx`, guard it:

```js
if (typeof isInNetEx === "function" && isInNetEx(host, "2001:db8::/32")) {
  return "DIRECT";
}
```

## Proxy addresses

Return proxies by **name**, not by IPv6 literal. Some services do not accept IPv6 in `PROXY`
statements at all. Palo Alto Networks' PAC file guidelines for Prisma Access say so explicitly
([PAC-K019](https://findproxyforurl.net/rules/prisma-ipv6-proxy-address/)).

## Check your file

The [PAC file tester](https://findproxyforurl.net/check/) flags `*Ex` calls, IPv6 networks in `isInNet` and missing IPv6
exceptions, and shows where Chromium and Firefox disagree.


## Frequently asked questions

### Does isInNet work with IPv6?

No. isInNet parses dotted-quad IPv4 addresses and masks. With an IPv6 network or mask it is false in every engine.

### Can I use isInNetEx in a PAC file?

Only where the engine provides it. Chromium and WinHTTP implement the Microsoft IPv6 extensions; Firefox does not, so an unguarded call throws there and the request goes direct. Guard it with typeof isInNetEx === "function" and provide a fallback.

### How do I send local IPv6 addresses direct?

Use string tests on host, which are portable. Check host == "::1" for loopback, and shExpMatch(host, "fe80:*"), "fc*:*" or "fd*:*" for link-local and unique-local addresses.

