How trusted-proxy rate-limit identity stays spoof-resistant

mcpgw derives IP-based rate-limit identity from RemoteAddr by default. Operators behind a reverse proxy can opt into RFC Forwarded or X-Forwarded-For with a CIDR allowlist. Forwarding headers remain untrusted everywhere else.


The threat model

X-Forwarded-For is a header. Headers come from the client. Any client can send any header value.

The convention is that a trusted proxy appends or replaces forwarding information with the real client IP. mcpgw only acts on that information when the TCP peer matches an explicitly configured trusted_proxies CIDR.

If mcpgw trusted X-Forwarded-For, an attacker who reached the gateway directly (bypassing the LB) could spoof their identity by sending X-Forwarded-For: 8.8.8.8 and either:

  • Evade per-IP throttles by rotating fake IPs
  • Frame a known-good IP as the source of abuse (e.g. to get it banned)

The first attack is more common; the second is more interesting. Both are defeated by ignoring headers from untrusted peers and walking trusted chains from right to left.


What the safe default is

RemoteAddr is the IP that actually opened the TCP connection. It cannot be forged at the application layer. Whatever sat at the other end of the kernel’s socket is what RemoteAddr reports.

The cost: if your gateway sits behind an LB and trusted_proxies is not configured, every request appears to come from the LB’s IP and all clients share one rate-limit bucket. This is safe in the absence of operator action.


What you do about it

There are two supported ways to recover real client identity for rate-limit purposes.

Option 1 — Terminate TLS at mcpgw

Skip the LB entirely. Run mcpgw as the TLS edge. RemoteAddr is the real client.

listen: 0.0.0.0:7332
tls:
  cert_file: /etc/mcpgw/tls/fullchain.pem
  key_file:  /etc/mcpgw/tls/privkey.pem

Renew certs out-of-band (cert-manager, certbot, ACM-side renewals + bind-mount swap). mcpgw re-reads cert/key on SIGHUP.

This is the simplest topology because it eliminates proxy-identity configuration.

Option 2 — Explicitly trust the reverse-proxy network

Configure only the CIDRs from which mcpgw actually receives proxy connections:

trusted_proxies:
  - 10.0.0.0/24
  - 2001:db8:10::/48

When the direct peer is trusted, mcpgw prefers RFC Forwarded, falls back to X-Forwarded-For, and walks the addresses from right to left. Trusted proxy hops are skipped; the first untrusted address becomes the client. A client-supplied leftmost address cannot override a later untrusted address appended by the proxy.

This still depends on correct network boundaries. Restrict direct access to mcpgw with firewall/security-group rules, configure the proxy to append or replace forwarding headers consistently, and never trust a broader CIDR than the actual ingress network. A compromised host inside a trusted CIDR can assert client identity.


What this does not affect

Two important clarifications:

Policy rate-limit identity uses the authenticated principal when available. When a policy.rules[] rule has action: rate_limit, the bucket key is (rule_id, principal). OAuth callers use identity.principal_claim (default JWT sub) with fallback to client_id; API-key callers use the key id; unauthenticated callers fall back to the effective client IP.

Gateway input rate-limit identity uses the effective client IP. The top-level input_rate_limit runs before body read, auth, parse, policy, and routing. Its bucket key is RemoteAddr by default or the resolved forwarding chain when the direct peer is trusted. This protects mcpgw’s own pre-parse work from request floods; it is not a per-tool quota.

The client_ip field in the audit log is RemoteAddr too. That field is for traceability of the connection, not for client identification. If your audit log says client_ip: <LB-IP>, that is correct: the LB is the connection peer. Authenticated caller identity, when present, lives in principal, auth_key_id, and the OAuth audit fields.


Why this is a CIDR list, not a boolean

A trust_xff: true switch cannot express who may assert client identity. trusted_proxies ties header trust to the immediate network peer and keeps direct clients on the non-spoofable RemoteAddr path. The CIDR list and network access controls should describe the same boundary.