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
References
Context
Inspired by the Cranker Connector's
withClientIpProviderabstraction,vyxshould provide a pluggable way to resolve the real client IP address from incoming HTTP requests.Currently, the rate limiter and access logger in
vyxuser.RemoteAddrdirectly, which always returns the IP of the last hop (e.g., a reverse proxy like nginx or Cloudflare). This means:The Problem
Proposed Solution
Introduce a
ClientIPResolverinterface that is pluggable at startup and used by both the rate limiter and the request context.Interface
Built-in implementations
Configuration in vyx.yaml
Usage
The resolved IP replaces
r.RemoteAddrin:RateLimiter.AllowIP(ip)incore/application/gateway/rate_limiter.goclient_ipfieldGatewayRequest.ClientIPfield (available to workers via IPC payload)Security Note
Acceptance Criteria
ClientIPResolverinterface defined incore/infrastructure/gatewayRemoteAddrResolverimplemented (default, no config required)ForwardedForResolverimplemented withtrusted_proxiesCIDR validationRealIPResolverimplementedRateLimiteruses the configured resolver instead ofr.RemoteAddrGatewayRequestvyx.yamlschema updated withgateway.client_ipblockforwarded-formodeReferences
withClientIpProvidercore/application/gateway/rate_limiter.go— current IP-based rate limitingcore/infrastructure/gateway/server.go—handle()wherer.RemoteAddris used