no-proxy-match
v0.1.0
Published
Compile and match NO_PROXY rules with explicit, tested semantics.
Maintainers
Readme
no-proxy-match
Compile and match NO_PROXY rules with explicit, tested semantics.
no-proxy-match handles domain boundaries, ports, IPv4 and IPv6 literals, CIDR ranges, bracketed IPv6, IPv4-mapped IPv6, IDNA, trailing root dots, diagnostics, and the behavioral differences between popular Node.js implementations.
npm install no-proxy-matchimport { compileNoProxy } from "no-proxy-match";
const policy = compileNoProxy(
"localhost,127.0.0.0/8,10.0.0.0/8,.internal.example",
);
policy.bypasses("http://10.4.5.6:8080"); // true
policy.bypasses("https://public.example"); // false
policy.explain("https://api.internal.example");
// { bypasses: true, reason: "matched", target: ..., rule: ... }The package has zero runtime dependencies and works in Node.js, Bun, Deno, browsers, workers, and edge runtimes. It does not open sockets, resolve DNS, or configure a proxy agent.
Why
NO_PROXY is widely deployed but has no binding standard. Implementations disagree about fundamental behavior:
- whether
example.comalso matchesapi.example.com; - whether
.example.comincludes the root domain; - whether IPv4 and IPv6 CIDR ranges work;
- how brackets, trailing dots, mapped addresses, wildcards, and ports are normalized;
- what malformed entries do.
Those disagreements surface as Node http.request() versus fetch() differences, Axios proxy-bypass vulnerabilities, Bun failures with private CIDR ranges, and separate matcher implementations in proxy libraries.
no-proxy-match makes the chosen semantics explicit and testable.
Quick API
One-shot matching
import { shouldBypassProxy } from "no-proxy-match";
shouldBypassProxy("https://api.example.com", "example.com"); // true
shouldBypassProxy("https://notexample.com", "example.com"); // falseWhen the policy argument is omitted, lowercase no_proxy takes precedence over uppercase NO_PROXY.
Compile once
Compile once when matching more than one request:
const policy = compileNoProxy(process.env.NO_PROXY);
for (const request of requests) {
const useProxy = !policy.bypasses(request.url);
}Targets can be absolute URL strings, URL objects, or already-parsed hosts:
policy.bypasses({
hostname: "2001:db8::7",
port: 443,
protocol: "https",
});The parsed-host form avoids reparsing a URL in HTTP clients and proxy agents.
Explain a decision
const result = policy.explain("http://10.20.30.40:3000");
if (result.bypasses) {
console.log("Direct because of", result.rule?.source);
}match() returns the matching rule and normalized target, or null.
Profiles
Choose behavior deliberately:
compileNoProxy("example.com", { profile: "canonical" });
compileNoProxy("example.com", { profile: "proxy-from-env" });
compileNoProxy("example.com", { profile: "undici" });| Behavior | canonical | proxy-from-env | undici |
| ------------------------------------- | ----------- | ---------------- | -------- |
| Bare example.com matches subdomains | Yes | No | Yes |
| .example.com includes the root | Yes | No | Yes |
| IPv4 and IPv6 CIDR | Yes | No | No |
| Leading *example string suffix | No | Yes | No |
| Trailing root dot normalized | Yes | No | Yes |
| Explicit port 0 stays literal | Yes | No | No |
canonical is the default. Profile names describe the important matching contracts, not proxy selection, environment precedence, authentication, or transport behavior.
CIDR and IP normalization
Canonical mode supports full IPv4 and IPv6 prefix ranges:
const policy = compileNoProxy("10.0.0.0/8,172.16.0.0/12,2001:db8::/32");Supported boundaries include /0, /32, and /128. Non-network bits in a policy are masked, so 10.123.45.67/8 is reported as 10.0.0.0/8.
IPv4-mapped IPv6 targets match equivalent IPv4 rules:
shouldBypassProxy("http://[::ffff:127.4.5.6]", "127.0.0.0/8"); // trueCIDR rules match IP literals only. The package intentionally does not resolve localhost before testing it against 127.0.0.0/8; implicit DNS would make the decision asynchronous and introduce DNS-rebinding ambiguity.
Diagnostics and fail-closed behavior
Malformed and unsupported rules never bypass the proxy silently:
const policy = compileNoProxy("10.0.0.0/99,*bad,good.example", {
onDiagnostic(diagnostic) {
console.warn(diagnostic.code, diagnostic.entry);
},
});
policy.diagnostics;
// INVALID_CIDR for 10.0.0.0/99
// UNSUPPORTED_WILDCARD for *badValid entries continue to work. Diagnostics are also retained on the compiled policy.
Conformance corpus
The published package includes a versioned, runtime-neutral fixture corpus:
import corpus from "no-proxy-match/corpus.json" with { type: "json" };Projects that cannot accept a runtime dependency can run the same cases against a vendored implementation. Corpus changes are versioned as public behavior.
Migrating
From should-proxy
- const shouldProxy = require("should-proxy");
- const useProxy = shouldProxy(url, { no_proxy: policy });
+ const { shouldBypassProxy } = require("no-proxy-match");
+ const useProxy = !shouldBypassProxy(url, policy);Unlike the historical should-proxy network matcher, CIDR prefix lengths are evaluated exactly and IPv6 is supported.
Alongside proxy-from-env
Use the compatibility profile when preserving its hostname semantics:
const bypass = compileNoProxy(process.env.NO_PROXY, {
profile: "proxy-from-env",
}).bypasses(url);Use canonical mode when opting into CIDR and stricter diagnostics.
With Undici
Use profile: "undici" to test current Undici hostname behavior, or canonical mode for a policy layer that includes CIDR. This package does not create or wrap an EnvHttpProxyAgent.
Important security boundary
NO_PROXY controls whether traffic goes directly to a target or through a configured proxy. Either direction can expose traffic when policy and application assumptions differ.
- Invalid rules fail closed: they do not bypass.
- Matching uses normalized URL hostnames and DNS-label boundaries.
- CIDR never performs DNS resolution.
- No network request, environment mutation, or global agent patching occurs.
- A
NO_PROXYlist is not a complete SSRF defense. Validate untrusted destinations separately and enforce policy at the proxy or network boundary too.
See SECURITY.md for the threat model and reporting instructions.
Runtime and package support
- Node.js 18+
- Bun and Deno
- Modern browsers, workers, and edge runtimes
- ESM and CommonJS
- TypeScript declarations
- Zero runtime dependencies
Scope
This package intentionally does not:
- choose
HTTP_PROXY,HTTPS_PROXY, orALL_PROXYvalues; - create proxy agents or alter global fetch behavior;
- resolve PAC files or operating-system proxy settings;
- resolve DNS names for CIDR matching;
- claim that one
NO_PROXYinterpretation is universally standardized.
Those belong in transport libraries built on top of a matcher.
License
MIT © Atomics Hub
