DM open reuses a three-participant conversation for a two-participant request
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
On a hosted Buzz community relay, opening a one-to-one DM between a human and a managed agent returns an existing three-participant DM that also contains a second managed agent.
This prevents the human from creating an isolated one-to-one conversation and can expose messages to an unintended existing participant.
All identifiers, participant names, relay hostname, and message content are redacted below.
## Environment
- Buzz Desktop: `0.4.26` on macOS
- Relay: hosted `*.communities.buzz.xyz` deployment; NIP-11 advertises version `0.2.0`
- CLI: bundled `buzz` CLI from the same Desktop installation
- Participants:
- human owner
- managed agent A (intended recipient)
- managed agent B (unintended participant)
## Observed state
The existing DM's live member list contains:
```text
human owner role=member
managed agent A role=member
managed agent B role=bot
```
From agent A, explicitly opening a DM with only the human returns the same existing DM with `created: false` rather than creating or resolving an exact two-participant conversation:
```text
buzz dms open --pubkey
# accepted=true, created=false, dm_id=
```
The discovery state is also inconsistent:
```text
buzz dms list
# []
buzz channels list
# includes the existing DM channel
```
Repeated attempts from Buzz Desktop to select only agent A also resolve to the same three-participant DM.
## Steps to reproduce
The original sequence that produced the inconsistent row is not known, but the bad state is reliably observable once present:
1. Have an existing DM whose active members are the human, managed agent A, and managed agent B.
2. As agent A, request a DM with only the human:
```text
buzz dms open --pubkey
```
3. Observe `created: false` and the existing three-member DM ID.
4. Query the returned channel's members.
5. Observe that agent B remains an active participant even though agent B was not in the requested participant set.
6. Try Buzz Desktop's new-message composer, selecting only agent A; observe that it resolves to the same group DM.
## Expected behavior
DM lookup must use the exact active participant set. Requesting `{human, agent A}` must never return a channel whose active members are `{human, agent A, agent B}`.
If no exact two-participant DM exists, Buzz should create one.
## Actual behavior
A three-participant DM is reused for a two-participant request. The human cannot establish an isolated one-to-one DM with agent A.
## Impact
- Confidentiality boundary failure: a participant omitted from the request remains able to access the returned conversation.
- One-to-one agent conversations cannot be reliably created.
- Users may believe they selected a single recipient while the returned conversation has a broader audience.
- Current workaround is to create a private stream and verify its member list manually.
## Source-level mismatch
Current `main` appears to require exact set matching:
- `crates/buzz-db/src/dm.rs` computes a stable SHA-256 hash from the sorted, deduplicated participant pubkeys and looks up a DM by that hash.
- The same file documents DM participant sets as immutable.
- `crates/buzz-relay/src/handlers/command_executor.rs` says adding a member creates a new DM.
- `desktop/src/features/messages/ui/NewMessageScreen.tsx` sends only the explicitly selected recipient pubkeys.
The live behavior conflicts with that contract. A likely explanation is a legacy/stale `participant_hash`, an older deployed mutation path, or a row whose hash no longer matches its active member set. Database inspection is required to distinguish these possibilities.
## Suggested safeguards
1. Before returning a DM found by `participant_hash`, verify that its current active member set exactly equals the requested set.
2. Add a migration/admin repair that recomputes or validates participant hashes for existing DMs and identifies collisions or stale rows.
3. Add a regression test where a stale/legacy DM row has a two-party hash but three active members; `open_dm` must not return it for a two-party request.
4. Preserve the invariant that adding participants creates a new DM rather than mutating the membership/hash of an existing DM.
5. Consider surfacing the resolved participant list in the `dms open` response so clients can fail closed on an audience mismatch.
## Related
- #2897 reports `buzz dms list` returning `[]` for an active participant. That discovery defect is also present here, but this issue is distinct: the resolved DM's active audience does not match the requested participant set.
Contributor guide
Assessment
This issue has not been assessed yet.