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:
- 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.
- 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.
- 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.
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-acpverifies 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]→handleEventdesktop/src/shared/api/relayClientSession.ts:841-858— dedupe, buffer, flush. No id check, no signature check on this path or anything downstream of it.verifyEventfromnostr-tools/pureis 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 arelay-signed event's
actor/pattribution tag is allowed to override the signer as thedisplayed 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:
desktop/src-tauri/src/native_relay_client.rs:551-553desktop/src-tauri/src/persona_catalog.rs:93desktop/src-tauri/src/team_catalog.rs:191desktop/src-tauri/src/nostr_convert.rs:79desktop/src-tauri/src/archive/pipeline.rs:122verify_id()+verify_signature()Impact
The relay is the single source of truth for reads (
ARCHITECTURE.md:7), and it does verifyat ingest (
crates/buzz-relay/src/handlers/ingest.rs:2219). But a client that never checkssignatures cannot distinguish a relay that is behaving from one that is not:
pubkey — including a community owner or an agent's owner — and Desktop renders it as
authentic, with the right avatar and name.
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.
buzz-acpdrops is stillshown 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:2253requires the event pubkeyto 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-acpdoes:idandsiginrelayClientSession.handleEventbefore the event reaches thededupe/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 finiterequests and could apply it to live subscriptions.
misconfigured relay is visible rather than silent.
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
sigverifies against neitherthe 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 getstripssig(#6874). Worth settling #5616 first.