Skip to content

fix(ip): align client IP resolution with the reference engine - #136

Merged
rennf93 merged 1 commit into
masterfrom
fix/client-ip-extraction-parity
Oct 15, 2026
Merged

rennf93 merged 1 commit into
masterfrom
fix/client-ip-extraction-parity

Conversation

@rennf93

@rennf93 rennf93 commented Oct 15, 2026

Copy link
Copy Markdown
Member

Closes #135

This aligns client IP resolution with the reference engine (guard_core/_utils/ip_extraction.py), so an IPv4 client on a dual-stack listener (::ffff:a.b.c.d) resolves to the same identity the lists, bans and rate-limit keys use.

Engine (@guardcore/core)

  • extractClientIp is a full port of the reference: canonical identity (IPv4-mapped IPv6 without a scope id maps to IPv4, ::1.2.3.4 is no longer misread, zones are kept), trusted proxy matching on the connecting address as given, trustedProxyDepth counted from the right with empty entries kept, port and bracket stripping, metacharacter and invalid entry rejection, the right-to-left walk when the depth over-counts proxy hops, the unix token for a request with no peer address, the cached state.client_ip, and the redacted spoof report for a header from an untrusted peer. The reference warn-once log lines are ported.
  • The route_config check stores the identity on request.state.client_ip. ip_security, rate_limit, suspicious_activity, cloud_provider, user_agent and emergency_mode read it, and the event bus, response factory, request logging, block payloads and the WebSocket guard use the same value (the WebSocket guard and two conformance harnesses used a clientIp state key that nothing else read, so they now use client_ip).
  • New ip-identity.ts holds the canonical text rules and one ipInList, replacing four separate list matchers. Entries and CIDRs compare in canonical form, so an IPv4 entry or CIDR matches an IPv4-mapped client, like _ip_in_list. canonicalizeIp (used by the ban manager) now comes from the same module.
  • trustedProxies accepts unix (reference allow_unix), and plain IP entries are validated with strict address parsing instead of ipaddr.js's lenient parser.
  • The adapters log and track behavioral usage with the resolved identity.

Raw clientHost reads replaced

ip-security.ts, rate-limit.ts, suspicious-activity.ts, cloud-provider.ts, user-agent.ts, emergency-mode.ts, route-config.ts (log line), block-events.ts, event-bus.ts, responses/factory.ts, utils.ts (scan and activity log context), websocket-guard.ts, bypass/handler.ts, and the four adapters' behavioral usage call.

Tests

  • tests/test-utils/client-ip-parity.test.ts drives 85 inputs whose expected values were produced by running the reference extract_client_ip (ip-extraction-cases.ts), plus pipeline tests for blacklist, whitelist, CIDR, bans, rate-limit keys, emergency whitelist, trusted X-Forwarded-For, spoofed X-Forwarded-For from an untrusted peer and the no-address path.
  • One real dual-stack listener test per adapter (express, fastify, hono, nestjs): an IPv4 blacklist entry and an IPv4 CIDR block a client that connected as ::ffff:127.0.0.1.
  • These fail on master (the blacklist tests return 200 instead of 403) and pass here.
  • The test request helper now models a request after route_config ran (state.client_ip set); the exempt-ips case that expected an IPv4 entry not to match an IPv4-mapped client now expects the reference behavior.

pnpm build, pnpm lint, pnpm test, pnpm test:coverage (100/100/97/100) and make conformance pass locally.

Port the reference extract_client_ip: canonical identity (IPv4-mapped IPv6 maps to IPv4), trusted proxy matching, trusted_proxy_depth semantics, port and bracket handling, the unix token and the redacted spoof report. The route_config check stores the result on request.state.client_ip and every check, ban, rate-limit key and event reads it instead of the raw connecting address. IP list entries and CIDRs are compared in canonical form.

Closes #135
@github-actions github-actions Bot added documentation Docs, README, CHANGELOG, governance files area: checks Touches packages/core/src/core/checks/ area: handlers Touches packages/core/src/handlers/ area: models Touches packages/core/src/models/ area: middleware-support Touches packages/core/src/middleware-support.ts or index.ts area: utils Touches packages/core/src/utils.ts package: core Changes inside @guardcore/core package: express Changes inside @guardcore/express package: fastify Changes inside @guardcore/fastify package: hono Changes inside @guardcore/hono package: nestjs Changes inside @guardcore/nestjs tests Test suite changes (vitest) area: core-subsystems labels Oct 15, 2026
@rennf93
rennf93 merged commit a5b1d79 into master Oct 15, 2026
17 checks passed
@rennf93
rennf93 deleted the fix/client-ip-extraction-parity branch October 15, 2026 10:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: checks Touches packages/core/src/core/checks/ area: core-subsystems area: handlers Touches packages/core/src/handlers/ area: middleware-support Touches packages/core/src/middleware-support.ts or index.ts area: models Touches packages/core/src/models/ area: utils Touches packages/core/src/utils.ts documentation Docs, README, CHANGELOG, governance files package: core Changes inside @guardcore/core package: express Changes inside @guardcore/express package: fastify Changes inside @guardcore/fastify package: hono Changes inside @guardcore/hono package: nestjs Changes inside @guardcore/nestjs tests Test suite changes (vitest)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Align client IP resolution with the reference engine

1 participant