Files
suaveplan/packages/api-stack/api/api-tenant-resolver-subdomain/src/forwarded/forwarded.md
2026-05-27 23:29:53 +01:00

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.
  • parseCidr returns null on malformed input so the caller (options parser) surfaces a stable INVALID_OPTIONS error 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.