Inbound Nostr events are dispatched without signature verification #9

Open
opened 2026-10-09 16:47:18 +00:00 by padreug · 0 comments
Owner

NostrEvent.check_signature() (nostr/event.py:31) is never called. process_nostr_message (services.py:515-528) parses the relay payload into a NostrEvent, runs the in-memory dedup on the self-declared event.id, and dispatches on event.kind. Kind 30017/30018 handlers (services.py:905-957) resolve the merchant via get_merchant_by_pubkey(event.pubkey) and insert pending stalls/products with name/price/quantity taken from the event; kind 0 calls update_customer_profile (crud.py:924-938), which updates by public_key alone with no merchant scoping. The NIP-59 path (nostr/nip59.py:174-195 unseal) checks rumor.pubkey == seal.pubkey but never verifies the seal's Schnorr signature, so sender authenticity there rests solely on the NIP-44 MAC.

Impact: the subscription filter only constrains what we ask the relay for; a malicious or compromised relay (nostrclient fans out to whatever relays are configured) can inject stalls/products into any merchant's catalog, overwrite any customer profile, and pre-seed the dedup cache with chosen ids to shadow legitimate events.

Fix direction: call event.check_signature() at the top of process_nostr_message before the dedup check and drop failures with a warning; add seal.check_signature() in unseal. The sandbox fix (sandbox-team/nostrmarket PR #14) is exactly this plus tests and applies to the fork as-is.

Found during reforge run #1 (sandbox nostrmarket#2).

`NostrEvent.check_signature()` (`nostr/event.py:31`) is never called. `process_nostr_message` (`services.py:515-528`) parses the relay payload into a `NostrEvent`, runs the in-memory dedup on the self-declared `event.id`, and dispatches on `event.kind`. Kind 30017/30018 handlers (`services.py:905-957`) resolve the merchant via `get_merchant_by_pubkey(event.pubkey)` and insert pending stalls/products with name/price/quantity taken from the event; kind 0 calls `update_customer_profile` (`crud.py:924-938`), which updates by `public_key` alone with no merchant scoping. The NIP-59 path (`nostr/nip59.py:174-195` `unseal`) checks `rumor.pubkey == seal.pubkey` but never verifies the seal's Schnorr signature, so sender authenticity there rests solely on the NIP-44 MAC. Impact: the subscription filter only constrains what we ask the relay for; a malicious or compromised relay (nostrclient fans out to whatever relays are configured) can inject stalls/products into any merchant's catalog, overwrite any customer profile, and pre-seed the dedup cache with chosen ids to shadow legitimate events. Fix direction: call `event.check_signature()` at the top of `process_nostr_message` before the dedup check and drop failures with a warning; add `seal.check_signature()` in `unseal`. The sandbox fix (sandbox-team/nostrmarket PR #14) is exactly this plus tests and applies to the fork as-is. Found during reforge run #1 (sandbox nostrmarket#2).
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/nostrmarket#9
No description provided.