Skip to content

[Feature] Pluggable Client IP Resolver (X-Forwarded-For / X-Real-IP support) #57

@ElioNeto

Description

@ElioNeto

Context

Inspired by the Cranker Connector's withClientIpProvider abstraction, vyx should provide a pluggable way to resolve the real client IP address from incoming HTTP requests.

Currently, the rate limiter and access logger in vyx use r.RemoteAddr directly, which always returns the IP of the last hop (e.g., a reverse proxy like nginx or Cloudflare). This means:

  • All clients behind a proxy appear as the same IP → rate limiting is broken
  • Access logs show proxy IPs instead of real client IPs
  • Security controls based on IP are ineffective

The Problem

[Real Client 1.2.3.4]
         │
         ▼
[Nginx / Cloudflare]  ── X-Forwarded-For: 1.2.3.4
         │
         ▼
[vyx Core]  ← r.RemoteAddr = "10.0.0.1:54321" (proxy IP, not client!)

Proposed Solution

Introduce a ClientIPResolver interface that is pluggable at startup and used by both the rate limiter and the request context.

Interface

// ClientIPResolver extracts the real client IP from an HTTP request.
type ClientIPResolver interface {
    ClientIP(r *http.Request) string
}

Built-in implementations

// RemoteAddrResolver uses r.RemoteAddr directly (default, safe for direct exposure).
type RemoteAddrResolver struct{}

// ForwardedForResolver trusts the first IP in X-Forwarded-For.
// Only use this when vyx is behind a trusted reverse proxy.
type ForwardedForResolver struct {
    TrustedProxies []string // CIDR ranges considered trusted
}

// RealIPResolver uses the X-Real-IP header (nginx convention).
type RealIPResolver struct{}

Configuration in vyx.yaml

gateway:
  client_ip:
    resolver: forwarded-for     # remote-addr | forwarded-for | real-ip
    trusted_proxies:
      - 10.0.0.0/8
      - 172.16.0.0/12
      - 103.21.244.0/22         # Cloudflare range example

Usage

The resolved IP replaces r.RemoteAddr in:

  • RateLimiter.AllowIP(ip) in core/application/gateway/rate_limiter.go
  • Access log client_ip field
  • GatewayRequest.ClientIP field (available to workers via IPC payload)

Security Note

⚠️ ForwardedForResolver must only be enabled when vyx is behind a trusted proxy. Blindly trusting X-Forwarded-For in a direct-exposure setup allows IP spoofing. The trusted_proxies list mitigates this.

Acceptance Criteria

  • ClientIPResolver interface defined in core/infrastructure/gateway
  • RemoteAddrResolver implemented (default, no config required)
  • ForwardedForResolver implemented with trusted_proxies CIDR validation
  • RealIPResolver implemented
  • RateLimiter uses the configured resolver instead of r.RemoteAddr
  • Resolved IP included in access logs and GatewayRequest
  • vyx.yaml schema updated with gateway.client_ip block
  • Unit tests for each resolver including spoofing scenarios
  • Documentation with security warnings for forwarded-for mode

References

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions