PAC-X006 · rfc1918-string-pattern

IP range matched as a text prefix

high · Security and fail-safety · Top 20 #9

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.

Why it matters

host is a string. shExpMatch(host, "127.*") is true for 127.0.0.1 but also for the hostname 127.foo.evil.test (lab, 2026-05-20) and any name starting with 127.. "172.*" covers 172.16.0.0/12 and the public ranges 172.0.0.0-172.15.255.255 and 172.32.0.0-172.255.255.255. "192.168.*" matches 192.168.evil.test. A DIRECT branch written this way can be reached by anyone who controls a hostname starting with digits.

The correct tool is isInNet(host, "127.0.0.0", "255.0.0.0"), ideally guarded by a check that host is a dotted-quad literal so that no DNS lookup is triggered (PAC-P002).

How to fix

Guard with an IP-literal test and use isInNet(host, "10.0.0.0", "255.0.0.0") etc.; keep "localhost" as a string comparison.

Examples

Bad

function FindProxyForURL(url, host) {
  if (shExpMatch(host, "172.*") || shExpMatch(host, "127.*")) {
    return "DIRECT";
  }
  return "PROXY proxy.corp.example:8080";
}
Open bad example in checker

Good

function FindProxyForURL(url, host) {
  if (host == "localhost") {
    return "DIRECT";
  }
  if (/^\d+\.\d+\.\d+\.\d+$/.test(host) &&
      (isInNet(host, "172.16.0.0", "255.240.0.0") ||
       isInNet(host, "127.0.0.0", "255.0.0.0"))) {
    return "DIRECT";
  }
  return "PROXY proxy.corp.example:8080";
}
Open good example in checker

Engine behaviour

EngineBehaviourSource
pacparser"127.*" matches the hostname 127.foo.evil.test; "127example.test" does not (the dot is required).lab, verified 2026-05-20

Source: code = read in the engine's source, doc = vendor documentation, lab = observed in a lab run, expert = practitioner knowledge, not yet verified.

Related rules

References