block / block/buzz

DM open reuses a three-participant conversation for a two-participant request

Open
#3,405 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.