fix(content_feedback): prevent anonymous rate-limit bypass via spoofed X-Forwarded-For - #3751
Merged
Merged
Conversation
Set REST_FRAMEWORK NUM_PROXIES=2 (APISIX + nginx) so the anonymous rate throttle identifies the client from the trusted, infra-appended entry of X-Forwarded-For instead of the whole spoofable header. Without it a client could rotate X-Forwarded-For to bypass the limit entirely. Fixes mitodl/hq#12775
OpenAPI ChangesNo changes detected Unexpected changes? Ensure your branch is up-to-date with |
Contributor
There was a problem hiding this comment.
Pull request overview
Secures anonymous content-feedback throttling against spoofed X-Forwarded-For values.
Changes:
- Configures DRF to trust two proxy hops.
- Adds a regression test for spoofed forwarding headers.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
main/settings.py |
Configures trusted proxy count. |
content_feedback/views_test.py |
Tests anonymous IP throttling. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…nt IP The regression only proved rotating the spoofable left entry shares a bucket -- which also holds if every request were keyed to one constant proxy/peer address. Add an assertion that a genuinely different client (different 2nd-from-right entry) gets a fresh 201, verifying the throttle selects the trusted address rather than merely ignoring the spoof.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What are the relevant tickets?
Fixes mitodl/hq#12775. Follow-up to #3738 (anonymous content-feedback submissions), part of mitodl/hq#11629.
Description (What does it do?)
The
content_feedbackrate throttle keys anonymous requests by client IP via DRF'sget_ident. WithNUM_PROXIESunset, DRF keys on the entireX-Forwarded-Forheader — and our nginx appends to that header (config/nginx.conf.erb), so a client can prepend arbitrary values and rotate them to bypass the limit.This sets
NUM_PROXIES = 2inREST_FRAMEWORK(the trusted hops in front of the app: APISIX + nginx, confirmed with DevOps). DRF then reads the real client from the trusted, infra-appended entry (2nd from the right) instead of the spoofable left side. Kept env-overridable (get_int("NUM_PROXIES", 2)) in case the topology changes.Now the sole guard on the open anonymous write endpoint actually holds.
Screenshot?
How can this be tested?
1. Automated
test_anonymous_throttle_ignores_spoofed_xffrotates the client-controlled left entry ofX-Forwarded-Forwhile keeping the trusted suffix fixed and asserts the third request over a2/minlimit is429. It fails onmain(all201— bypass) and passes with this change.2. Manual (mirrors the local repro from #3738 review)
Drop the limit so it's easy to trip, then restart web:
Post straight to the web container (no proxy locally, so
curlcontrols the whole header). Emulate the prod chain<spoofed>, <real-client>, <apisix>and rotate only the spoofable left entry:201, 201, 429. Rotating the spoof no longer mints a new bucket; the trusted203.0.113.5keys all three.NUM_PROXIES=99inenv/backend.local.env(trusts the spoofable left entry) and re-run →201, 201, 201, matching Matt's reproduction onmain.Re-running within the same minute reuses the bucket — bump the real-client IP (
203.0.113.6) or wait 60s for a clean pass.