You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Deploy: production proxy-trust chain unsolved — silently disables #144 HSTS and the #143 per-IP login limiter #260
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:
Security: forwarded headers, HSTS, CSP + security headers, pinned AllowedHosts #144 HSTS + HTTPS redirect become inert.ForwardedHeaders clears the
framework defaults and honours X-Forwarded-Proto/X-Forwarded-Foronly
from networks listed in RateLimiting:TrustedProxies
(Program.cs:243-247).
With nothing trusted, Request.IsHttps == false on every request (the
proxy→container hop is plain HTTP), so app.UseHsts() short-circuits and never
emits Strict-Transport-Security, and app.UseHttpsRedirection() is a no-op
(ASPNETCORE_URLS=http://+:8080 only).
Security: rate limiting on anonymous auth endpoints (login/refresh) #143 per-IP auth rate limiter collapses to one global bucket. The limiter
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/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 TrustedProxiesfail 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.
App-side landed in PR #293 (merged): the serving process now fails boot loudly in Production when RateLimiting:TrustedProxies is empty, with an AllowNoTrustedProxies opt-out for a deliberate direct-TLS deploy. This closes the silent-degradation vector that made this a BLOCKER — HSTS (#144) and the per-IP limiter (#143) can no longer be inert without the boot failing.
Remaining is deploy-side and belongs in a separate deployment/ops repo: set the concrete edge/proxy CIDR for the chosen host (the guard now enforces that it's present). Leaving this open to track that one config step; safe to close once the CIDR is wired in that deployment repo (or now, since the app-side protection is complete).
Closing as a duplicate of the deploy-side work now tracked in a separate deployment/ops repo (set the trusted-proxy CIDR for the chosen edge).
The app-side is complete and merged (PR #293): an empty RateLimiting:TrustedProxies now fails the Production boot (with an AllowNoTrustedProxies opt-out), so the silent-degradation vector that made this a BLOCKER is closed. The only remaining work — setting the concrete edge CIDR — is host config and lives in that separate deployment repo.
Activity
test(api): address #260 review — assert human-readable control names;…
feat(api): fail Production boot when RateLimiting:TrustedProxies is e…
mforce commentedon Jul 30, 2026
App-side landed in PR #293 (merged): the serving process now fails boot loudly in Production when
RateLimiting:TrustedProxiesis empty, with anAllowNoTrustedProxiesopt-out for a deliberate direct-TLS deploy. This closes the silent-degradation vector that made this a BLOCKER — HSTS (#144) and the per-IP limiter (#143) can no longer be inert without the boot failing.Remaining is deploy-side and belongs in a separate deployment/ops repo: set the concrete edge/proxy CIDR for the chosen host (the guard now enforces that it's present). Leaving this open to track that one config step; safe to close once the CIDR is wired in that deployment repo (or now, since the app-side protection is complete).
fix(test): opt out of #260/#262 Production boot guards in integration…
feat(infra): URI connection strings (#261) + Production TLS floor (#262…
docs(agents): document deploy-hardening behaviors + fix stale distro …
mforce commentedon Jul 30, 2026
Closing as a duplicate of the deploy-side work now tracked in a separate deployment/ops repo (set the trusted-proxy CIDR for the chosen edge).
The app-side is complete and merged (PR #293): an empty
RateLimiting:TrustedProxiesnow fails the Production boot (with anAllowNoTrustedProxiesopt-out), so the silent-degradation vector that made this a BLOCKER is closed. The only remaining work — setting the concrete edge CIDR — is host config and lives in that separate deployment repo.docs(agents): drop the private deploy repo's name from the host-agnos…
fix(ci): supply AllowedHosts to the image-job smoke test's serving co…
feat(api): fail Production boot on wildcard/unset AllowedHosts (#319) (…
fix(api): reject a nonzero report queue limit at boot (#311)
fix(api): bound report query cost and cap concurrent reports per acco…
fix(sim): restore the dropped drift check, kill two false passes, bou…
fix(sim): unbreak the #243 harness against main, with a local self-ch…
test(api): prove the serving guards hostile, not just one of them
test(api): probe malformed serving-only config, not just its absence
fix(api): give every boot guard an explicit process role, and fail cl…
docs: compress AGENTS.md to one paragraph per rule, and draw the two …
test(api): verify HSTS and HTTPS redirection over real TLS (#344) (#821)