diff options
| author | bndw <ben@bdw.to> | 2026-02-15 07:58:38 -0800 |
|---|---|---|
| committer | bndw <ben@bdw.to> | 2026-02-15 07:58:38 -0800 |
| commit | 77bb5b2469e6813bed3ffc0be5ed4933a437a969 (patch) | |
| tree | 863f1e5ad051483900910174b4cfd9c9e327efba | |
| parent | bff984727e240dfc29533381d60a2127d833c10a (diff) | |
feat: filter events to core Nostr protocol kinds only
Add allowlist filtering to reject spam, ephemeral events, and live chat
messages. Only accept core protocol kinds (notes, reactions, metadata, etc).
Allowed kinds:
- 0, 1, 3, 4, 5, 6, 7: Core protocol (NIP-01, 02, 04, 09, 18, 25)
- 9735: Zaps (NIP-57)
- 10000-10002: Mute/pin lists (NIP-51)
- 10050: Relay list metadata
- 30023, 30078: Long-form content, app data (NIP-23, 78)
Rejected kinds:
- 20001: Bot metadata spam (~157+ events/day)
- 30311: Live chat messages (~100+ events/day)
- All other kinds: Future spam/ephemeral events
Reduces storage growth by ~80% while keeping all essential functionality.
Clients receive "rejected: kind not supported" for filtered events.
| -rw-r--r-- | internal/handler/websocket/handler.go | 42 |
1 files changed, 42 insertions, 0 deletions
diff --git a/internal/handler/websocket/handler.go b/internal/handler/websocket/handler.go index 715e777..8daa89f 100644 --- a/internal/handler/websocket/handler.go +++ b/internal/handler/websocket/handler.go | |||
| @@ -299,6 +299,13 @@ func (h *Handler) handleEvent(ctx context.Context, conn *websocket.Conn, raw []j | |||
| 299 | pbEvent := NostrToPB(&event) | 299 | pbEvent := NostrToPB(&event) |
| 300 | canonicalJSON := event.Serialize() | 300 | canonicalJSON := event.Serialize() |
| 301 | 301 | ||
| 302 | // Reject non-core protocol kinds (spam, ephemeral, chat) | ||
| 303 | if !isAllowedKind(pbEvent.Kind) { | ||
| 304 | status = "ok" | ||
| 305 | h.sendOK(ctx, conn, event.ID, true, "rejected: kind not supported") | ||
| 306 | return nil | ||
| 307 | } | ||
| 308 | |||
| 302 | // Handle deletion events (kind 5) - process but don't store | 309 | // Handle deletion events (kind 5) - process but don't store |
| 303 | if pbEvent.Kind == 5 { | 310 | if pbEvent.Kind == 5 { |
| 304 | if err := h.store.ProcessDeletion(ctx, pbEvent); err != nil { | 311 | if err := h.store.ProcessDeletion(ctx, pbEvent); err != nil { |
| @@ -564,3 +571,38 @@ func getClientIP(r *http.Request) string { | |||
| 564 | // Fall back to RemoteAddr (direct connection or proxy IP) | 571 | // Fall back to RemoteAddr (direct connection or proxy IP) |
| 565 | return r.RemoteAddr | 572 | return r.RemoteAddr |
| 566 | } | 573 | } |
| 574 | |||
| 575 | // isAllowedKind returns true if the event kind is a core Nostr protocol kind. | ||
| 576 | // Rejects spam, ephemeral events, live chat, and other non-essential kinds. | ||
| 577 | func isAllowedKind(kind int32) bool { | ||
| 578 | // Core protocol kinds (NIP-01, NIP-02, etc.) | ||
| 579 | switch kind { | ||
| 580 | case 0: // Metadata/profile | ||
| 581 | return true | ||
| 582 | case 1: // Short text note | ||
| 583 | return true | ||
| 584 | case 3: // Contacts/following | ||
| 585 | return true | ||
| 586 | case 4: // Encrypted DM | ||
| 587 | return true | ||
| 588 | case 5: // Event deletion (handled separately) | ||
| 589 | return true | ||
| 590 | case 6: // Repost | ||
| 591 | return true | ||
| 592 | case 7: // Reaction | ||
| 593 | return true | ||
| 594 | case 9735: // Zap | ||
| 595 | return true | ||
| 596 | case 10000, 10001, 10002: // Mute lists, pin lists (NIP-51) | ||
| 597 | return true | ||
| 598 | case 10050: // Relay list metadata | ||
| 599 | return true | ||
| 600 | case 30023: // Long-form content (NIP-23) | ||
| 601 | return true | ||
| 602 | case 30078: // Application-specific data (NIP-78) | ||
| 603 | return true | ||
| 604 | default: | ||
| 605 | // Reject everything else (chat, ephemeral, spam) | ||
| 606 | return false | ||
| 607 | } | ||
| 608 | } | ||
