# How a PAC File Chooses a Proxy

> A PAC file is one small JavaScript function, FindProxyForURL(url, host), that a browser calls for every request to decide between going direct and using a proxy. This explains the return-string grammar (DIRECT, PROXY, SOCKS, and semicolon failover), the helper functions and which of them force a blocking DNS lookup, the traps that bite in practice, and how Netskope uses a PAC to steer traffic to its Cloud Explicit Proxy.

Source: https://ronutz.com/en/learn/how-a-pac-file-chooses-a-proxy  
Updated: 2026-07-06  
Related tools: https://ronutz.com/en/tools/pac-file-explainer

---

Proxy auto-config predates almost everything else in web infrastructure, and it is still how a great deal of enterprise traffic is steered. A PAC file is a single JavaScript function that the browser evaluates for every request. Understanding it is the difference between trusting a steering configuration and guessing at it. This explains the model the paired tool reads.

## One function, called for every request

A PAC file defines exactly one entry point:

```
function FindProxyForURL(url, host) {
  // ... return a proxy string
}
```

The browser calls it with the full `url` and the extracted `host`, and expects a string back telling it where to send the request. Everything else in the file exists to help that function decide. The file is served with the MIME type `application/x-ns-proxy-autoconfig`, usually from a URL configured in the browser or discovered by WPAD.

There is one modern subtlety worth knowing up front: many Chromium-based browsers now strip the path and query from `https://` URLs before calling the function, passing only the scheme, host, and port. Logic that tries to match on the path of an HTTPS URL will not see it. Matching on the host still works.

## The return-string grammar

The string `FindProxyForURL` returns is one or more directives separated by semicolons. The browser tries them left to right and uses the first one that works, which makes the semicolon a failover mechanism.

The directives are `DIRECT` (connect straight to the destination with no proxy), `PROXY host:port` (use that HTTP proxy), and `SOCKS host:port` (use that SOCKS server). Newer browsers also accept `HTTP host:port`, `HTTPS host:port` (a TLS-encrypted connection to the proxy), and the explicit `SOCKS4` and `SOCKS5` forms. So `PROXY primary:8080; PROXY backup:8080; DIRECT` means "try the primary proxy, then the backup, and if neither answers, go direct." That trailing `DIRECT` is a common and deliberate safety net: without it, an unreachable proxy can leave traffic with nowhere to go.

## The helper functions, and the ones that cost a DNS lookup

PAC files decide using a fixed library of helper functions. Most of them are pure string operations and cost nothing: `isPlainHostName` (true for an unqualified single-label name, the classic way to send intranet short names direct), `dnsDomainIs` (host ends in a domain suffix), `shExpMatch` (a shell-glob match), `dnsDomainLevels`, `weekdayRange`, `dateRange`, and `timeRange`.

Three helpers are different, and the distinction matters for performance: `isInNet`, `isResolvable`, and `dnsResolve` all consult DNS. MDN calls this out explicitly, because a DNS lookup on every request can block and slow browsing. The practical rule is to order your checks so the cheap string comparisons decide first, and DNS is only consulted when nothing else has. A file that reaches for `isInNet` at the top of the function pays that cost on every single request.

Two more sharp edges are worth internalizing. `shExpMatch` uses shell-glob wildcards (`*` and `?`), not regular expressions, so a pattern written in regex syntax silently fails to match. And `myIpAddress` is unreliable on multi-homed machines and can fall back to a loopback address such as `127.0.0.1`, so logic that branches on it may behave differently than expected on machines with a VPN or multiple interfaces. There is also a set of Microsoft IPv6 extensions with an `Ex` suffix (`dnsResolveEx`, `isInNetEx`, `myIpAddressEx`, and others) that are supported by Chromium but not by Firefox.

## How Netskope uses a PAC

A Secure Web Gateway needs a way to get the browser to send traffic to it, and one of the standard ways is a PAC file. Netskope's Cloud Explicit Proxy is steered exactly this way. A Netskope PAC returns `PROXY eproxy-<tenant>.goskope.com:8081` for the web traffic it should inspect, with the tenant name filled in, and returns `DIRECT` for the things that must bypass the proxy: plain hostnames, and the identity-provider hosts the login flow depends on. Port 8081 is the explicit-proxy port. Because the proxy is doing TLS inspection, the Netskope root CA has to be trusted on the client, and Netskope uses cookie surrogates to carry user identity through the explicit-proxy path. Reading a Netskope PAC, then, is mostly a matter of separating the `DIRECT` bypasses from the one `PROXY` line that sends everything else to the tenant's explicit-proxy host.

## Where this comes from

The PAC mechanics here are from the MDN Proxy Auto-Configuration reference and the Wikipedia proxy auto-config article (for the Microsoft IPv6 extensions); the Netskope specifics are from Netskope's Cloud Explicit Proxy documentation. The paired tool reads a pasted PAC file lexically and explains it; it never evaluates the file or contacts a proxy.
