Per-IP unlock rate limiting via X-Real-IP #161
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lz/agent-nexus#161
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up to #149 / PR #160, which shipped a global-only limiter. This adds the per-IP key that #110 item 1 also asked for, by a route that PR #160 did not consider.
What #160 established, and where its conclusion goes too far
PR #160 concluded that per-IP is mutually exclusive with the worker callbacks. The blocker it found is real — I verified it in
@sveltejs/adapter-node@5.5.4(files/handler.js):address_headeris read once at module scope — process-global, no per-route form — andgetClientAddress()throws when the named header is absent. So enabling it for/unlockwould break the four/api/agent/*callbacks, which reach host-mode Nexus directly over the workers bridge carrying no such header, taking the bridge-IP guard (fact #17) with them.But that proves
ADDRESS_HEADERis unusable, not that per-IP is impossible. The mutual exclusivity is a property of the adapter's mechanism, not of the problem.The route not taken
DEPLOYMENT.mdalready documents:proxy_set_headeroverwrites rather than appends, so via nginx that header is the real TCP peer and is not client-forgeable. The unlock route can readrequest.headers.get('x-real-ip')directly, scoped to that one route, never touchinggetClientAddress()and leaving the agent callbacks completely alone.(Note this is why the same trick does not work for
X-Forwarded-For: DEPLOYMENT.md does not set it at all, so under the documented config it is whatever the caller sent.)Why it is worth doing
It fixes the exact weakness PR #160 names in its own body:
Per-IP removes that for the internet-facing case, which is the case that motivated #149.
The honest caveat
Nexus runs
network_mode: hoston port 3001, so it is also directly reachable on the LAN, bypassing nginx. A LAN client can forgeX-Real-IP. That is a weaker threat model — the attacker is already on the LAN — and the global counter still covers it.So the design is: per-IP when a trustworthy header is present, global always. Not per-IP instead of global.
Work
x-real-ipin the unlock route only. Do not setADDRESS_HEADER, and do not route this throughgetClientAddress().a.lck.shand flagged that its conclusions rest on the documented config. If the live config differs, DEPLOYMENT.md is stale and should be corrected in the same PR.Tests
PR #160 already ships a guard that forges a different
X-Forwarded-Foron every request, so a naive left-most per-IP key goes red. Keep it. Add the equivalent forX-Real-IP: prove that a request reaching Nexus directly (no proxy) cannot mint an arbitrary key.Repo standard: break the thing each test protects and confirm it goes red before counting it.