You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
relay: NIP-11 advertises max_limit 10,000 but enforces 1,000 — silent truncation for clients that trust it #3757
The relay advertises max_limit: 10_000 in its NIP-11 document but enforces an effective websocket page limit of 1,000 — a 10x discrepancy. NIP-11 defines the field as "the relay server will clamp each filter's limit value to this number… with this number, you can know that there are additional results" (11.md:129), so a client that trusts it can silently lose data.
The three layers
Layer
Value
Source
NIP-11 advertised max_limit
10,000
crates/buzz-relay/src/nip11.rs:106
WS REQ clamp
2,000
crates/buzz-relay/src/handlers/req.rs:25 (MAX_HISTORICAL_LIMIT), applied at :881-882
DB clamp
1,000
crates/buzz-db/src/event.rs:347 — let clamp = q.max_limit.unwrap_or(1000)
EventQuery::max_limit is left None on the websocket REQ path (it is set only by the COUNT fallback, req.rs:755), so the DB default of 1,000 applies. Effective page limit is min(requested, 2000, 1000) = 1,000.
The HTTP bridge POST /query path is the same: it builds its query through crate::handlers::req::build_event_query_from_filter (crates/buzz-relay/src/api/bridge.rs:1215) and likewise leaves max_limit unset.
Why it matters
A paginating client that reads NIP-11 and asks for limit: 10000 receives 1,000 events. Seeing a page an order of magnitude shorter than the limit it requested and than the cap the relay advertised, it concludes the query is exhausted — and silently drops everything older. There is no error and no truncation signal.
This is the exact failure mode that NIP-MP's pagination contract (#3163) names as condition 2: "The relay exposes the exact effective page limit it enforces… If the advertised cap differs from the enforced one, 'shorter than the effective limit' is undecidable by the client." Writing that condition down is what surfaced this.
Suggested fix
Advertise what is enforced. Either set NIP-11 max_limit to the true effective value, or derive it from the same constant the enforcing paths use so the two cannot drift again. The second is preferable — a hardcoded 10_000 next to a MAX_HISTORICAL_LIMIT of 2_000 and a DB default of 1_000 is three sources of truth for one number.
Note the two enforced clamps are themselves redundant: the WS clamp of 2,000 can never bind, because the DB clamp of 1,000 is always tighter on that path.
Found while reviewing #3163 (NIP-MP). Not a blocker for that PR — its spec text is scoped correctly and does not claim Buzz satisfies the contract — but it is a live silent-data-loss path for any client that trusts the advertised limit.
Heads-up: #3635 (fix(relay): align NIP-11 max_limit with REQ ceiling, open since 2026-07-29) already implements this – it landed in the queue about 17 hours before this issue was filed, which is probably why neither of you found the other.
It does what the "Suggested fix" section here asks for: it introduces buzz_db::DEFAULT_MAX_PAGE_LIMIT and derives NIP-11 limitation.max_limit from it, so the advertised and enforced caps can't drift. It also covers the two things this issue notes in passing – the redundant MAX_HISTORICAL_LIMIT path in handlers/req.rs, and the POST /query bridge.
It doesn't carry a Fixes #3757 line, so this issue won't auto-close when that PR merges.
For what it's worth, I reproduced the discrepancy independently on main @ bf139e8 before finding that PR – the three values are still nip11.rs:106 = 10_000, handlers/req.rs:25 = 2_000, and buzz-db/src/event.rs:358 = 1_000 – so the report holds and the fix in #3635 looks like the right shape.
Activity
dpetrakov commentedon Jul 30, 2026
Heads-up: #3635 (
fix(relay): align NIP-11 max_limit with REQ ceiling, open since 2026-07-29) already implements this – it landed in the queue about 17 hours before this issue was filed, which is probably why neither of you found the other.It does what the "Suggested fix" section here asks for: it introduces
buzz_db::DEFAULT_MAX_PAGE_LIMITand derives NIP-11limitation.max_limitfrom it, so the advertised and enforced caps can't drift. It also covers the two things this issue notes in passing – the redundantMAX_HISTORICAL_LIMITpath inhandlers/req.rs, and thePOST /querybridge.It doesn't carry a
Fixes #3757line, so this issue won't auto-close when that PR merges.For what it's worth, I reproduced the discrepancy independently on
main@ bf139e8 before finding that PR – the three values are stillnip11.rs:106= 10_000,handlers/req.rs:25= 2_000, andbuzz-db/src/event.rs:358= 1_000 – so the report holds and the fix in #3635 looks like the right shape.fix(relay): align NIP-11 max_limit with REQ ceiling (#3635)
fix(relay): align NIP-11 max_limit with REQ ceiling (block#3635)
fix(relay): align NIP-11 max_limit with REQ ceiling (block#3635)