Promote the 2026-08-30 review security fixes and supplies ordering to main

This commit is contained in:
2026-08-31 09:24:13 -04:00
parent 6680c3c5de
commit 0e3d5a6226
30 changed files with 991 additions and 98 deletions

View File

@@ -6,6 +6,23 @@ every connection - so it must be stripped before the value is stored (audit),
matched (dashboard IP fallback), or used as a rate-limit key (login). Left in,
it makes every request look like a different client.
X-Forwarded-For is NOT read here. A client can send that header itself, so
trusting it unconditionally hands any caller a free identity: the login rate
limit and the account lockout are keyed on this value, and the audit log
records it. Reading it directly also walked around TRUSTED_PROXY_COUNT, which
exists precisely to decide whether a proxy is really in front (see the ProxyFix
block in shopdb/__init__.py).
The trusted case is already handled upstream. When TRUSTED_PROXY_COUNT is set,
ProxyFix resolves X-Forwarded-For into remote_addr before the request reaches a
view, so remote_addr is the real client. When it is unset, remote_addr is the
peer that actually connected. Either way remote_addr is the trustworthy source,
which is why this module only strips the port from it.
A site behind IIS or nginx MUST set TRUSTED_PROXY_COUNT (1 for a single proxy),
or every request will key on the proxy's own address and the per-client rate
limit becomes one shared bucket.
Port stripping is IPv6-aware: a bracketed [2001:db8::1]:443 loses its port; a
bare 2001:db8::1 (many colons, no brackets) is returned untouched.
"""
@@ -26,14 +43,11 @@ def strip_port(ipaddress):
def client_ip(request_obj):
"""First-hop client IP behind one proxy, port stripped.
"""Client IP, port stripped, from the connecting peer.
Honors the first X-Forwarded-For hop, else remote_addr. Returns None only
when neither is present (callers that need a non-null key coerce it).
Resolved from remote_addr, which ProxyFix has already rewritten from
X-Forwarded-For when TRUSTED_PROXY_COUNT says a proxy is really in front.
Returns None only when remote_addr is absent (callers that need a non-null
key coerce it).
"""
forwarded = request_obj.headers.get('X-Forwarded-For')
if forwarded:
raw = forwarded.split(',')[0].strip()
else:
raw = request_obj.remote_addr
return strip_port(raw)
return strip_port(request_obj.remote_addr)