Nobody has started this yet — be first.
Business impact
Rate limiting exists to protect the business from abuse -- credential stuffing on login, scraping, or a single bad actor consuming disproportionate infrastructure cost. A key-derivation bug that lets an attacker pick a fresh "identity" per request defeats that protection entirely, for every rule keyed by IP. Concretely: the login-brute-force-guard rule becomes decorative and any IP-based abuse mitigation elsewhere in the product inherits the same hole. This is a security control that looks like it's working (metrics show denials happening, dashboards look normal) while silently protecting against nothing for anyone who bothers to read the header-splitting logic -- exactly the kind of gap that shows up in a security audit or, worse, in an incident postmortem.
Problem
KeyByIP's doc comment says it prefers "the leftmost hop in X-Forwarded-For (set by a trusted upstream proxy)." The implementation does not do that anymore -- it picks a different entry than documented.
Current behavior
For a request with header X-Forwarded-For: 203.0.113.7, 10.0.0.5 (the standard convention: original client IP first, each proxy appends its own hop's peer address after), KeyByIP returns 10.0.0.5 -- one of your own infrastructure's internal hop addresses, not the client.
Expected behavior
KeyByIP should return 203.0.113.7, the leftmost entry, which by convention is the original client address as seen by the first proxy in the chain.
Steps to reproduce
req := httptest.NewRequest("GET", "/", nil) req.Header.Set("X-Forwarded-For", "203.0.113.7, 10.0.0.5") key := middleware.KeyByIP(req) // got: "10.0.0.5" // want: "203.0.113.7"
Why this matters
This isn't just cosmetic -- it's a rate-limit bypass. If the key used for rate limiting is influenced by a hop an attacker can control (any value they put in X-Forwarded-For themselves, if your edge proxy doesn't strip and overwrite the header before forwarding), and the code picks a different position than the one your trusted proxy actually appends, an attacker can manipulate the number of comma-separated segments they send to control which "key" they get rate-limited under -- effectively picking a fresh identity per request and evading the limiter entirely.
Suggested approach
Look at exactly which slice index is being read out of the comma-split header value, and compare it against what the doc comment promises ("leftmost"). Fix the index, not the splitting/trimming logic (which is otherwise correct).
Acceptance criteria
Verification
go test ./test/ -run TestTask04 -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b fix/forwarded-for-wrong-hopFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/forwarded-for-wrong-hopSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inhttp://localhost:8080localhost:6380http://localhost:8081curl localhost:8080/healthz
curl localhost:8080/api/v1/rules
curl -s -X POST localhost:8080/api/v1/check -d '{"rule":"free-tier","key":"user-123"}' | jq
No Docker? STORE_BACKEND=memory is the default, so go run ./cmd/server alone (no Redis needed) exercises everything except the Redis-backed code paths.
Work the tickets in tasks/ (ordered easy → hard); each names one Go test in test/:
make test-race # go test -race -count=1 ./...
go test ./test/ -run TestTask01 -v # a single ticket
./test/run_all.sh # all 15, pass/fail summary
Level 1
Fix a bug
Read existing behaviour, correct it.