fix(rate_limit): use ConnectInfo as sole IP source, reject without it

The rate limiter previously extracted client IP from the X-Forwarded-For
header first, falling back to ConnectInfo. This allowed attackers to bypass
rate limits by sending spoofed X-Forwarded-For headers. Per ADR-025, the
rate limiter now uses ConnectInfo<SocketAddr> exclusively and rejects
requests with 429 when ConnectInfo is absent.
This commit is contained in:
2026-06-12 14:00:31 +00:00
parent 54f1725173
commit ad9b9b9b78
2 changed files with 9 additions and 25 deletions

View File

@@ -64,24 +64,12 @@ pub async fn rate_limit_middleware(
next: Next,
) -> axum::response::Response {
let client_ip = req
.headers()
.get("x-forwarded-for")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.split(',').next())
.and_then(|v| v.trim().parse::<IpAddr>().ok())
.or_else(|| {
req.extensions()
.get::<axum::extract::ConnectInfo<std::net::SocketAddr>>()
.map(|ci| ci.ip())
});
.extensions()
.get::<axum::extract::ConnectInfo<std::net::SocketAddr>>()
.map(|ci| ci.ip());
let Some(ip) = client_ip else {
// If no client IP can be identified, the request passes through without rate
// limiting. In practice, ConnectInfo is always set by the server's
// ConnectInfoService, so this branch is unreachable. If the proxy were ever
// deployed without ConnectInfo propagation, rate limiting would silently become
// a no-op. Consider adding a warning log or returning 429 in a future phase.
return next.run(req).await;
return (StatusCode::TOO_MANY_REQUESTS, "Too Many Requests").into_response();
};
let host = req