Deployment-readiness gap (#244), BLOCKER — flagged independently by all 3 reviewers.
Problem
Behind any reverse proxy / edge (the production topology terminates TLS at an
edge and forwards plain HTTP to the container), the app never learns the original
request was HTTPS or who the real client is unless the immediate proxy is
trusted. Two already-"hardened" controls silently break when the trusted-proxy
list is empty:
The compose stack already ships a sane default (TRUSTED_PROXY_CIDR=172.16.0.0/12
in deploy/.env.example + docker-compose.yml); the gap is that a real deploy
must set the correct immediate-proxy CIDR for its edge, and leaving it empty is
unsafe.
Fix (app-side, portable)
The app already reads the trusted-proxy list from config — keep that, and make an
empty TrustedProxies fail loud in Production (refuse to serve with inert
HSTS / a collapsed limiter) so a misconfigured deploy can't silently ship without
the controls. The concrete CIDR for the chosen edge is deploy-repo config.
Verify
curl -I https://<prod> shows Strict-Transport-Security.
- A forged
X-Forwarded-For from a non-proxy peer is ignored.
- Two distinct clients hit two distinct login buckets (11 logins from client A
does not 429 client B).
Part of the #244 deployment-readiness audit; tracked in epic #15.
Deployment-readiness gap (#244), BLOCKER — flagged independently by all 3 reviewers.
Problem
Behind any reverse proxy / edge (the production topology terminates TLS at an
edge and forwards plain HTTP to the container), the app never learns the original
request was HTTPS or who the real client is unless the immediate proxy is
trusted. Two already-"hardened" controls silently break when the trusted-proxy
list is empty:
ForwardedHeadersclears theframework defaults and honours
X-Forwarded-Proto/X-Forwarded-Foronlyfrom networks listed in
RateLimiting:TrustedProxies(Program.cs:243-247).
With nothing trusted,
Request.IsHttps == falseon every request (theproxy→container hop is plain HTTP), so
app.UseHsts()short-circuits and neveremits
Strict-Transport-Security, andapp.UseHttpsRedirection()is a no-op(
ASPNETCORE_URLS=http://+:8080only).keys on
context.Connection.RemoteIpAddress(Program.cs:297), which is now
the proxy's single IP — identical for every visitor. One attacker exhausts the
global 10-logins-per-15-min budget and locks out every real user.
The compose stack already ships a sane default (
TRUSTED_PROXY_CIDR=172.16.0.0/12in
deploy/.env.example+docker-compose.yml); the gap is that a real deploymust set the correct immediate-proxy CIDR for its edge, and leaving it empty is
unsafe.
Fix (app-side, portable)
The app already reads the trusted-proxy list from config — keep that, and make an
empty
TrustedProxiesfail loud in Production (refuse to serve with inertHSTS / a collapsed limiter) so a misconfigured deploy can't silently ship without
the controls. The concrete CIDR for the chosen edge is deploy-repo config.
Verify
curl -I https://<prod>showsStrict-Transport-Security.X-Forwarded-Forfrom a non-proxy peer is ignored.does not 429 client B).
Part of the #244 deployment-readiness audit; tracked in epic #15.