fix(security): stop trusting client-supplied forwarded headers in rate limiting

extract_client_ip took X-Real-IP/X-Forwarded-For from any request, so
a client talking to the backend directly (the FIPS peer listener, or
any non-proxy path) could rotate a fake IP per request and never trip
the login rate limiter. The accept loop now records the TCP peer
address in request extensions, and forwarded headers are honored only
when the connection itself is from loopback — where nginx overwrites
X-Real-IP with the real client address. Direct connections bucket
under their socket IP.

§C of the 1.8.0 hardening plan; 3 new unit tests cover the
loopback/direct/no-header matrix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-04 15:48:07 -04:00
co-authored by Claude Fable 5
parent bd7edb4376
commit 9020b8526c
4 changed files with 106 additions and 12 deletions
+4 -3
View File
@@ -58,6 +58,7 @@ use middleware::{
derive_csrf_token, extract_client_ip, extract_cookie, sanitize_error_message,
CACHEABLE_METHODS, UNAUTHENTICATED_METHODS,
};
pub use middleware::PeerAddr;
use response::{cookie_header, json_response, ResponseCache, RpcError, RpcRequest, RpcResponse};
/// Default dev password when no user is set up (matches mock-backend).
@@ -369,7 +370,7 @@ impl RpcHandler {
// Rate limit login attempts
if rpc_req.method == "auth.login" {
let client_ip = extract_client_ip(&parts.headers);
let client_ip = extract_client_ip(&parts);
if !self.login_rate_limiter.check(client_ip).await {
return Ok(self.rate_limit_response());
}
@@ -377,7 +378,7 @@ impl RpcHandler {
// Rate limit sensitive endpoints
{
let client_ip = extract_client_ip(&parts.headers);
let client_ip = extract_client_ip(&parts);
if !self
.endpoint_rate_limiter
.check(&rpc_req.method, client_ip)
@@ -451,7 +452,7 @@ impl RpcHandler {
let mut response = json_response(StatusCode::OK, &resp_body);
// Post-dispatch: set cookies for auth-related methods
let client_ip = extract_client_ip(&parts.headers);
let client_ip = extract_client_ip(&parts);
self.apply_auth_cookies(
&rpc_req.method,
&mut rpc_resp,