# 🔀 Proxy Auto-Configuration (PAC)
<div class="book-article-intro">
<div>
A Proxy Auto-Config (PAC) file is a JavaScript function that determines whether web browser requests (HTTP, HTTPS, and FTP) go directly to the destination or are forwarded to a web proxy server.
<p>— <a href="https://en.wikipedia.org/wiki/Proxy_auto-config">Wikipedia</a></p>
</div>
</div>
## The "Smart" Routing Script
While static system proxies are simple (everything goes to one IP), they lack nuance. In a real-world network, you don't want to send traffic to your internal printer or a local dev server through a remote Rama proxy. You need a way to say: "Proxy the internet, but stay direct for the office."
This is exactly what a PAC file does. It is a single JavaScript file containing a function called `FindProxyForURL(url, host)`. Every time your browser wants to load a resource, it runs this function to get instructions.
The client passes two arguments to this function:
* **url**: The full destination URL (e.g., `https://example.com:8443/foo/bar?baz=1`).
* **host**: The host component extracted from the URL (e.g., `example.com`).
The function returns a string instructing the client on how to proceed:
* **`DIRECT`**: Connect to the destination server directly, bypassing the proxy.
* **`PROXY host:port`**: Connect via the specified HTTP proxy.
> [!NOTE]
> While some clients support `SOCKS`, `HTTPS`, or `SOCKS5` directives, `PROXY` and `DIRECT` are the most universally compatible across all platforms and browsers.
>
> Unless you really need to and know for sure it is supported it is best to only use
> the `PROXY` and `DIRECT` directives.
You can return multiple options separated by a semicolon (`;`). The client will attempt them in order.
## Script Example
The power of PAC lies in its simplicity. It provides a set of helper functions that allow you to make decisions based on the destination.
```javascript
function FindProxyForURL(url, host) {
// 1. Stay direct for local hostnames
if (isPlainHostName(host) || dnsDomainIs(host, ".local")) {
return "DIRECT";
}
// 2. Stay direct for the internal corporate network
if (isInNet(dnsResolve(host), "10.0.0.0", "255.0.0.0")) {
return "DIRECT";
}
// 3. Send everything else to our Rama proxy
// If the proxy is down, the browser will try to go DIRECT as a backup
return "PROXY proxy.rama.internal:8080; DIRECT";
}
```
### Key PAC Functions:
* `isPlainHostName(host)`: True if there are no dots in the hostname (e.g., `http://intranet`).
* `dnsDomainIs(host, ".com")`: Allows for domain-specific routing.
* `isInNet(ip, pattern, mask)`: Allows for IP-range based routing.
* `shExpMatch(str, shellExpression)`: Pattern matching for URLs using shell-style wildcards.
## Distribution: How Browsers Find the File
A PAC file isn't much use if the client doesn't know where it is. There are two primary ways to distribute a PAC script:
1. **Manual URL:** You enter the address (e.g., `http://config.rama.internal/proxy.pac`) into the System Proxy settings.
2. **WPAD (Web Proxy Auto-Discovery):** This is the "zero-config" method. The browser uses DNS or DHCP to look for a server named `wpad`. It then tries to download `http://wpad/wpad.dat`. While convenient, WPAD has significant security risks (like DNS poisoning), which is why many modern environments prefer manual URLs or MDM-pushed configs.
> Learn more about WPAD at <https://en.wikipedia.org/wiki/Web_Proxy_Auto-Discovery_Protocol>.
System proxy settings is the more common way to use PAC.
## The Limitations of PAC
As flexible as PAC files are, they share the same weakness as all [System Proxies](./system.md): **Application Compliance**.
A PAC file is a suggestion, not a law. Furthermore, because PAC relies on JavaScript, many non-browser applications (like low-level CLI tools or embedded devices) don't have a JavaScript engine to run the script. For those "blind" applications, the PAC settings are essentially invisible.
## Rama Support
Rama both **evaluates** PAC scripts (route your own client's traffic the way
a PAC file says) and **generates** them (hand a PAC file to clients you do
not control). It lives in `rama::js::pac`, behind the `pac` feature.
Evaluating means a request's URL goes through the script's `FindProxyForURL`
and the resulting proxy list is attached to that request, for rama's
connector stack to dial through — trying each proxy in the order the script
listed and falling back to the next when one is unreachable. `DIRECT`
becomes an explicit "no proxy" route rather than an absent one. Scripts are
compiled once and evaluated per request on a dedicated worker thread,
bounded in wall-clock time, so a hostile or runaway script cannot hold up
the client. Where the script *comes* from is left open: bundled with your
configuration, fetched over http, read from a file, cached with a TTL, or
anything else you can express as a rama service.
The full set of PAC host functions is available, including Microsoft's six
IPv6-aware extensions. Chromium defines five of them (not
`getClientVersion`), while Firefox defines none, so they can be left undefined
for a deployment that wants only the classic surface. Rama also follows
WinHTTP by preferring `FindProxyForURLEx` when it is available. Name
resolution goes through rama's own DNS stack rather than a second one. Two
choices here are worth being aware of because they are about what a script
gets to *see*: `https` URLs are
stripped down to their origin before the script sees them (a proxy decision
needs the origin, not which page someone visited), and `myIpAddress` tells
a script something about your network topology. Both are configurable, and
default to what browsers do.
`shExpMatch` reads its pattern the way browsers do — as a regex under the
covers, so a pattern like `vpn[0-9].corp.example` means there what it means
here. That inheritance includes the sharp edges: an unparenthesised `|`
anchors only one of its branches, and a bracket expression written literally
(an IPv6 address, say) is a character class. A deployment that would rather
have a pattern mean exactly what it spells can opt into literal matching.
A script also cannot decide how much work its request is worth. Rama's
execution time limit only reaches JavaScript, not the native work a helper
function does, so name resolution and glob matching carry per-request
budgets of their own; exhausting one fails that request rather than quietly
answering "no match", since the latter is how a rule gets bypassed. The
number of proxies a verdict may name is bounded the same way.
Note that a PAC script only ever names *where* to connect. It does not
decide how rama talks to that proxy: socks5 DNS behaviour, credentials,
TLS, and connection reuse all remain the connector's business.
WPAD auto-discovery (DHCP option 252 / DNS `wpad`) is **not** implemented —
point rama at a script URI explicitly. `SOCKS`/`SOCKS4` directives are
skipped, as rama has no SOCKS4 support.
See the [`rama::js::pac` module docs][pac-docs] for the API, and
[`http_pac_client`][pac-example] for a client that routes through a
generated script.
[pac-docs]: https://ramaproxy.org/docs/rama/js/pac/index.html
[pac-example]: https://github.com/plabayo/rama/tree/main/examples/src/http_pac_client.rs
## Evaluate and Generate PAC Files
The `rama pac` command evaluates existing scripts and generates simple,
ordered domain policies. A positional PAC source is read from an existing
file, or treated as inline JavaScript otherwise. Use `-`, `--stdin`,
`--file`, or `--source` when the source should be explicit.
Evaluate one or more URIs immediately. As with `rama send`, `http` is assumed
when a scheme is omitted; results report the completed, canonical URI:
```bash
rama pac eval proxy.pac \
www.example.com \
https://service.corp.example/
```
With a named source, newline-delimited URIs can come from stdin:
```bash
printf '%s\n' https://a.example/ https://b.example/ | rama pac eval proxy.pac
```
Without URI arguments in a terminal, `eval` opens a line-oriented REPL with
cursor editing and in-session history navigation. Its `:help` command lists
source reload, realm reset, and URL-sanitization controls. Batch output supports
`text`, `json`, and `jsonl`; `--offline` disables DNS and local-interface
disclosure, while `--fresh` uses a new JavaScript realm for every URI instead
of preserving script-global state.
Generate a PAC file from first-match-wins rules:
```bash
rama pac generate \
--route 'exact:health.corp.example=DIRECT' \
--route '*.corp.example=PROXY proxy.example:8080; DIRECT' \
--default 'DIRECT' \
--output proxy.pac
```
Ordinary domains match themselves and their subdomains. Prefix a route's
domain list with `exact:` when only those hosts should match. Repeat
`--route` in precedence order; comma-separate domains that share the same
directive list. Existing output files are not replaced unless `--force` is
passed.
## More Resources
* **[MDN: Proxy Auto-Configuration](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file)**: Excellent documentation on built-in helper functions (like `shExpMatch`). *Note: These functions can be slow; Safechain often uses optimized custom logic instead.*
* **[Cloudflare: PAC Best Practices](https://developers.cloudflare.com/cloudflare-one/networks/resolvers-and-proxies/proxy-endpoints/best-practices/)**: Modern performance tips.
* **[Microsoft: WinHTTP IPv6 Extensions](https://learn.microsoft.com/en-us/windows/win32/winhttp/ipv6-aware-proxy-helper-api-definitions)**: Information on `FindProxyForURLEx` and the other IPv6-aware helpers. Microsoft defines all six helpers; Chromium defines five (omitting `getClientVersion`), and Firefox defines none, so a script cannot assume the full set exists everywhere.