http/proxy.ts

Trusted proxy configuration and middleware.

Resolves the client IP from X-Forwarded-For only when the TCP connection originates from a configured trusted proxy. Without this middleware, get_client_ip returns 'unknown'.

Declarations
#

10 declarations

view source

create_proxy_middleware
#

http/proxy.ts view source

(options: ProxyOptions): MiddlewareHandler

Create a Hono middleware that resolves the client IP from trusted proxies.

Sets client_ip on the Hono context for downstream use by get_client_ip. All client IPs are normalized (lowercase, IPv4-mapped IPv6 stripped).

Resolution logic: 1. No X-Forwarded-For → use connection IP directly. 2. X-Forwarded-For present but connection is untrusted → ignore header (spoofed by a direct attacker), use connection IP. 3. X-Forwarded-For present and connection is trusted → walk header right-to-left, strip trusted entries, use first untrusted entry.

options

trusted proxy configuration

returns

MiddlewareHandler

throws

  • Error - if any entry in `options.trusted_proxies` is invalid (parsed eagerly via `parse_proxy_entry`)

create_proxy_middleware_spec
#

http/proxy.ts view source

(options: ProxyOptions): MiddlewareSpec

Create a middleware spec for trusted proxy resolution.

Apply before auth middleware so client_ip is available for rate limiting.

options

trusted proxy configuration

returns

MiddlewareSpec

get_client_ip
#

http/proxy.ts view source

(c: Context<any, any, {}>): string

Read the resolved client IP from the Hono context.

Returns 'unknown' if the proxy middleware has not run or no IP is available. Set by create_proxy_middleware.

c

type Context<any, any, {}>

returns

string

is_trusted_ip
#

http/proxy.ts view source

(ip: string, proxies: ParsedProxy[]): boolean

Check whether ip matches any entry in the trusted proxy list.

Normalizes ip before matching (lowercase, IPv4-mapped IPv6 stripped). Uses validate_ip_strict to reject malformed input — without strict validation, Hono's lax distinctRemoteAddr would let an entry like '203.0.113.1:8080' (false-positive 'IPv6') reach convertIPv6ToBinary in the CIDR-match branch and throw.

ip

type string

proxies

type ParsedProxy[]

returns

boolean

normalize_ip
#

http/proxy.ts view source

(ip: string): string

Normalize an IP address for consistent matching and storage.

- Strips ::ffff: prefix from IPv4-mapped IPv6 addresses (e.g. ::ffff:127.0.0.1127.0.0.1) - Lowercases for case-insensitive IPv6 comparison - Idempotent: calling twice produces the same result - Safe on non-IP strings: normalize_ip('unknown') returns 'unknown'

ip

type string

returns

string

parse_proxy_entry
#

http/proxy.ts view source

(entry: string): ParsedProxy

Parse a trusted proxy entry string into a structured form.

Accepts plain IPs ('127.0.0.1', '::1') and CIDR notation ('10.0.0.0/8', 'fe80::/10'). Plain IPs are normalized (lowercase, IPv4-mapped IPv6 stripped) and validated. CIDR prefixes are validated against address family bounds.

entry

IP address or CIDR notation

type string

returns

ParsedProxy

throws

  • Error - on invalid IP, invalid CIDR network, or NaN/negative/over-range prefix

ParsedProxy
#

ProxyOptions
#

http/proxy.ts view source

ProxyOptions

Configuration for trusted proxy resolution.

trusted_proxies

Trusted proxy IPs or CIDR ranges (e.g. '127.0.0.1', '10.0.0.0/8', '::1').

type Array<string>

get_connection_ip

Extract the raw TCP connection IP from the Hono context.

type (c: Context) => string | undefined

log

Optional logger for proxy resolution diagnostics.

type Logger

resolve_client_ip
#

http/proxy.ts view source

(forwarded_for: string, proxies: ParsedProxy[]): string | undefined

Resolve the real client IP from an X-Forwarded-For header value.

Walks right-to-left, skipping trusted proxy entries AND any entry that fails strict IP validation (validate_ip_strict). The first untrusted, strictly-valid entry is the client IP. If every walked entry is trusted or malformed, returns the leftmost strictly-valid (trusted) entry (likely-misconfigured all-trusted case) or undefined (everything was malformed — middleware falls back to the connection IP). All entries are normalized before matching and in the returned value.

Skipping malformed entries is the rate-limit-key fix for the "attacker controls XFF and the proxy passes it through" surface — without the skip, an attacker could rotate arbitrary strings (incl. 'attacker:controlled', which Hono's lax distinctRemoteAddr misclassifies as IPv6) as XFF values to get fresh per-IP rate-limit buckets. Tradeoff: legitimate non-standard proxies that include ports in XFF entries (e.g. 203.0.113.1:8080) also fail strict validation, so those entries get skipped and the rate-limit bucket collapses to the proxy's connection IP (one bucket for everyone behind that proxy). Standard proxies (nginx, cloud LBs) don't include ports.

forwarded_for

the X-Forwarded-For header value

type string

proxies

parsed trusted proxy entries

type ParsedProxy[]

returns

string | undefined

the normalized client IP, or undefined if the header is empty / all entries malformed

validate_ip_strict
#

http/proxy.ts view source

(ip: string): "IPv4" | "IPv6" | undefined

Strict IP validity check.

Defense in depth around Hono's hono/utils/ipaddr helpers, which are lax in two ways:

1. distinctRemoteAddr classifies anything-with-a-colon as 'IPv6', including 'host:port', 'attacker:controlled', '203.0.113.1:8080'. 2. convertIPv6ToBinary silently accepts malformed forms like '[::1]:8080' and '::1\n', parsing them as inconsistent binary values that would still serve as distinct rate-limit keys for an attacker rotating the suffix.

Strict validation here is two-layered: a character-set pre-filter (IP_LITERAL_CHARS), then a round-trip through convertIPv*ToBinary to confirm the input parses cleanly. Either layer alone has holes; together they reject every input form we've seen Hono mis-handle.

Used as the security primitive for any code path that takes an IP string from an untrusted source (XFF, query params) and uses it as a key (rate limiting, audit subject) or compares it against trusted proxies via CIDR (where the latent throw would otherwise bubble out).

ip

type string

returns

"IPv4" | "IPv6" | undefined

the address family on success, undefined if the string is not a strictly-valid IP

Imported by
#