Files
ngit-grasp/docs/reference/relay-limits.md
T
DanConwayDev aa543d8051 feat(relay): make discoverable hardening limits explicit
Motivation: rust-nostr 0.45 introduced a broad local-relay hardening series
whose effective defaults were mostly absent from its changelog and entirely
absent from ngit-grasp's custom NIP-11 response. One new 64 KiB event bound is
incompatible with production history containing a valid roughly 149 KiB
NIP-34 patch event. Leaving other defaults implicit risks another dependency
upgrade silently changing serving policy, while exposing every internal knob
would create configuration that peers cannot usefully negotiate.

Approach: explicitly select every retained rust-nostr hardening value in the
builder. Expose only the subscription and per-filter result limits that peers
can discover and ngit-grasp sync already consumes, plus the Git-specific event
size policy. Apply one filter-limit option consistently to explicit, query,
and omitted-limit caps. Raise the event default from 64 KiB to 192 KiB and
validate it remains beneath the fixed 5 MiB WebSocket message ceiling. Publish
the standard NIP-11 limitation fields and correct the architecture/reference
documentation, including removal of the obsolete max_filters claim.

Correctness: the NIP-11 max_subscriptions value feeds the existing per-session
subscription ledger; default_limit feeds adaptive pagination with its existing
verification-page safeguard. max_limit is advertised truthfully but is not
misused as an omitted-filter promise. The 192 KiB bound clears the observed
patch by about 29% while preserving a finite allocation boundary. All three
new options are synchronized across source, reference docs, NixOS, and the
environment example.

Excluded scope: message-size negotiation, filter-payload limits, per-IP
fairness, and non-standard NIP-11 extensions remain separate work. Rate,
handshake, subscription-memory, filter-count, and negentropy bounds are pinned
but deliberately not operator-configurable because our sync cannot negotiate
them through standard NIP-11 fields.

Validation: focused unit tests passed for explicit configuration defaults,
event/WebSocket size validation, configured NIP-11 advertisement, and the full
http::nip11::tests module (10 tests before adding the focused override case).
The previously validated full library suite and deployment build were not
repeated at the user's request.
2026-08-07 07:44:31 +00:00

2.6 KiB

Embedded relay limits

ngit-grasp embeds rust-nostr LocalRelay 0.45.0. The application selects every effective limit explicitly so future dependency defaults cannot silently alter production admission policy.

Effective limits

Limit ngit-grasp default Operator configuration NIP-11
Total inbound connections Unbounded NGIT_MAX_CONNECTIONS No standard field
Active REQs per connection 500 NGIT_RELAY_MAX_SUBSCRIPTIONS max_subscriptions
Results per filter 500 NGIT_RELAY_FILTER_LIMIT max_limit, default_limit
Serialized event size 192 KiB NGIT_RELAY_MAX_EVENT_SIZE_BYTES No equivalent field
Event writes per minute 60 Fixed No standard field
Queries per minute 120 Fixed No standard field
Authentication events per minute 30 Fixed No standard field
Text messages per minute 300 Fixed No standard field
WebSocket message size 5 MiB Fixed max_message_length
Handshake deadline 10 seconds Fixed No standard field
Subscription-ID length 250 bytes Fixed max_subid_length
Filters per REQ 20 Fixed No standard field
Subscription state per connection 1 MiB Fixed No standard field
Active negentropy sessions per connection 10 Fixed No standard field
Negentropy items per connection 50,000 Fixed No standard field
Negentropy frame 60,000 bytes Fixed upstream No standard field

The filter setting is applied consistently to rust-nostr's explicit filter cap, per-query result cap, and omitted-limit default. Limits are per filter; results from multiple filters in one REQ are merged without an aggregate truncation.

The 192 KiB event default is three times rust-nostr's new 64 KiB default. Production history contains a valid NIP-34 patch event of about 149 KiB, so 64 KiB is incompatible with ngit-grasp's purpose. The raised limit remains bounded and below the 5 MiB WebSocket message ceiling.

Client adaptation

ngit-grasp refetches NIP-11 per connection session. Its outbound sync ledger uses max_subscriptions, falling back conservatively when absent. Adaptive historic pagination uses default_limit with a verification page and learns from raw delivered page sizes. max_limit describes explicit filter limits; historic sync currently omits limit, so it does not treat max_limit as an omitted-filter page-size promise.

NIP-11 has no standard max_filters field in the current schema. Filter-count and serialized-message budgets therefore remain conservative client-side constants rather than falsely negotiated capabilities.