Skip to content

fix(settings): trust reverse proxy for client IP in rate limiting - #1813

Merged
edward-ly merged 1 commit into
masterfrom
fix/login-ratelimit-trusted-proxy
Jul 15, 2026
Merged

fix(settings): trust reverse proxy for client IP in rate limiting#1813
edward-ly merged 1 commit into
masterfrom
fix/login-ratelimit-trusted-proxy

Conversation

@miaulalala

Copy link
Copy Markdown
Contributor

Summary

  • mod_wsgi logs show every request's REMOTE_ADDR as 127.0.0.1 — there's a reverse proxy in front of Apache that isn't being accounted for by the app.
  • django-allauth's rate limiter (backing ACCOUNT_RATE_LIMITS["login_failed"]) resolves the client IP via allauth.core.internal.httpkit.get_client_ip(), which only trusts X-Forwarded-For when ALLAUTH_TRUSTED_PROXY_COUNT > 0. That setting wasn't set anywhere, so it fell back to REMOTE_ADDR — i.e. the proxy's own address for every visitor.
  • Net effect: the per-IP login_failed: "10/h/ip" limit collapsed into a single site-wide bucket of 10 failed password attempts per hour, shared by every user. Once exhausted (typos, scanners, credential stuffing — trivial to trigger), every regular username/password login got rejected with 429 until the hour rolled over, while GitHub OAuth — a separate code path with no rate limiter — kept working. This matches reports of "GitHub login works, regular login doesn't."
  • Confirmed with sysadmins: there's exactly one reverse-proxy hop in front of Apache, and X-Forwarded-For is reliably populated with the real client IP in this deployment (an existing ops-side logging filter for bad_requests.log/fail2ban already depends on it).
  • Fix: set ALLAUTH_TRUSTED_PROXY_COUNT = 1 so allauth resolves the real client IP from the trusted hop of X-Forwarded-For instead of REMOTE_ADDR, restoring per-visitor rate limiting.

Test plan

  • python -c "... from allauth import app_settings; print(app_settings.TRUSTED_PROXY_COUNT)" → confirms 1 is picked up from settings.
  • Post-deploy: verify a burst of failed logins from one real client IP no longer blocks unrelated users' logins.

🤖 Generated with Claude Code

@marcelklehr marcelklehr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@edward-ly
edward-ly force-pushed the fix/login-ratelimit-trusted-proxy branch from 06a7bfb to f103e4c Compare July 15, 2026 04:17
Apache/mod_wsgi logs show every request's remote address as 127.0.0.1,
meaning a reverse proxy sits in front of it. allauth's rate limiter
(used by ACCOUNT_RATE_LIMITS["login_failed"]) only reads the real
client IP from X-Forwarded-For when ALLAUTH_TRUSTED_PROXY_COUNT > 0;
left unset, it fell back to REMOTE_ADDR, i.e. the proxy's own address
for every visitor. That collapsed the per-IP login_failed limit into a
single site-wide bucket of 10 failed attempts per hour shared by every
user, so once exhausted (typos, scanners, credential stuffing), regular
username/password login was rejected for everyone until the hour
rolled over -- while GitHub OAuth, a separate code path with no rate
limiter, kept working. This matches reports of 'GitHub login works but
regular login doesn't'.

Set ALLAUTH_TRUSTED_PROXY_COUNT = 1 so allauth resolves the real client
IP from the last hop of X-Forwarded-For instead of REMOTE_ADDR,
restoring per-visitor rate limiting.

Pending sysadmin confirmation that there is exactly one reverse proxy
hop in front of Apache, that it sets X-Forwarded-For itself (rather
than forwarding a client-supplied value), and that Apache is never
reachable bypassing that proxy -- required for this setting to be safe.

Signed-off-by: Anna Larch <anna@nextcloud.com>
@edward-ly
edward-ly force-pushed the fix/login-ratelimit-trusted-proxy branch from f103e4c to 90401db Compare July 15, 2026 04:26
@edward-ly
edward-ly merged commit 7553796 into master Jul 15, 2026
8 checks passed
@edward-ly
edward-ly deleted the fix/login-ratelimit-trusted-proxy branch July 15, 2026 04:43
pull Bot pushed a commit to Zezo-Ai/Zezo-Ai-next-appstore that referenced this pull request Jul 21, 2026
copy-webpack-plugin@14 requires Node >= 20.9.0 (uses Array.prototype.toSorted()
internally), but the production server runs Node 18.19.1, so the production
build fails with "TypeError: [...].toSorted is not a function". This blocks
deployment of nextcloud#1813, which fixes app store login/password-reset.

Signed-off-by: Anna Larch <anna@nextcloud.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants