fix: reduce invite API per-IP rate limit to 4/24h and key on socket peer IP#74
Merged
Conversation
The freenet.org/quickstart flow mints River invites into the shared "Freenet Official" room via gkapi's POST /create-invite endpoint. The room is being spammed through bulk invite generation, so tighten the per-IP limit from 20 to 2 invites per rolling 24h window. Also add test_rate_limiter_enforces_two_per_window, which hardcodes the intended value. The existing rate-limit tests are parameterized on MAX_INVITES_PER_WINDOW, so they pass at any value and would not catch an accidental bump of the constant (its history shows it drifting 5 -> 20). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B49wBfvR8EjpfYTw5muNV9
Contributor
Author
Review (Light tier)
Local [AI-assisted - Claude] |
Per review on PR #74: - Raise MAX_INVITES_PER_WINDOW from 2 to 4. A few invites per day covers legitimate re-invites while still slowing bulk account creation. - Key the rate limiter on the TCP connection peer address (addr.ip()) instead of trusting X-Forwarded-For / X-Real-IP. gkapi is directly internet-facing (binds :80/:443, own TLS, no reverse proxy), so those headers are fully client-controlled and made the limit bypassable at any threshold by rotating a spoofed header. ConnectInfo<SocketAddr> is already wired via into_make_service_with_connect_info, and addr.ip() yields the bare IpAddr so IPv4/IPv6 peers key correctly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B49wBfvR8EjpfYTw5muNV9
Contributor
Author
Re-review after update (limit → 4, IP source → socket peer)The prior review was on the earlier diff; re-ran on the new HEAD
CI note: the [AI-assisted - Claude] |
sanity
marked this pull request as ready for review
July 16, 2026 14:12
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.
Problem
The
freenet.org/quickstartflow mints River invitations into the sharedFreenet Official room via gkapi's
POST https://gkapi.freenet.org/create-invite.The room is being spammed via bulk invite generation. Two issues made this easy:
X-Forwarded-Forfirst. gkapi is directlyinternet-facing (binds
0.0.0.0:80/:443, terminates its own Let'sEncrypt TLS, no reverse proxy;
gkapi.freenet.orgresolves straight to thehost). With no trusted proxy in front,
X-Forwarded-Foris fullyclient-controlled, so the limit was bypassable at any threshold by
rotating a spoofed header.
Approach
MAX_INVITES_PER_WINDOW = 4in
rust/api/src/rate_limit.rs; 24h window unchanged. 4 covers legitimatere-invites while still slowing bulk creation. The 429 message is updated to
match.
X-Forwarded-For.get_client_ipnow returnsaddr.ip()fromConnectInfo<SocketAddr>(alreadywired via
into_make_service_with_connect_info::<SocketAddr>()inmain.rs)and no longer trusts
X-Forwarded-For/X-Real-IP.addr.ip()is the bareIpAddr(no port), so IPv4/IPv6 peers key correctly. A comment marks the spotto revisit only if a trusted reverse proxy / Cloudflare is ever added (trust
XFF from that proxy's IP only — never blanket XFF trust again).
test_rate_limiter_enforces_four_per_window, which hardcodes theintended value (
4) and asserts the 5th invite from an IP is rejected. Thepre-existing rate-limit tests are parameterized on the constant and would not
catch an accidental bump (history shows it drifting
5 -> 20). Runs in CI viacargo make integration-test(cd rust/api && cargo test).Testing
cargo testinrust/api— all 5rate_limit::testspass, including the new one.Notes
0.0.0.0(IPv4-only) today, so peers are IPv4 in practice; thebare-
IpAddrkeying is still correct if IPv6 binding is ever added.mainon the host (update-from-github.fish);it only takes effect after this merges to main.
[AI-assisted - Claude]
🤖 Generated with Claude Code
https://claude.ai/code/session_01B49wBfvR8EjpfYTw5muNV9