mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 15:31:38 +02:00
fix(relay): harden admission (fail-safe, loopback-only, bounded, size cap) [review]
Adversarial review of the relay admission surface: - FAIL-SAFE on admission death: nostr-rs-relay fails OPEN if its gRPC admission server is unreachable, so a dead admission = an unrestricted relay. The backend now EXITS if the admission service stops (supervisor restarts a clean pair); documented in RELAY.md. Pin the relay image to a digest (compose note). - Registration-oracle protection: validate() now refuses a non-loopback RELAY_ADMISSION_BIND unless RELAY_ADMISSION_ALLOW_PUBLIC=true (+ test). - No N+1 on crafted events: recipient p-tags are capped (20, deduped) and resolved in ONE batched query (db.any_registered_npub) instead of one-per-tag. - Defence-in-depth size cap: admission denies events over max_event_bytes before any DB work (the relay enforces its own limit too). - Docs: RELAY.md Security section (fail-open, loopback oracle, default PoW=0 spam footgun, client sender-verification); config.toml relay port 8088 (not 8080); Docker Hub image; .env.example RELAY_ADMISSION_ALLOW_PUBLIC. Note: the client-side sender-impersonation blocker is fixed separately in the monorepo (verifying gift-wrap unwrap).
This commit is contained in:
@@ -78,6 +78,28 @@ author allowlist. Only the `inbox-outbox` external-inbound branch is gated.
|
||||
event → rejected (`inbox-outbox`); an under-PoW cross-ecosystem event →
|
||||
rejected (when `RELAY_INBOUND_POW_BITS > 0`).
|
||||
|
||||
## Security notes (read before exposing a public relay)
|
||||
|
||||
- **The relay fails OPEN if admission is unreachable.** `nostr-rs-relay` treats an
|
||||
unreachable gRPC admission server as PERMIT. So a down/crashed admission service
|
||||
= an unrestricted relay. Two defences: (1) the backend **exits** if its admission
|
||||
service stops (so your supervisor restarts a clean pair — don't run them
|
||||
independently), and (2) run the relay + backend as one supervised unit so the
|
||||
relay isn't serving while admission is down. Pin the relay image to a **digest**,
|
||||
not `:latest` — the fail-open semantics can change across versions.
|
||||
- **The admission socket is a registration oracle** (it answers "is this npub
|
||||
registered?"). It MUST stay loopback; `validate()` refuses a non-loopback
|
||||
`RELAY_ADMISSION_BIND` unless you set `RELAY_ADMISSION_ALLOW_PUBLIC=true` and
|
||||
firewall it yourself.
|
||||
- **Default `RELAY_INBOUND_POW_BITS=0` has no spam friction.** Under `inbox-outbox`
|
||||
a `p` tag is public, so anyone can address gift-wraps to a registered user's
|
||||
inbox. A PUBLIC operator should raise the PoW bits (e.g. 8–16) to blunt inbox
|
||||
flooding; content is always encrypted, so this is a storage/spam concern, not a
|
||||
confidentiality one.
|
||||
- **Clients verify the sender.** A gift-wrap's inner author is only trusted after
|
||||
the wallet checks the seal signature + `seal.pubkey == rumor.pubkey`, so a
|
||||
relay/sender cannot forge the displayed "from".
|
||||
|
||||
## Modularity
|
||||
|
||||
`nostr-rs-relay` is the first adapter behind the `RelayProvider` seam
|
||||
|
||||
Reference in New Issue
Block a user