Add clarifying comments for correct-but-non-obvious behaviors (C3, W8, W10, W11, S9)

This commit is contained in:
2026-06-12 05:05:10 +00:00
parent 516efb0403
commit 9b3fe23499
5 changed files with 20 additions and 0 deletions

View File

@@ -181,6 +181,10 @@ pub fn validate(
}
}
// Health check always binds to 127.0.0.1 (hardcoded in src/health.rs), so this
// conflict check is conservative — it warns even when the health check port
// wouldn't actually conflict (e.g., health check on 127.0.0.1:80 vs listener
// on 203.0.113.10:80). This is acceptable for Phase 1.
if static_config.health_check_port > 0 {
for listener in &static_config.listeners {
if static_config.health_check_port == listener.https_port {

View File

@@ -23,6 +23,10 @@ pub async fn body_limit_middleware(
limit
};
// Early rejection: if Content-Length is present and exceeds the limit, reject
// immediately without reading the body. For requests without Content-Length
// (chunked, HTTP/2), the Limited body wrapper below enforces the limit during
// streaming. This is a two-layer defense.
if let Some(content_length) = request.headers().get("content-length") {
if let Ok(length_str) = content_length.to_str() {
if let Ok(length) = length_str.parse::<u64>() {

View File

@@ -110,6 +110,9 @@ async fn proxy_handler(
);
let (mut parts, body) = upstream_resp.into_parts();
remove_hop_by_hop(&mut parts.headers);
// The upstream Server header is intentionally removed. As a security-focused
// reverse proxy, we hide upstream server identity as a defense-in-depth measure.
// The proxy does not add its own Server header either. See W8 in review #002.
parts.headers.remove("server");
let body = Body::new(body);
Response::from_parts(parts, body)

View File

@@ -25,6 +25,10 @@ pub fn inject_proxy_headers(headers: &mut HeaderMap, remote_addr: SocketAddr) {
headers.insert(HeaderName::from_static("x-real-ip"), ip_value.clone());
// X-Forwarded-For is SET (not appended) because this proxy is the outermost
// edge proxy. Any existing X-Forwarded-For from the client is untrusted and
// must be replaced with the actual client IP from ConnectInfo.
// See ADR-021 for the edge proxy model rationale.
headers.insert(HeaderName::from_static("x-forwarded-for"), ip_value);
// X-Forwarded-Proto is always "https" because this proxy only forwards requests

View File

@@ -76,6 +76,11 @@ pub async fn rate_limit_middleware(
});
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;
};