Skip to content

sec: parse X-Forwarded-For from trusted proxies via WIREFAN_TRUSTED_PROXIES - #19

Merged
EthanY33 merged 1 commit into
mainfrom
sec/trust-proxy-xff
May 7, 2026
Merged

sec: parse X-Forwarded-For from trusted proxies via WIREFAN_TRUSTED_PROXIES#19
EthanY33 merged 1 commit into
mainfrom
sec/trust-proxy-xff

Conversation

@EthanY33

@EthanY33 EthanY33 commented May 7, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #9.

clientIP returned RemoteAddr unconditionally. Behind the documented Caddy deployment every connection is 127.0.0.1, so defaultIPCap = 200 is a global cap — one attacker exhausts the slot pool for everyone.

Adds opt-in X-Forwarded-For parsing using rightmost-untrusted-hop:

  • New env WIREFAN_TRUSTED_PROXIES, comma-separated CIDRs or single IPs (e.g. 127.0.0.1,10.0.0.0/8). Single IPs become /32 (IPv4) or /128 (IPv6).
  • When RemoteAddr is in trustedProxies, walk X-Forwarded-For right-to-left and return the first hop whose IP is not in trustedProxies. The leftmost entry is never authoritative — clients can pick that themselves.
  • When trustedProxies is empty (default), behavior is unchanged: XFF is ignored.

What changed

  • internal/server/upgrade.go:
    • UpgradeHandler gains trustedProxies []netip.Prefix, populated from WIREFAN_TRUSTED_PROXIES at construction.
    • New parseTrustedProxies, addrInPrefixes, stripHostPort helpers.
    • clientIP takes trustedProxies and parses XFF when the source is trusted.
  • internal/server/upgrade_test.go:
    • TestParseTrustedProxies (CIDR / bare IP / malformed dropped)
    • TestClientIPNoTrust (XFF ignored without trust)
    • TestClientIPTrustedProxyPicksRightmostUntrusted (six scenarios)

Operator notes

# behind Caddy on the same host
WIREFAN_TRUSTED_PROXIES=127.0.0.1 ./wirefan

# behind a private subnet of proxies
WIREFAN_TRUSTED_PROXIES=10.0.0.0/8,192.168.0.0/16 ./wirefan

The Caddyfile already sets X-Forwarded-For; setting the env makes the per-IP cap actually per-client-IP again.

Test plan

  • go build ./... clean.
  • go test ./internal/server/... passes (existing + 8 new cases).
  • Manual: start with WIREFAN_TRUSTED_PROXIES=127.0.0.1, send curl -H "X-Forwarded-For: 1.2.3.4" localhost:8080/v1/connect?key=... from loopback → metrics show source IP 1.2.3.4, not 127.0.0.1.

Conflict notes

This PR touches internal/server/upgrade.go (also touched by #16 sanitize-logs). Conflicts are minimal — different lines — and git merge should resolve cleanly. Whichever lands first, the other will need a trivial rebase.

…ROXIES

Fixes #9.

clientIP previously returned RemoteAddr unconditionally. Behind the
documented Caddy deployment that means every connection is
127.0.0.1, so the per-IP cap of 200 was effectively a global cap of
200 — one attacker exhausts the slot pool for everyone.

Adds opt-in XFF parsing:

- New env WIREFAN_TRUSTED_PROXIES, comma-separated CIDRs or single IPs
  (e.g. "127.0.0.1,10.0.0.0/8"). Single IPs become /32 or /128.
- When a request's RemoteAddr is in trustedProxies, walk
  X-Forwarded-For right-to-left and return the first untrusted hop.
  This is the rightmost-untrusted-hop algorithm — clients can write
  the leftmost entry themselves, so the leftmost is never authoritative.
- When trustedProxies is empty (default), behavior is unchanged: XFF
  is ignored, RemoteAddr is the source of truth.

Helpers:
- parseTrustedProxies(string) []netip.Prefix — handles bare IPs,
  CIDRs, malformed entries dropped silently.
- addrInPrefixes / stripHostPort — small utilities.

Tests cover: untrusted source ignores XFF, trusted proxy walks
right-to-left, all-trusted falls back to leftmost, IPv6 loopback,
single-client and chained-proxy paths.
@EthanY33
EthanY33 force-pushed the sec/trust-proxy-xff branch from 0f9a1b1 to c36ba65 Compare May 7, 2026 23:02
@EthanY33
EthanY33 merged commit 3abaffb into main May 7, 2026
2 checks passed
@EthanY33
EthanY33 deleted the sec/trust-proxy-xff branch May 7, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Medium] clientIP trusts RemoteAddr only (X-Forwarded-For not parsed behind Caddy)

1 participant