Skip to content

fix(acp): reject unattended permission requests - #4609

Merged
wesbillman merged 3 commits into
mainfrom
codex/security-acp-shell-auto-approval
Aug 5, 2026
Merged

fix(acp): reject unattended permission requests#4609
wesbillman merged 3 commits into
mainfrom
codex/security-acp-shell-auto-approval

Conversation

@jmecom

@jmecom jmecom commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

This change removes the ACP permission-bypass mode, defaults managed sessions to dontAsk, and answers permission requests with reject_once or cancellation in both ACP read loops.

Unattended operations that require interactive approval now fail closed instead of being silently authorized. Explicit non-interactive modes that do not bypass a permission request remain available.

Both layers have to change together: apply_permission_mode treats an unsupported mode and a failed set_config_option as non-fatal by design, so a request can still reach the harness even in a non-interactive mode. Removing bypassPermissions from the enum rather than only changing the default means the mode cannot be restored by configuration alone.

The scope of the guarantee is that buzz-acp never grants approval. An agent that pre-authorizes tools in its own configuration (for example Claude Code's settings.json) still runs them without asking, which is outside this harness.

Testing

  • env -u BUZZ_ACP_LAZY_POOL bin/cargo test -p buzz-acp at 16fff4d: 671 library tests and 9 integration tests passed
  • cargo clippy -p buzz-acp --all-targets -- -D warnings and cargo fmt -p buzz-acp -- --check: clean
  • git diff --check origin/main...codex/security-acp-shell-auto-approval

The permission tests previously re-implemented the reject_once lookup in the test body instead of calling the code under test, so they would have passed unchanged if the harness went back to selecting allow_once. They could not call it directly, because handle_permission_request is a method on AcpClient, which owns a live Child and its stdio pipes. The choice is now a free function, permission_denial_response, and the tests exercise it: reject_once preferred over offered allow options, the cancelled fallback when no reject_once exists, an empty option list, and a reject_once missing its optionId. The cancelled fallback had no coverage before despite being the fail-closed backstop.

Operator notes

  • BUZZ_ACP_PERMISSION_MODE=bypassPermissions no longer parses, so a process configured with it fails to start rather than silently downgrading.
  • Desktop managed agents do not set a permission mode, so they inherit dontAsk. The desktop has no permission prompt, so operations needing approval now fail with no in-app way to approve them.

Originating Buzz thread: buzz://message?channel=3928fe05-df61-4b5d-b9c7-d623b9b10ea1&id=3c6c02312f763fbe0d2bfc33a6c1a362f91d0354f3d18b039cf7a0558c1439d1

jmecom added 2 commits August 3, 2026 13:44
Default managed sessions to dontAsk and reject or cancel permission prompts instead of selecting allow_once. Explicit owner-selected non-interactive modes remain available.

Co-authored-by: Jordan Mecom <jm@squareup.com>
Signed-off-by: Jordan Mecom <jm@squareup.com>
Co-authored-by: Jordan Mecom <jm@squareup.com>
Signed-off-by: Jordan Mecom <jm@squareup.com>
@jmecom
jmecom marked this pull request as ready for review August 3, 2026 21:00
@jmecom
jmecom requested a review from a team as a code owner August 3, 2026 21:00
The permission tests re-implemented the `reject_once` lookup in the test
body rather than calling the code under test, so they would all still
pass if the harness went back to selecting `allow_once`. They could not
call it directly: `handle_permission_request` is a method on `AcpClient`,
which owns a live `Child` and its stdio pipes.

Extract the choice into `permission_denial_response` and point the tests
at it. No behaviour change. This covers the cancelled fallback, which had
no test despite being the fail-closed backstop for adapters that offer no
`reject_once`, plus the empty-option-list and missing-`optionId` edges.

Also drops `find_allow_once_returns_none_when_absent`, which asserted a
property of a search no production path performs any more.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Eli Foster <efoster@squareup.com>

@elifoster-block elifoster-block left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - expanded test coverage and removed an unused function.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security review by Carl, acting on Wes’s behalf.

I found no blocking issue at head 16fff4dac4370e1a260739d59cdf7b1dda124e4e.

The patch closes both authorization paths that mattered: it removes bypassPermissions from the accepted configuration entirely and changes both ACP response loops to route session/request_permission through one fail-closed decision. That decision selects only an offered reject_once; if none exists it returns the protocol-level cancelled outcome. It never selects allow_once or allow_always, and malformed reject options tear the turn down rather than approving.

The default is now dontAsk. If an adapter does not advertise/support that mode, the harness still remains safe because every interactive request is rejected locally. Explicit modes such as acceptEdits remain operator-selected preauthorization, not an unattended prompt bypass.

The new tests exercise the production decision helper, including allow options offered alongside rejection, no rejection option, an empty list, malformed rejection, and JSON-RPC string IDs. Exact-head CI is green, including unit tests, Rust lint, security, desktop and relay/backend E2E, cross-compiles, DCO, Semgrep, and zizmor.

Operational consequence, intentionally fail-closed: managed desktop agents have no in-app permission prompt, so an operation requiring interactive approval will fail rather than pause for approval.

@wesbillman
wesbillman merged commit ad538bf into main Aug 5, 2026
33 checks passed
@wesbillman
wesbillman deleted the codex/security-acp-shell-auto-approval branch August 5, 2026 17:38
tlongwell-block pushed a commit that referenced this pull request Aug 5, 2026
Pull main in before remediation, per Tyler's instruction: no rebase, no
force, new commits on top.

Co-authored-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>
Signed-off-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>

* origin/main:
  fix(desktop): remove join API token control (#4897)
  fix(desktop): allow shared agent mentions (#4913)
  Polish mobile top navigation (#4778)
  fix(release): tag immutable desktop candidates (#4811)
  fix(channels): restrict private-channel invitations (#4612)
  fix(acp): reject unattended permission requests (#4609)
  fix(workflow): bind trigger author to the signed event (#4607)
  fix(git): revoke access for banned relay members (#4608)
  fix(agent): recover from unsupported image input instead of poisoning the turn (#4896)
  Define private managed agent wire protocol (#4593)
  fix(mobile): serialize channel sections sync (#3165)
  fix(desktop): make missing-command error actionable for released builds (#4802)

Signed-off-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>
wpfleger96 pushed a commit that referenced this pull request Aug 5, 2026
…-overflow-recovery

* origin/main:
  fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805)
  Fix mobile message timeline bounce (#4862)
  Polish mobile bottom sheets and profile cards (#4911)
  Fix media attachment actions (#4849)
  fix(desktop): remove join API token control (#4897)
  fix(desktop): allow shared agent mentions (#4913)
  Polish mobile top navigation (#4778)
  fix(release): tag immutable desktop candidates (#4811)
  fix(channels): restrict private-channel invitations (#4612)
  fix(acp): reject unattended permission requests (#4609)
  fix(workflow): bind trigger author to the signed event (#4607)
  fix(git): revoke access for banned relay members (#4608)
  fix(agent): recover from unsupported image input instead of poisoning the turn (#4896)
  Define private managed agent wire protocol (#4593)
  fix(mobile): serialize channel sections sync (#3165)
  fix(desktop): make missing-command error actionable for released builds (#4802)
  chore(release): release Buzz Desktop version 0.5.5 (#4809)
  feat: paste composer text without formatting (#4801)
  Revert "chore(release): release Buzz Desktop version 0.5.5" (#4808)
  chore(release): release Buzz Desktop version 0.5.5 (#4800)

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

# Conflicts:
#	crates/buzz-agent/src/agent.rs
#	crates/buzz-agent/src/handoff.rs
#	crates/buzz-agent/src/types.rs
#	crates/buzz-agent/tests/regressions.rs
atishpatel added a commit that referenced this pull request Aug 5, 2026
…usage

* origin/main:
  fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805)
  Fix mobile message timeline bounce (#4862)
  Polish mobile bottom sheets and profile cards (#4911)
  Fix media attachment actions (#4849)
  fix(desktop): remove join API token control (#4897)
  fix(desktop): allow shared agent mentions (#4913)
  Polish mobile top navigation (#4778)
  fix(release): tag immutable desktop candidates (#4811)
  fix(channels): restrict private-channel invitations (#4612)
  fix(acp): reject unattended permission requests (#4609)
  fix(workflow): bind trigger author to the signed event (#4607)
  fix(git): revoke access for banned relay members (#4608)

Co-authored-by: Atish Patel <atish@squareup.com>
Signed-off-by: Atish Patel <atish@squareup.com>
wpfleger96 pushed a commit that referenced this pull request Aug 5, 2026
…arer-auth

* origin/main: (65 commits)
  fix(desktop): route macos notification clicks (#4799)
  feat(mobile): sync themes per community (#3767)
  feat(desktop): sync themes per community (#3653)
  feat(desktop): cap OpenClaw agent parallelism at 5 (#4019)
  fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805)
  Fix mobile message timeline bounce (#4862)
  Polish mobile bottom sheets and profile cards (#4911)
  Fix media attachment actions (#4849)
  fix(desktop): remove join API token control (#4897)
  fix(desktop): allow shared agent mentions (#4913)
  Polish mobile top navigation (#4778)
  fix(release): tag immutable desktop candidates (#4811)
  fix(channels): restrict private-channel invitations (#4612)
  fix(acp): reject unattended permission requests (#4609)
  fix(workflow): bind trigger author to the signed event (#4607)
  fix(git): revoke access for banned relay members (#4608)
  fix(agent): recover from unsupported image input instead of poisoning the turn (#4896)
  Define private managed agent wire protocol (#4593)
  fix(mobile): serialize channel sections sync (#3165)
  fix(desktop): make missing-command error actionable for released builds (#4802)
  ...

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

# Conflicts:
#	CHANGELOG.md
atishpatel added a commit that referenced this pull request Aug 5, 2026
…usage

* origin/main:
  fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805)
  Fix mobile message timeline bounce (#4862)
  Polish mobile bottom sheets and profile cards (#4911)
  Fix media attachment actions (#4849)
  fix(desktop): remove join API token control (#4897)
  fix(desktop): allow shared agent mentions (#4913)
  Polish mobile top navigation (#4778)
  fix(release): tag immutable desktop candidates (#4811)
  fix(channels): restrict private-channel invitations (#4612)
  fix(acp): reject unattended permission requests (#4609)
  fix(workflow): bind trigger author to the signed event (#4607)
  fix(git): revoke access for banned relay members (#4608)

Co-authored-by: Atish Patel <atish@squareup.com>
Signed-off-by: Atish Patel <atish@squareup.com>
wpfleger96 added a commit that referenced this pull request Aug 5, 2026
Resolve two conflicts:

1. crates/buzz-acp/src/config.rs: PermissionMode::BypassPermissions removed
   by main (#4609, security fix). Branch added PermissionMode::Auto and
   effort_level. Resolution: keep Auto and effort_level additions; drop
   BypassPermissions (aligns with #4609's intent that the variant cannot
   be restored by configuration). One test assertion for
   BypassPermissions.is_default() removed accordingly.

2. desktop/src-tauri/src/commands/agents_deploy.rs: branch added claude
   B2/I-4 tests (ANTHROPIC_MODEL routing, BUZZ_ACP_EFFORT_LEVEL); main
   added OpenClaw parallelism-cap tests via #4019. Resolution: keep both
   test sets; fix trailing blank line introduced by merge tool.

Also: desktop/src-tauri/src/managed_agents/parallelism.rs (new in main
via #4019) constructs ManagedAgentRecord in tests without the
effort_level field added by this branch. Added effort_level: None to the
test helper to satisfy the struct initializer.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
wpfleger96 pushed a commit that referenced this pull request Aug 5, 2026
…ed-agent-store-merge

* origin/main: (24 commits)
  fix(reactions): support max-length custom emoji (#3833)
  feat(desktop): allow leaving your final community (#3621)
  fix(buzz-agent): recover from context-window 400s instead of sticking (#4946)
  docs(persona-pack): fix stale desktop import instructions (#4500)
  fix(desktop): route macos notification clicks (#4799)
  feat(mobile): sync themes per community (#3767)
  feat(desktop): sync themes per community (#3653)
  feat(desktop): cap OpenClaw agent parallelism at 5 (#4019)
  fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805)
  Fix mobile message timeline bounce (#4862)
  Polish mobile bottom sheets and profile cards (#4911)
  Fix media attachment actions (#4849)
  fix(desktop): remove join API token control (#4897)
  fix(desktop): allow shared agent mentions (#4913)
  Polish mobile top navigation (#4778)
  fix(release): tag immutable desktop candidates (#4811)
  fix(channels): restrict private-channel invitations (#4612)
  fix(acp): reject unattended permission requests (#4609)
  fix(workflow): bind trigger author to the signed event (#4607)
  fix(git): revoke access for banned relay members (#4608)
  ...

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>

# Conflicts:
#	desktop/src-tauri/src/managed_agents/runtime.rs
elifoster-block added a commit that referenced this pull request Aug 5, 2026
…p-csp

* origin/main: (66 commits)
  fix(reactions): support max-length custom emoji (#3833)
  feat(desktop): allow leaving your final community (#3621)
  fix(buzz-agent): recover from context-window 400s instead of sticking (#4946)
  docs(persona-pack): fix stale desktop import instructions (#4500)
  fix(desktop): route macos notification clicks (#4799)
  feat(mobile): sync themes per community (#3767)
  feat(desktop): sync themes per community (#3653)
  feat(desktop): cap OpenClaw agent parallelism at 5 (#4019)
  fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805)
  Fix mobile message timeline bounce (#4862)
  Polish mobile bottom sheets and profile cards (#4911)
  Fix media attachment actions (#4849)
  fix(desktop): remove join API token control (#4897)
  fix(desktop): allow shared agent mentions (#4913)
  Polish mobile top navigation (#4778)
  fix(release): tag immutable desktop candidates (#4811)
  fix(channels): restrict private-channel invitations (#4612)
  fix(acp): reject unattended permission requests (#4609)
  fix(workflow): bind trigger author to the signed event (#4607)
  fix(git): revoke access for banned relay members (#4608)
  ...
@FabianHertwig

Copy link
Copy Markdown

FYI this blocks Claude agents from using the Buzz CLI and they get locked out. From a user perspective the agents can no longer answer requests. See #5262

wpfleger96 added a commit that referenced this pull request Aug 8, 2026
The 74c9200 merge let main's revert-of-#4609 semantics leak into
branch-owned buzz-acp code. Specifically:

- config.rs: BypassPermissions variant reintroduced with no guard in
  PermissionConfig::resolve; test_default_config_uses_bypass_permissions
  asserted effective_mode == DontAsk
- pool.rs: ResolvedPermissionConfig replaced with bare PermissionMode,
  relay_event_publisher/permission_decision_tx stripped, sentinel wiring
  removed from run_prompt_task, doc comments spliced mid-sentence
- acp.rs: test helpers options()/outcome() deleted while call sites
  remained (5 E0425 compile errors); find_allow_once_returns_none_when_absent
  body corrupted with undefined `response` variable
- lib.rs: import regression

Fix: restore all four crates/buzz-acp files to the branch tip (d808c4b)
which correctly supersedes both #4609 and its revert #5323. No semantics
from the revert survive on this branch.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
marwanakeel added a commit to marwanakeel/buzz that referenced this pull request Aug 9, 2026
Per review on block#5316: `dontAsk` overrides the agent's configured *default mode*,
but matching `permissions.allow` rules are still evaluated per tool call, so a
pre-authorized command runs and only unmatched ones are denied. The first draft
said the mode override defeats all agent-side preauthorization, which would
have told operators the wrong thing.

The negative `settings.json` result that suggested otherwise is explained by
Claude Code's workspace-trust gate: `permissions.allow` from a project
`.claude/settings.json` is dropped in a workspace never trusted interactively —
which is every desktop-managed agent directory — while the identical rule in
`.claude/settings.local.json` applies. Independently reproduced on block#5262 with
default setting sources and the filename as the only variable.

Help text and startup warning now say allow rules do still apply and name
`settings.local.json` as the file that survives the trust gate, so scoped
preauthorization is presented as the recovery it actually is, alongside
`--permission-mode default`.

Still no behaviour change, and the block#4609 guarantee is untouched: a
`session/request_permission` reaching buzz-acp is answered with `reject_once`
or cancellation in every mode.

Thanks to @LucasMoskun for the correction and @DamienStevens for the
controlled test.

Refs block#5290, block#5303, block#5262, block#2884

Signed-off-by: Marwan <marwan.hefny@gmail.com>
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.

4 participants