feat(api): add trusted proxy handling to local request detection

The `is_local_request` function now supports trusted reverse proxies by:
- Reading `TRUSTED_PROXIES` from configuration.
- Resolving the real client IP from `X-Real-IP` or `X-Forwarded-For` headers when the request comes through a trusted proxy.
- Adding IPv6 loopback support and detailed logging for misconfigurations.
- Updating configuration to expose `trusted_proxies` via the `TRUSTED_PROXIES` environment variable.
This commit is contained in:
vvzvlad
2026-05-17 22:55:50 +03:00
parent 64fe6327d8
commit 40fa9797e8
2 changed files with 39 additions and 5 deletions
+38 -5
View File
@@ -703,11 +703,44 @@ def calculate_cache_stats() -> dict[str, Any]:
def is_local_request(request: Request) -> bool:
local_hosts = ["127.0.0.1", "localhost"]
if request and request.client and request.client.host:
if request.client.host in local_hosts:
return True
return False
"""Return True if the request originates from a local (loopback) address.
When the service runs behind a trusted reverse proxy (configured via
TRUSTED_PROXIES env var), the real client IP is taken from X-Real-IP or
X-Forwarded-For instead of the TCP connection address.
"""
local_hosts = {"127.0.0.1", "::1"}
if not request or not request.client or not request.client.host:
return False
connection_ip = request.client.host
trusted_proxies: list[str] = Config.get("trusted_proxies", [])
if trusted_proxies and connection_ip in trusted_proxies:
# Connection comes from a known proxy — resolve the real client IP.
# X-Real-IP is a single IP set by nginx; prefer it.
real_ip = request.headers.get("x-real-ip", "").strip()
if not real_ip:
# X-Forwarded-For may be a comma-separated list; rightmost is the value appended
# by the trusted proxy itself and cannot be forged by the client.
forwarded_for = request.headers.get("x-forwarded-for", "").strip()
real_ip = forwarded_for.split(",")[-1].strip() if forwarded_for else ""
if not real_ip:
# Trusted proxy did not supply any forwarding header — misconfiguration.
# Fail safe: do not grant local access when the real client IP is unknown.
logger.warning(
"Trusted proxy %s provided no X-Real-IP or X-Forwarded-For header; "
"treating as non-local request for safety.",
connection_ip
)
return False
client_ip = real_ip
else:
# Direct connection (no trusted proxy): use TCP connection address.
client_ip = connection_ip
return client_ip in local_hosts
@app.get("/", response_class=HTMLResponse)
+1
View File
@@ -71,6 +71,7 @@ def get_settings() -> dict[str, Any]:
"log_level": log_level,
"debug": os.getenv("DEBUG", "False") == "True",
"token": os.getenv("TOKEN", ""),
"trusted_proxies": [ip.strip() for ip in os.getenv("TRUSTED_PROXIES", "").split(",") if ip.strip()],
"time_based_merge": os.getenv("TIME_BASED_MERGE", "False").strip() in ["True", "true"],
"show_bridge_link": os.getenv("SHOW_BRIDGE_LINK", "False").strip() in ["True", "true"],
"show_post_flags": os.getenv("SHOW_POST_FLAGS", "False").strip() in ["True", "true"],