2.8 KiB
forwarded — X-Forwarded-Host trust enforcement
(no submodules)
Purpose
Implements CIDR-based trust enforcement for the optional
X-Forwarded-Host header. Edge proxies (CDN, load balancer, reverse
proxy) rewrite the inbound Host to their internal hostname and stash
the public host on X-Forwarded-Host. The resolver may honour that
header, but ONLY when (a) the operator opted in via
trustForwardedHost: true and (b) the upstream IP belongs to a
trusted-proxy CIDR — otherwise a malicious client could spoof its
tenant by sending X-Forwarded-Host: victim.api.example.com directly
to the origin.
This module owns the parser (parseCidr, parseIp) and the matcher
(cidrMatches, isTrustedProxy) used by the resolver hot-path.
Features
- IPv4 and IPv6 support (including
::shorthand and zone-id stripping). - Byte-array comparison with bit-aware prefix masking — no string manipulation in the hot-path.
- Family-disjoint match — an IPv6 CIDR never matches an IPv4 address.
parseCidrreturnsnullon malformed input so the caller (options parser) surfaces a stableINVALID_OPTIONSerror rather than silently widening the trust set.
Usage: Basic
import {
isTrustedProxy,
parseCidr,
} from "@suaveplan/api-tenant-resolver-subdomain/forwarded";
const cidrs = ["10.0.0.0/8", "2001:db8::/32"]
.map((c) => parseCidr(c)!)
.filter((c) => c !== null);
isTrustedProxy("10.4.7.2", cidrs); // → true
isTrustedProxy("8.8.8.8", cidrs); // → false
Usage: Advanced
import {
cidrMatches,
parseCidr,
parseIp,
} from "@suaveplan/api-tenant-resolver-subdomain/forwarded";
const cidr = parseCidr("172.16.0.0/12");
const ip = parseIp("172.16.5.99");
if (cidr !== null && ip !== null && cidrMatches(ip, cidr)) {
// ...trusted...
}
API Reference
ParsedCidr—{ bytes: Uint8Array; prefix: number; family: 4 | 6 }.parseCidr(input: string): ParsedCidr | null.parseIp(addr: string): { bytes: Uint8Array; family: 4 | 6 } | null.cidrMatches(ip, cidr): boolean.isTrustedProxy(remoteAddress, cidrs): boolean.
Implementation Notes
IPv6 parsing handles the canonical :: double-colon expansion, zone-id
suffixes (%eth0 is stripped — never meaningful for trust checks),
and rejects > 8 groups or non-hex content. Prefix matching extracts
the full-byte run with a byte-equality loop, then applies a
high-bit mask to the trailing partial byte; prefix === 0 is the
"match everything in this family" case and is handled implicitly
(loop runs zero times, tail short-circuit returns true).
The module is dependency-free — no node:net, no ipaddr.js. This
keeps the package universal and the CIDR parsing observable in
tests without runtime injection.