Skip to content

feat(buzz-acp): --allow-dm-allowlist opt-in for explicit allowlist in DMs - #5013

Open
joe-rodgers wants to merge 1 commit into
block:mainfrom
joe-rodgers:acp-dm-allowlist-only
Open

joe-rodgers wants to merge 1 commit into
block:mainfrom
joe-rodgers:acp-dm-allowlist-only

Conversation

@joe-rodgers

Copy link
Copy Markdown

What

The inbound author gate hardens DMs: because clients auto-p-tag every DM participant, every DM message looks like a mention, so inside a DM only the owner and cryptographically verified same-owner siblings can fire a turn. Neither --respond-to=allowlist nor --respond-to=anyone applies there.

That default is right, but it silently breaks a legitimate case: an operator puts a real person on --respond-to-allowlist, that person can talk to the agent in a stream channel, and then every DM they send is dropped with no turn and no error. It is indistinguishable from a broken agent.

This adds --allow-dm-allowlist / BUZZ_ACP_ALLOW_DM_ALLOWLIST (default false). When set and --respond-to=allowlist, the explicit pubkey list also admits authors inside DMs.

Security tradeoff, deliberately scoped

  • Only RespondTo::Allowlist is affected. RespondTo::Anyone in a DM stays owner/sibling-only even with the flag on — allowing it would restore the original transitive-grant hole, where anyone who lands in a DM with the agent (including a DM the agent itself was asked to open with a third party) could prompt it. RespondTo::Nobody still drops everything, including the owner.
  • Admission remains gated on a list the operator wrote by hand, so merely being in a DM with the agent grants nothing.
  • What is being accepted: an allowlisted pubkey can now prompt the agent in a 1:1 DM, with no channel membership around it — no other participants, no shared audit surface. Default-off, so existing deployments are unchanged.

Threaded through CliArgsConfigauthor_allowed and the setup-mode gate. Summary line reports dm_allowlist=on when active.

Testing

Existing DM cases now pin the flag-OFF default explicitly. Added coverage for flag-ON admitting an allowlisted pubkey, still rejecting an unlisted stranger, not loosening anyone/nobody/owner-only, and still admitting owner+sibling.

cargo test -p buzz-acp --release: 675 passed, 0 failed. cargo fmt --check clean.

Context

Found while running a small fleet where a second trusted principal is on the allowlist — her DMs to the agents vanished with no error, which reads exactly like a dead agent.

… DMs

The inbound author gate hardens DMs: because clients auto-p-tag every DM
participant, every DM message looks like a mention, so inside a DM only the
owner and cryptographically verified same-owner siblings can fire a turn.
Neither --respond-to=allowlist nor --respond-to=anyone applies there.

That default is right, but it silently breaks a legitimate case: an operator
puts a real person on --respond-to-allowlist, that person can talk to the
agent in a stream channel, and then every DM they send is dropped with no
turn and no error. It is indistinguishable from a broken agent.

Add --allow-dm-allowlist / BUZZ_ACP_ALLOW_DM_ALLOWLIST (default false). When
set AND --respond-to=allowlist, the explicit pubkey list also admits authors
inside DMs.

Security tradeoff, deliberately scoped:

- Only RespondTo::Allowlist is affected. RespondTo::Anyone in a DM stays
  owner/sibling-only even with the flag on -- allowing it would restore the
  original transitive-grant hole, where anyone who lands in a DM with the
  agent (including a DM the agent itself was asked to open with a third
  party) could prompt it. RespondTo::Nobody still drops everything,
  including the owner.
- Admission remains gated on a list the operator wrote by hand, so merely
  being in a DM with the agent grants nothing.
- What is being accepted: an allowlisted pubkey can now prompt the agent in
  a 1:1 DM, with no channel membership around it -- no other participants,
  no shared audit surface. Default-off, so existing deployments are
  unchanged.

Threaded through CliArgs -> Config -> author_allowed and the setup-mode gate.
Summary line reports " dm_allowlist=on" when active.

Tests: existing DM cases now pin the flag-OFF default explicitly; added
coverage for flag-ON admitting an allowlisted pubkey, still rejecting an
unlisted stranger, not loosening anyone/nobody/owner-only, and still
admitting owner+sibling.
@joe-rodgers
joe-rodgers requested a review from a team as a code owner August 6, 2026 04:37
@VIPKaiser

Copy link
Copy Markdown

Validating the exact scenario this unlocks, from the deployment that motivated it (2026-08-06):

Owner created an agent for a family member and put her on respond_to_allowlist via Buzz Desktop (the UI presents it as "who may talk to this agent"). She could talk to the agent in channels, but every DM she sent was silently dropped — including one with a correct p-tag. Same-hour discriminator: a same-owner sibling agent's p-tagged probe DM created a session and got a reply in 9 seconds, isolating the cause to the is_dm branch of author_allowed. Zero INFO-level log lines on any drop; she concluded the agent didn't exist.

Timeline: 19:38Z DM (no p-tag) dropped · 20:02Z DM (p-tag present) dropped · 20:14Z DM (p-tag) dropped · 20:15Z sibling probe DM → session + reply in 9s.

An opt-in flag is the right shape — keeps the transitive-grant exploit closed while letting owners who explicitly vetted someone use DMs. The other thing worth pairing with this: raise the drop log from debug! to info!/warn! so operators can see rejections without a debugger; right now the gate is invisible in production.

@BrianArbuckle

Copy link
Copy Markdown

Adding a second production deployment that hit this, with the evidence, plus two small asks that hold whether or not the flag lands.

Setup (all verified from the host, not the UI): Buzz Desktop desktop-v0.5.23 on macOS 14.7.1 running three managed agents (codex-acp 1.10.0) owned by an always-on Mac mini account; self-hosted relay ghcr.io/block/buzz:0.2.0. Three humans on respond_to_allowlist, set from the Desktop "Respond to" picker. ps -E on each harness pid shows BUZZ_ACP_RESPOND_TO=allowlist and BUZZ_ACP_RESPOND_TO_ALLOWLIST=<the three pubkeys>; the startup line says respond_to=allowlist(3).

Symptom: every DM from an allowlisted human is dropped. Presence online, no reaction, no reply, and nothing in the agent log at the default RUST_LOG=buzz_acp=info. The relay logs Event ingested for the DM, so delivery is not the problem.

Isolation: with RUST_LOG=buzz_acp::inbound_author_gate=debug added, the same DM produces

DEBUG buzz_acp::inbound_author_gate: inbound author gate - dropping event channel_id=<dm channel> raw_author=<allowlisted human> effective_author=<same> mode=allowlist is_dm=true

and the same author, same agent, same minute, in a private stream channel: turn starting, turn complete: end_turn, reply published 11 s later. The delta is exactly the is_dm branch of author_allowed, as this PR describes.

What made it hard to see: the drop is logged at debug level under the desktop's default info filter, the Desktop picker says "Selected people" without mentioning that direct messages stay owner-only, and the buzz-acp README's Modes table says allowlist forwards "the listed pubkeys plus the owner", with the DM exception from #2591 not mentioned there. We went through the allowlist not being saved, the mention filter, subscribe=all, and host resources before reading author_allowed; each was a reasonable reading of the docs.

Support for the opt-in shape. Anyone staying owner-only in DMs is right; a list the owner typed is a different thing, and this is the case #2591 itself left as an open product question.

Two asks regardless of the flag:

  1. Log the DM drop at warn or info level, once per author and channel, as @VIPKaiser suggested above, so the gate is visible in production.
  2. Mention the DM exception where operators look: the Modes table in crates/buzz-acp/README.md, and the Desktop "Respond to" field help for Selected people / Anyone.

Our workaround until this merges, for anyone landing here: one private stream channel per person and agent, and --subscribe config with a rules file that sets require_mention = false for those channels (agents only subscribe to channels they are members of, so one shared file works for all of them).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants