Desktop never verifies event id or signature on the message timeline — channel authenticity rests on TLS to the relay, not the signature
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
Buzz Desktop renders relay-delivered events without ever checking their NIP-01 id hash or
Schnorr signature. Everything a human reads in a channel is authenticated by the TLS
connection to the relay and by the relay's own ingest check — not by the signature that
travels with the event.
This is now asymmetric with the agent runtime: since #7010 (merged 2026-09-04) `buzz-acp`
verifies every EVENT frame before routing, so an agent and the human sitting next to it no
longer agree on what is authentic.
Source state for every line reference below: `origin/main` @ `051c3a270`.
## Evidence
The renderer's relay session dispatches EVENT frames straight into the render buffer:
- `desktop/src/shared/api/relayClientSession.ts:780-782` — `["EVENT", subId, event]` → `handleEvent`
- `desktop/src/shared/api/relayClientSession.ts:841-858` — dedupe, buffer, flush. No id check, no signature check on this path or anything downstream of it.
`verifyEvent` from `nostr-tools/pure` is imported in exactly one file in the renderer:
- `desktop/src/shared/lib/authors.ts:2,44-46`, used at `:92` — and only to decide whether a
relay-signed event's `actor`/`p` attribution tag is allowed to override the signer as the
displayed author.
So the capability is present and already wired for one narrow decision; it is simply not
applied to the messages themselves.
## Verification that does exist elsewhere in Desktop
None of these cover the channel timeline:
| Path | Line | Scope |
|---|---|---|
| Native relay client | `desktop/src-tauri/src/native_relay_client.rs:551-553` | finite (catalog) REQ results only — live/archive subscription events skip it |
| Persona catalog | `desktop/src-tauri/src/persona_catalog.rs:93` | catalog head selection |
| Team catalog | `desktop/src-tauri/src/team_catalog.rs:191` | catalog head selection |
| NIP-OA owner auth tag | `desktop/src-tauri/src/nostr_convert.rs:79` | kind:0 ownership provenance |
| Local archive ingest | `desktop/src-tauri/src/archive/pipeline.rs:122` | `verify_id()` + `verify_signature()` |
## Impact
The relay is the single source of truth for reads (`ARCHITECTURE.md:7`), and it does verify
at ingest (`crates/buzz-relay/src/handlers/ingest.rs:2219`). But a client that never checks
signatures cannot distinguish a relay that is behaving from one that is not:
1. A compromised, malicious, or simply buggy relay can fabricate a message attributed to any
pubkey — including a community owner or an agent's owner — and Desktop renders it as
authentic, with the right avatar and name.
2. Self-hosted and third-party relays are a supported deployment. "Trust your relay operator
completely, for authorship as well as availability" is a much stronger requirement than
the signed-event model implies, and it is nowhere stated in the UI.
3. The agent/human asymmetry cuts both ways. A forged event that `buzz-acp` drops is still
shown to the human, who may then act on it or ask the agent why it ignored an instruction
that — from the human's view — is right there in the channel.
Nothing here is exploitable by an ordinary member: `ingest.rs:2253` requires the event pubkey
to equal the NIP-42 authenticated identity, so member-to-member impersonation is already
blocked at the relay. This issue is specifically about the client having no independent
check.
## Suggested direction
Verify at the transport seam and fail closed, mirroring what `buzz-acp` does:
- Check `id` and `sig` in `relayClientSession.handleEvent` before the event reaches the
dedupe/render buffer, on a worker so the crypto does not block the UI thread; or move
channel traffic onto `native_relay_client`, which already has the check for finite
requests and could apply it to live subscriptions.
- Drop failures rather than render them, and surface a counter/telemetry so a
misconfigured relay is visible rather than silent.
- Consider shipping behind a flag with an "unverified" affordance first, because of the
caveat below.
## One caveat before enabling this
#5616 reports that kind:9 events authored in Buzz Desktop fail third-party NIP-01
verification (id ≠ sha256 of the canonical serialization, and `sig` verifies against neither
the stated nor the recomputed id). If that reproduces, turning on client-side verification
would drop "legitimate" traffic.
Partial check on 2026-09-10 against a hosted relay: the ids of two kind:9 events recompute
exactly from the delivered fields, so that half of #5616 did not reproduce there. The
signature half could not be checked from the CLI because `buzz messages get` strips `sig`
(#6874). Worth settling #5616 first.
Contributor guide
Research direction
Start with #5616 and the event path in desktop/src/shared/api/relayClientSession.ts, especially handleEvent and the dedupe/buffer flow at lines 780-858. Compare this with buzz-acp verification and the existing verifyEvent use in desktop/src/shared/lib/authors.ts. Done means the chosen transport path independently handles valid and invalid event ids and signatures without silently rendering failures, with the kind:9 compatibility caveat resolved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust, typescript
- Domain
- desktop, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100