Plain 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.
Why it matters
Zscaler explains the failure mode: a request for an internal host that is proxied “first goes outside
your network to the Zscaler proxy but is then blocked as it tries to access an internal host”. Its
samples, Netskope’s template (isPlainHostName(host) first), Cloudflare’s templates (“No proxy for
private (RFC 1918) IP addresses”, “Bypass plain hostnames”) and Palo Alto Networks’ sample (“Bypass
localhost and Private IPs”) all put these two exclusions at the top. Skyhigh adds that in Client
Proxy explicit mode “bypass rules are not applicable; instead, they must be configured via the PAC
file”. The rule reports two things separately: no DIRECT branch for plain hostnames
(isPlainHostName(host) or dnsDomainLevels(host) == 0), and no DIRECT branch for the private
ranges (isInNet with 10/8, 172.16/12 or 192.168/16, or a regular expression or glob on the host
string). Keep the IP-literal guard from PAC-P001 in front of isInNet, as the examples show.
Zscaler’s App Profile PAC for Tunnel with Local Proxy mode is the one documented exception: there
the Forwarding Profile PAC carries the bypasses and the App Profile PAC is a single return.
How to fix
Start the function with if (isPlainHostName(host)) return "DIRECT"; if (/^\d+\.\d+\.\d+\.\d+$/.test(host) && (isInNet(host, "10.0.0.0", "255.0.0.0") || isInNet(host, "172.16.0.0", "255.240.0.0") || isInNet(host, "192.168.0.0", "255.255.0.0"))) return "DIRECT";
Examples
Bad
function FindProxyForURL(url, host) {
if (dnsDomainIs(host, ".corp.example")) {
return "DIRECT";
}
return "PROXY ${GATEWAY}:80; PROXY ${SECONDARY_GATEWAY}:80";
}
Open bad example in checkerGood
function FindProxyForURL(url, host) {
if (isPlainHostName(host) || dnsDomainIs(host, ".corp.example")) {
return "DIRECT";
}
if (/^\d+\.\d+\.\d+\.\d+$/.test(host) &&
(isInNet(host, "10.0.0.0", "255.0.0.0") ||
isInNet(host, "172.16.0.0", "255.240.0.0") ||
isInNet(host, "192.168.0.0", "255.255.0.0"))) {
return "DIRECT";
}
return "PROXY ${GATEWAY}:80; PROXY ${SECONDARY_GATEWAY}:80";
}
Open good example in checkerRelated rules
- DNS lookup on every request PAC-P001
- isPlainHostName alone sends every single-label name direct PAC-B006
- RFC 1918 range with the wrong mask PAC-C007
- Non-HTTP URLs reach a proxy that only accepts HTTP and HTTPS PAC-C023
References
- https://help.zscaler.com/zia/writing-pac-file
- https://docs.netskope.com/en/explicit-proxy
- https://developers.cloudflare.com/cloudflare-one/networks/resolvers-and-proxies/proxy-endpoints/best-practices/
- https://docs.paloaltonetworks.com/prisma-access/administration/prisma-access-mobile-users/mobile-users-explicit-proxy/pac-file-guidelines
- https://success.skyhighsecurity.com/Skyhigh_Client_Proxy/Manage_Client_Proxy_through_Skyhigh_SSE/Alternate_Proxy_Support_for_Explicit_Proxy_Mode