PAC rule catalogue
Every check the checker runs against a proxy.pac or wpad.dat file. Each rule explains why it matters, how the engines behave, and shows a bad and a good example you can open in the checker.
Errors and robustness
- PAC-E001PAC file does not parse critical
The file is not valid JavaScript. No engine can run it; every browser falls back to its no-PAC behaviour, which in practice is DIRECT.
- PAC-E002No FindProxyForURL function critical
The file parses but defines no function called exactly FindProxyForURL. Engines bind by that exact, case-sensitive name; nothing else is called.
- PAC-E003FindProxyForURL declared more than once high
Two or more function declarations named FindProxyForURL. JavaScript keeps the last one; the others are dead code that still looks authoritative to a reader.
- PAC-E004FindProxyForURL does not take exactly two parameters medium
Engines call FindProxyForURL(url, host). With one parameter the hostname is unavailable; with more than two the extra parameters are always undefined.
- PAC-E005Call to a function that is not defined critical
The function name is neither a PAC helper, an ES5 global, nor defined in the file. The branch throws when reached and the engine treats the request as a script failure.
- PAC-E006Evaluation throws an exception high
A test evaluation ended with an uncaught exception instead of a return value. The engine treats this as a script failure for that request.
- PAC-E007Evaluation did not finish within the time limit critical
A test evaluation ran into the sandbox timeout. Real engines have no comparable guard; a loop that never ends hangs proxy resolution for the browser.
- PAC-E008Loop without a reachable exit high
A while/for/do loop whose condition is a constant truthy value and whose body contains no break, return or throw. It can never terminate.
- PAC-E009Assignment used as a condition high
A single = inside an if condition assigns instead of compares. The condition is then simply the assigned value, so the branch fires for every request.
- PAC-E010isInNet called with an invalid address, mask or argument count high
The network or mask literal is not a dotted-quad IPv4 address, the mask is not contiguous, or the call has fewer than three arguments. The comparison is then false for every host.
- PAC-E011Return value is not a valid proxy string high
The function returns something that is not a string of semicolon-separated 'DIRECT' / 'PROXY host:port' / 'SOCKS host:port' entries (or an engine-specific extension). Engines drop what they cannot parse and typically end up DIRECT.
- PAC-E012Proxy port out of range or not numeric high
A PROXY/SOCKS entry carries a port that is not an integer between 1 and 65535. Browsers discard the entry; when no other entry follows, traffic goes DIRECT.
- PAC-E013File starts with a UTF-8 byte order mark medium
The first three bytes are a UTF-8 BOM, typically written by Windows editors. Modern browsers strip it; older or embedded engines may fail to parse the file.
- PAC-E014Non-ASCII characters in code high
The file contains characters outside 7-bit ASCII in code position, typically smart quotes from a word processor or a stray emoji. Smart quotes are a syntax error; other characters are at best unnecessary.
- PAC-E015Zero-width or bidirectional control characters high
Zero-width spaces, joiners, bidi marks, no-break spaces or a mid-file BOM. They are invisible in editors but change what a string literal contains, so a pattern that looks right never matches.
- PAC-E016Statements after an unconditional return medium
Statements that follow a return in the same block are never executed. Rules placed there look active but have no effect.
- PAC-E017PAC helper called with the wrong number of arguments high
A built-in PAC helper is called with too few or too many arguments. Missing arguments are undefined, which makes the helper return false or an unexpected value for every host.
- PAC-E018console.log or other browser APIs used in the PAC high
The PAC sandbox is not a web page. console, window, document, fetch, setTimeout and similar objects are not defined; using them throws a ReferenceError and aborts the evaluation.
- PAC-E019Unknown Zscaler PAC variable high
A Zscaler-hosted PAC file may contain variables such as ${GATEWAY} that the PAC server replaces before the browser sees the file. A misspelt or undocumented variable stays literal, the proxy entry becomes invalid and browsers skip it, which can mean a silent direct connection.
Security and fail-safety
- PAC-X001No unconditional return at the end of FindProxyForURL critical
Some code path reaches the end of FindProxyForURL without a return. The function then returns undefined, and Chromium, Firefox and WinHTTP all connect directly. Traffic silently bypasses the proxy.
- PAC-X002Trailing * in a host pattern matches attacker-controlled suffixes high
shExpMatch(host, "example.com*") matches example.com.evil.test and example.comms.test. Anyone who controls a domain can append the trusted name as a subdomain label and inherit the rule.
- PAC-X003dnsDomainIs pattern without a leading dot matches look-alike domains high
dnsDomainIs is a plain string-suffix test. "corp.example" matches www.corp.example, corp.example and also notcorp.example. Only ".corp.example" restricts the match to subdomains.
- PAC-X004Leading * without a dot matches look-alike domains high
shExpMatch(host, "*corp.example") matches badcorp.example as well as www.corp.example. The safe form is "*.corp.example", where the dot forces a label boundary.
- PAC-X005Pattern matches an entire top-level domain high
shExpMatch(host, "*.de") or dnsDomainIs(host, ".com") routes a whole country or generic TLD. In the usual "proxy everything, except..." PAC this bypasses inspection for millions of unrelated sites.
- PAC-X006IP range matched as a text prefix high
shExpMatch(host, "127.*") or "10.*" compares characters, not addresses. It matches the hostname 127.foo.evil.test and, for "172.*", every public 172.x address. Use isInNet with a mask.
- PAC-X007String prefix used to match an IP range high
host.substring(0, 8) == "10.1.2.9" matches 10.1.2.99 and the hostname 10.1.2.9.evil.test. Prefix arithmetic on strings is not subnet arithmetic.
- PAC-X008Port matched as a substring of the URL high
shExpMatch(url, "*:1080*") was meant to match port 1080 but also matches http://www.example.com/page?id=1080. A bypass is one query parameter away.
- PAC-X009Pattern with leading and trailing * is a substring match high
shExpMatch(host, "*example.com*") or shExpMatch(url, "*abc*") is true for any string that contains the characters anywhere. Over url this includes query strings an attacker controls; over host it includes look-alike domains.
- PAC-X010dnsResolve result compared with a literal address medium
if (dnsResolve(host) == "10.1.2.3") depends on the resolver answering, answering fast and answering with exactly that record. Any failure makes the condition false and the request takes the default route unnoticed.
- PAC-X011Exceptions are caught and turned into DIRECT high
A try/catch whose catch returns "DIRECT" converts every runtime problem (DNS failure, typo, engine quirk) into a proxy bypass. Errors should fail closed, towards the proxy.
- PAC-X012Wildcard inside a domain label medium
shExpMatch(host, "corp*.example") matches corp.example and corp-cdn.example but also corpevil.example. A wildcard inside a label spans arbitrary registrable names.
- PAC-X013PROXY entry without a port medium
"PROXY proxy.corp.example" names no port. Chromium assumes 80; other engines are reported to reject the entry, and a rejected sole entry means a direct connection. Always write host:port.
- PAC-X014DIRECT as fallback after a proxy info
"PROXY proxy.corp.example:8080; DIRECT" keeps users online when the proxy is down, at the price of uninspected traffic during the outage. Make sure that trade-off is intended.
- PAC-X015eval or Function constructor in the PAC medium
eval() or new Function() builds code from strings at runtime. There is no legitimate need for it in a PAC; it hides logic from review and from static analysis.
Correctness
- PAC-C001Hostname pattern applied to url instead of host high
url is the complete request URL (scheme, host, port, path, query); host is the bare hostname. A hostname pattern tested against url either never matches or matches far too much. The most common PAC mistake.
- PAC-C002URL scheme in a host pattern medium
host never contains "://". A pattern such as "http://intranet.corp.example" can never match it; the branch is dead code and the request takes the default route.
- PAC-C003Path, port or query characters in a host pattern medium
host contains no slash, colon, question mark, space or port. A host pattern with any of these can never match.
- PAC-C004Wildcard characters in dnsDomainIs or localHostOrDomainIs medium
dnsDomainIs and localHostOrDomainIs compare strings literally. "*.corp.example" is looked for as the literal characters *.corp.example, which no hostname contains.
- PAC-C005isPlainHostName called with url medium
isPlainHostName returns true for names without a dot. A URL always contains dots (and slashes), so isPlainHostName(url) is permanently false and the branch never fires.
- PAC-C006Bitwise | or & used instead of || or && medium
A single | or & converts both operands to 32-bit integers. For booleans the result is accidentally right, but both sides are always evaluated and non-boolean operands produce 0, which silently drops the branch.
- PAC-C007RFC 1918 range with the wrong mask medium
172.16.0.0 with mask 255.255.0.0 covers only 172.16.x.x. The private block is 172.16.0.0/12 (mask 255.240.0.0), i.e. 172.16.0.0 to 172.31.255.255; the /16 misses fifteen sixteenths of it.
- PAC-C008Only 127.0.0.1 excluded instead of 127.0.0.0/8 low
The entire 127.0.0.0/8 block is loopback. A PAC that only exempts 127.0.0.1 sends 127.0.0.2 or 127.1.2.3 (used by local development tools and some agents) to the proxy, where the connection fails.
- PAC-C009Domain matched exactly, subdomains not covered low
host == "example.com" matches exactly that name. www.example.com, api.example.com and every other subdomain fall through. If the whole site was meant, add the subdomain pattern.
- PAC-C010dnsDomainLevels used without excluding IP literals low
dnsDomainLevels counts dots. 192.0.2.10 has three, like a.b.c.example. A "this is a fully qualified name" test based on it also matches every IPv4 address.
- PAC-C011Very short or trailing-dot dnsDomainIs pattern medium
Patterns like ".uk." (trailing dot) or ".ab" are suspicious. A trailing dot never matches the host as engines pass it; a two-letter suffix without a label is a TLD or a typo.
- PAC-C012IPv4 loopback/private ranges handled, IPv6 equivalents not info
The PAC routes 127.0.0.0/8 or RFC 1918 literals DIRECT but has no equivalent for ::1, link-local or unique-local IPv6 addresses. IPv6 literal hosts take the default route, usually the proxy.
- PAC-C013Identical condition appears twice low
The same condition (or the same pattern literal in the same function) occurs more than once. If the first occurrence returns, the second is dead; if it does not, one of them is probably a copy-paste error.
- PAC-C014Branch can never match because an earlier branch covers it medium
A later, more specific rule (www.corp.example -> proxy) is unreachable because an earlier, broader rule (*.corp.example -> DIRECT) returns for the same hosts. First match wins.
- PAC-C015URL pattern pinned to http:// only info
A url pattern starting with "http://" does not match "https://". Most sites are https today; unless different routing for plain http is intended, the rule is probably outdated.
- PAC-C016isInNet arguments in the wrong order high
isInNet(host, network, mask) is the order. isInNet("10.0.0.0", host, "255.0.0.0") compares the network literal against a mask derived from the hostname and is false for every request.
- PAC-C017Condition is a constant medium
if (true), if (0) or if ("DIRECT") does not depend on the request. Everything after an always-true branch that returns is dead; an always-false branch is dead itself.
- PAC-C018Upper-case letters in a hostname pattern medium
Browsers hand the PAC a lower-cased hostname. A pattern such as "Intranet.Corp.Example" never matches in Chromium or Firefox. Where engines do not lower-case (WinHTTP), the comparison is case-sensitive anyway, so patterns must be lower case and host should be lower-cased explicitly.
- PAC-C019Zscaler gateway on an undocumented port medium
Zscaler Public Service Edges accept browser traffic on ports 80, 443, 9400, 9443 and 9480, plus dedicated ports your organisation subscribes to. Any other port in a ${GATEWAY} entry means the browser tries, fails and falls through to the next entry.
- PAC-C020Forwarding Profile and App Profile roles mixed in one Zscaler Client Connector PAC high
Zscaler Client Connector uses two PAC files with different jobs. The Forwarding Profile PAC steers traffic to the client (${ZAPP_LOCAL_PROXY}) or around it; the App Profile PAC sends what the client received to the cloud (${GATEWAY}). A file that does both is used in the wrong slot somewhere.
- PAC-C021Netskope explicit proxy on the wrong port high
Netskope's Cloud Explicit Proxy requires the browser to use port 8081; Explicit Proxy over Tunnel uses 163.116.128.80/81 or epot.goskope.com on port 80 (recommended) or 8080. Other ports are dropped, so the browser waits, fails and falls through.
- PAC-C022Identity provider not excluded from the SSE proxy medium
Explicit proxies that authenticate through SAML redirect the browser to the identity provider. If the IdP itself is reached through the proxy, the login loops. Netskope, Cloudflare and Palo Alto Networks all require the IdP to be bypassed in the PAC.
- PAC-C023Non-HTTP URLs reach a proxy that only accepts HTTP and HTTPS low
Cloud explicit proxies accept HTTP and HTTPS. Zscaler, Netskope and Palo Alto Networks all start their sample PAC files with a DIRECT for ftp: URLs; without such a guard, ftp: and other scheme requests from older clients are sent to a proxy that drops them.
- PAC-C024Skyhigh cloud proxy on a port browsers cannot use high
The Skyhigh Secure Web Gateway cloud proxy c<customer-id>.wgcs.skyhigh.cloud is configured with port 80 or 8080. Port 8081 is the Client Proxy Secure Channel port and is not a browser proxy port; any other port is a dead entry.
- PAC-C025Secure Web Gateway itself is not returned DIRECT medium
An on-premises Skyhigh Secure Web Gateway talks to the browser directly for block pages, coaching pages, authentication redirects (port 9094) and injected files (port 9999). If the PAC proxies requests to the gateway's own host name or address, those pages break.
- PAC-C026Plain hostnames or private addresses are sent to the cloud proxy medium
Every vendor template returns DIRECT for plain hostnames and RFC 1918 addresses before the proxy statement. A cloud proxy cannot reach an intranet host; the request is denied or times out, and Skyhigh Client Proxy in explicit mode has no other place for these exceptions than the PAC.
- PAC-C027Cloudflare Gateway proxy endpoint with PROXY instead of HTTPS high
Cloudflare Gateway proxy endpoints only accept TLS from the browser. The PAC must return "HTTPS <subdomain>.proxy.cloudflare-gateway.com:443"; "PROXY ..." to the same host fails for every request and the browser falls through to the next entry or to an error.
- PAC-C028Cloudflare Gateway proxy endpoint on a port other than 443 high
Every Cloudflare example returns the proxy endpoint with port 443. Another port is not a TLS listener of the endpoint; the browser's connection fails and the request falls through.
- PAC-C029Prisma Access Explicit Proxy on a port other than 8080 high
The Explicit Proxy URL is <name>.proxy.prismaaccess.com on port 8080; the PAC file guidelines and every Palo Alto Networks example return "PROXY <name>.proxy.prismaaccess.com:8080". Another port is a dead entry.
- PAC-C030Prisma Access service domains not returned DIRECT info
Palo Alto Networks requires the PAC to bypass the Authentication Cache Service (*.acs.prismaaccess.com) and, when GlobalProtect is used alongside Explicit Proxy, *.prismaaccess.com and *.gpcloudservice.com. Sending that traffic to the proxy breaks authentication or the agent connection.
Compatibility across engines
- PAC-K001ES2015+ syntax (let, const, arrow functions, template literals) high
Modern browsers accept ES2015 syntax, but PAC files also run in WinHTTP-based clients (Windows services, Edge IE mode, many agents) whose engine is ES5-level or older. There the entire file is a syntax error and all traffic goes DIRECT.
- PAC-K002alert() used in the PAC medium
alert() is the PAC specification's debugging hook. Chromium records it internally, Firefox writes to its console, and engines without alert throw, which aborts the evaluation. It has no place in a deployed file.
- PAC-K003IPv6 extension functions (isInNetEx, dnsResolveEx, myIpAddressEx) medium
The Ex functions come from Microsoft's IPv6 extensions to the PAC format. Chromium and WinHTTP implement them; Firefox does not. On an engine without them the call throws and the request is treated as a script failure.
- PAC-K004FindProxyForURLEx entry point medium
FindProxyForURLEx is the PAC v2 entry point from Microsoft's IPv6 extensions. Chromium and Firefox ignore it and call FindProxyForURL. A file that relies on the Ex function alone has no effect in browsers.
- PAC-K005SOCKS keywords mean different versions on different engines low
The PAC format only defines "SOCKS host:port" and does not say which protocol version. Chromium treats plain SOCKS as SOCKS4 and also accepts SOCKS4 and SOCKS5; Firefox is reported to treat SOCKS as SOCKS5; WinHTTP support for the versioned keywords is unverified.
- PAC-K006HTTPS proxy keyword without a PROXY fallback medium
"HTTPS host:port" tells the browser to open TLS to the proxy itself. Chromium and Firefox support it; WinHTTP-based clients do not, and most on-premises proxies have no TLS listener. Without a PROXY fallback those clients end up without a proxy.
- PAC-K007Time-based routing (weekdayRange, dateRange, timeRange) low
Time-based routing depends on the client's clock and timezone and on helper implementations that differ between engines in argument handling. Behaviour is hard to test and hard to reproduce in support cases.
- PAC-K008Mutable state outside FindProxyForURL low
A top-level variable that FindProxyForURL writes to (a cache, a counter, a "first call" flag) assumes one persistent JavaScript context. Engines may run several contexts or recreate them, so the state is unreliable and behaviour becomes non-reproducible.
- PAC-K009Case-sensitive host comparisons without lower-casing host low
Chromium and Firefox lower-case the hostname before calling the PAC; WinHTTP is reported to pass it as typed. Lower-casing host once at the top of the function makes the file behave the same everywhere.
- PAC-K010ES2015+ library methods (includes, startsWith, Map, Set) medium
Unlike ES2015 syntax, newer library methods parse fine on an old engine and fail only at runtime with a TypeError for the request that reaches them. The branch then behaves like a script failure.
- PAC-K011Routing depends on Math.random or the clock low
Using Math.random() or the current time to pick a proxy produces different answers for identical requests. Load balancing done this way fragments connection pools and makes support cases impossible to reproduce.
- PAC-K012Engines return different results for the same URL info
The same test URL produced different proxy decisions under different engine profiles (for example because Chromium strips the path from https URLs, or because an engine lacks a helper). The PAC's behaviour depends on which client runs it.
- PAC-K013Routing decided by myIpAddress() medium
Each engine chooses "the client's IP" by its own heuristic. With VPNs, Wi-Fi plus Ethernet, virtualisation adapters or IPv6, the answer differs between browsers and changes without a PAC re-fetch. Location-based routing keyed on it is unreliable.
- PAC-K014isInNet called with an IPv6 address medium
isInNet is defined for dotted-quad IPv4 addresses and masks. An IPv6 network or mask makes the test false on every engine; the IPv6-capable isInNetEx exists only in some engines.
- PAC-K015Regular-expression syntax inside a shExpMatch pattern low
shExpMatch(host, "(www|mail).corp.example") works in Chromium, Firefox and pacparser only because their helper converts the glob into a JavaScript RegExp without escaping ( ) and |. That is an implementation accident, not the PAC contract; other engines may treat the characters literally.
- PAC-K016Zscaler gateway named directly instead of through ${GATEWAY} low
Zscaler discourages hard-coded gateway addresses because they can change. The ${GATEWAY} variables are resolved per request by the PAC server to the closest healthy Public Service Edge; a literal gateway.<cloud>.net host or IP is static and skips that logic.
- PAC-K017ES2015+ syntax and the Zscaler Client Connector legacy PAC parser medium
Zscaler Client Connector evaluates the App Profile and Forwarding Profile PAC files itself. Older versions offered a legacy parser next to the V8-based one; whether the legacy parser accepts ES2015 syntax is undocumented, so a modern-syntax PAC may fail on an old client while browsers run it.
- PAC-K018Legacy Skyhigh / McAfee cloud proxy domain low
The cloud proxy moved from mcafee-cloud.com (retired at the end of 2022) and saasprotection.com to wgcs.skyhigh.cloud. A PAC that still names the retired domain has no proxy; one that names the previous domain works today but should be migrated.
- PAC-K019IPv6 address in a PROXY statement for Prisma Access high
Palo Alto Networks' PAC file guidelines state that only IPv4 addresses are supported in PROXY statements. An IPv6 literal does not reach the Explicit Proxy.
Performance
- PAC-P001DNS lookup on every request high
dnsResolve, isResolvable and isInNet on a hostname each cost a DNS round trip inside proxy resolution, for every connection, before the page starts loading. They also make routing depend on the resolver being reachable and honest. Most PACs can decide on the hostname string alone.
- PAC-P002isInNet called with a hostname medium
isInNet resolves its first argument when it is not already an IP address. Passing host means a DNS lookup for every non-IP request, with all the latency and fail-open behaviour of dnsResolve, and only one of possibly several addresses is checked.
- PAC-P003isResolvable used as a reachability test medium
isResolvable asks the resolver for an A record. It is a DNS round trip per request and answers a different question than "can I reach this host directly"; resolvable hosts can be unreachable and unreachable hosts can be cached as resolvable.
- PAC-P004dnsResolve called repeatedly for the same host low
Each dnsResolve(host) call crosses into the engine's resolver, even when the answer is cached. Two or more calls for the same argument in one evaluation double the cost for nothing. Resolve once, reuse the variable.
- PAC-P005DNS-dependent rule placed before string rules low
Rules execute in source order and the first match returns. When a DNS-dependent rule precedes string rules, every request pays the lookup even if a later cheap rule would have decided it.
- PAC-P006PAC file is large low
Above roughly 50 KB a PAC starts to cost measurable time per connection; above 1 MB engines refuse to load it. Long host lists usually belong on the proxy, not in the file every client downloads and executes.
- PAC-P007Many conditions and branches info
A PAC with dozens of conditions is slow to read, slow to review and slow to run for default-route traffic. Complexity metrics are reported so the trend can be watched; grouping rules by domain and helper functions keep the file maintainable.
- PAC-P008Deeply nested conditions info
if blocks nested more than two or three levels deep hide which rule decides. A flat sequence of if (...) return ...; statements gives first-match-wins semantics without nesting.
Best practice and maintainability
- PAC-B001if statement without braces low
if (cond) return "DIRECT"; works, but the next person who adds a line under it changes the logic without noticing. PAC files are edited in production by people who are not JavaScript developers; braces are cheap insurance.
- PAC-B002Parameters not named url and host info
Engines pass the arguments by position, so function FindProxyForURL(u, h) works. Every document, example and reviewer assumes url and host; deviating names invite mix-ups between the two (PAC-C001).
- PAC-B003Unused functions or variables low
Helper functions and variables that nothing references are leftovers from earlier versions. They are harmless to the engine but mislead reviewers and hide the rules that actually run.
- PAC-B004Default route has no comment info
The final unconditional return is the most consequential line in the file; it decides every request no exception matched. A one-line comment stating the intended default ("everything else via the corporate proxy") documents the design and is one of the conditions for an A+ grade.
- PAC-B005Same proxy string repeated many times info
Writing "PROXY proxy.corp.example:8080" in a dozen places means a dozen edits when the proxy moves and a dozen chances for a typo in one of them.
- PAC-B006isPlainHostName alone sends every single-label name direct info
isPlainHostName(host) is true for any name without a dot, including a mistyped public name. The user then gets a local resolver error instead of the proxy's block page. Fine as a performance short-cut, weak as the only definition of "internal".
- PAC-B007*.local routed direct info
Routing *.local direct is right where .local is reserved for mDNS/Bonjour. Older enterprises still use .local as their internal DNS suffix; there the rule is also right, but it must be a deliberate decision rather than a template copy.
- PAC-B008Long list of public hostnames routed around the proxy low
A PAC that lists dozens of public SaaS, identity-provider or update hostnames as DIRECT has turned into a security policy document with weak change control. Routing belongs in the PAC; bypass and inspection decisions belong on the proxy.
- PAC-B009Result assembled in a variable instead of returned per rule low
var proxy = ...; if (a) proxy = ...; if (b) proxy = ...; return proxy; inverts the usual semantics (last match wins instead of first) and evaluates every condition, including DNS lookups, for every request.
- PAC-B010Empty block low
An if, else or catch with an empty body is usually an unfinished edit. The condition is still evaluated (possibly a DNS lookup) and the reader has to guess what was intended.
- PAC-B011Single proxy without a fallback entry info
A return with a single PROXY entry gives the browser no alternative when that proxy is down. A second proxy in the list keeps inspection in place during an outage; what should happen when all proxies are down is a design decision (see PAC-X014).
- PAC-B012Single Zscaler gateway without ${SECONDARY_GATEWAY} medium
Zscaler's variables come in pairs for a reason: ${GATEWAY} is the closest Public Service Edge and ${SECONDARY_GATEWAY} the next one. A return with only the primary leaves the browser without a proxy during a data-centre outage or maintenance.
- PAC-B013PAC sends traffic to a proxy the Netskope Client may not know low
When the Netskope Client runs on a device whose PAC file also points at other proxies, Netskope requires those proxies to be declared in the Client configuration ("Interoperate with Proxy"). The Client then intercepts CONNECT requests to them and steers managed destinations through its tunnel.
- PAC-B014DIRECT in the PAC does not bypass the vendor's agent info
With an SSE agent installed, the PAC is evaluated first by the application or OS, and the agent intercepts afterwards at the TCP or driver level. A DIRECT in the PAC therefore means "not through this proxy", not "not through the agent"; the destination also needs the agent's own bypass.
- PAC-B015Selected vendor pack, but the PAC never routes to that vendor info
The vendor packs recognise the vendor's gateway by its host name pattern or PAC variable. If the file never returns such an entry, either the wrong pack is selected or the file is an agent-only steering PAC; the pack's gateway checks then have nothing to check.
Delivery (HTTP headers)
- PAC-D001PAC served over plain HTTP high
The PAC is executable routing policy fetched on every browser start. Over plain HTTP a network attacker (hostile Wi-Fi, compromised router, ISP middlebox) can rewrite it and route all web traffic through a proxy of their choice.
- PAC-D002Content-Type is not application/x-ns-proxy-autoconfig medium
Browsers mostly ignore the Content-Type of a PAC, but not all consumers do, and application/octet-stream or text/html responses (typically an error page or a download prompt) are a sign the server is not configured for the file.
- PAC-D003No Cache-Control header on the PAC response medium
Without Cache-Control the browser decides when to re-fetch the PAC (hours or the whole session). A proxy change then takes effect at unpredictable times per client. An explicit max-age makes the roll-out window known and controllable.
- PAC-D004PAC marked no-cache / no-store / max-age=0 low
Forbidding caching does not make changes faster in a useful way; it makes every client re-fetch the file on the engine's minimum interval, which adds load on the PAC server and makes the PAC server a per-request dependency on some clients.
- PAC-D005PAC cached for more than a day low
A max-age of several days means a wrong or outdated PAC stays active on clients for that long. Keep the window at or below one day so that fixes propagate within a working day.
- PAC-D006Large PAC served without compression low
A PAC of tens of kilobytes is fetched by every client on every refresh. Text compresses well; serving it gzip-encoded cuts the transfer without touching the file.
- PAC-D007PAC distributed via WPAD DNS discovery medium
WPAD clients look for wpad.<suffix>, strip one label and retry up to wpad.<tld>. If no internal record answers, a registered public wpad name can supply the PAC. Own the name, block the walk at the resolver, prefer explicit configuration.
- PAC-D008PAC URL distributed via DHCP option 252 medium
DHCP answers are plaintext broadcasts with no authentication in practice. A rogue DHCP responder on the same segment can hand out its own PAC URL and take over routing for every client that trusts option 252. Switch-level DHCP snooping or explicit configuration closes the gap.
- PAC-D009PAC loaded from a file path or SMB share medium
A PAC on a file share is executable policy stored where many people can write. Access control on the share, not on the proxy team, now decides who controls routing. Chromium also no longer accepts file-scheme PAC URLs.
- PAC-D010PAC URL does not return 200 high
A 3xx, 4xx or 5xx on the PAC URL means clients have no script to run. Browsers then connect directly (or, with a mandatory-PAC policy, fail every request). Redirects add a hop that some consumers do not follow.