From 3e0087abe2eab76a0bca9ecd38487ec20375ad10 Mon Sep 17 00:00:00 2001 From: Francesco Bonacci Date: Sun, 16 Aug 2026 17:02:20 +0000 Subject: [PATCH] fix(computer-use): warn when an approval bypass widens the driver mode `--yolo` / `-z` read as "don't prompt me", but they also swap computer_use onto a private `unrestricted` daemon, dropping the ceilings the configured mode would have applied. Nothing said so. A script picks up `-z` for quiet output and loses its limits as a side effect, and the only trace is a driver process nobody inspects. The mapping itself stays. It is deliberate, and `unrestricted` is reachable no other way: it is intentionally not a config value so a stale config line can never silently bypass approvals (see `_cua_configured_permission_mode`). Removing the mapping would delete the capability rather than fix it, and splitting it onto a second CLI flag was declined to avoid growing the surface. So the widening is now stated instead: one warning per session naming the configured mode it left, what stopped applying, and the two ways to keep a ceiling - drop the bypass flag, or declare a version-3 capability manifest, which now rides along with unrestricted as of the previous commit. Once per session, not per dispatch: the resolver runs on every tool call. Co-Authored-By: Claude Opus 5 (1M context) --- .../test_computer_use_cua_0_10_permissions.py | 70 +++++++++++++++++++ tools/computer_use/tool.py | 44 ++++++++++++ 2 files changed, 114 insertions(+) diff --git a/tests/tools/test_computer_use_cua_0_10_permissions.py b/tests/tools/test_computer_use_cua_0_10_permissions.py index 6913487e25..7b813d1c1d 100644 --- a/tests/tools/test_computer_use_cua_0_10_permissions.py +++ b/tests/tools/test_computer_use_cua_0_10_permissions.py @@ -257,3 +257,73 @@ def test_transport_reset_invalidates_native_and_browser_capabilities(): assert backend._snapshot_tokens == {} assert backend._typed_browser.state.target_id is None assert backend._typed_browser.state.refs == {} + + +# ── the escalation is at least audible ────────────────────────────────── + + +def test_bypass_escalation_is_warned_once_per_session(caplog): + """`-z` reads as "don't prompt me" but also drops the driver's ceiling. + + That widening is deliberate and unrestricted is reachable no other way, + but it is easy to trigger by accident: a script takes -z for quiet output + and loses its limits as a side effect. It must not be silent. + """ + import logging + + from tools.computer_use import tool as computer_use + + computer_use._escalation_warned.clear() + with patch( + "tools.approval.is_approval_bypass_active_for_session", + return_value=True, + ): + with caplog.at_level(logging.WARNING, logger=computer_use.logger.name): + assert computer_use._cua_permission_mode("session-warn") == "unrestricted" + assert computer_use._cua_permission_mode("session-warn") == "unrestricted" + + escalation = [ + r for r in caplog.records if "escalated the cua-driver" in r.getMessage() + ] + assert len(escalation) == 1, "warning must fire once, not on every dispatch" + message = escalation[0].getMessage() + assert "standard" in message + assert "unrestricted" in message + + +def test_no_escalation_warning_without_a_bypass(caplog): + import logging + + from tools.computer_use import tool as computer_use + + computer_use._escalation_warned.clear() + with patch( + "tools.approval.is_approval_bypass_active_for_session", + return_value=False, + ): + with caplog.at_level(logging.WARNING, logger=computer_use.logger.name): + assert computer_use._cua_permission_mode("session-quiet") == "standard" + + assert not [ + r for r in caplog.records if "escalated the cua-driver" in r.getMessage() + ] + + +def test_each_session_is_warned_separately(caplog): + import logging + + from tools.computer_use import tool as computer_use + + computer_use._escalation_warned.clear() + with patch( + "tools.approval.is_approval_bypass_active_for_session", + return_value=True, + ): + with caplog.at_level(logging.WARNING, logger=computer_use.logger.name): + computer_use._cua_permission_mode("session-one") + computer_use._cua_permission_mode("session-two") + + escalation = [ + r for r in caplog.records if "escalated the cua-driver" in r.getMessage() + ] + assert len(escalation) == 2 diff --git a/tools/computer_use/tool.py b/tools/computer_use/tool.py index 99e36b8239..951cf9715c 100644 --- a/tools/computer_use/tool.py +++ b/tools/computer_use/tool.py @@ -196,6 +196,47 @@ _session_auto_approve: Dict[str, bool] = {} _always_allow: Dict[str, set] = {} +# Sessions already told that their approval bypass widened the driver mode. +# The resolver runs per dispatch, so without this the warning would repeat on +# every single tool call. +_escalation_warned: set = set() + + +def _warn_bypass_escalation(session_id: str) -> None: + """Say out loud that an approval bypass just widened the driver's mode. + + ``-z`` / ``--yolo`` read as "don't prompt me", but they also swap the + driver onto a private ``unrestricted`` daemon, dropping the ceiling the + configured mode would have applied. That is deliberate (see + ``_cua_permission_mode``) and ``unrestricted`` is reachable no other way + — it is intentionally not a config value, so a stale config line cannot + silently bypass approvals. But it is easy to trigger without meaning to: + a script gets ``-z`` for quiet output and loses its limits as a side + effect. So the widening is at least stated, once per session. + """ + key = str(session_id or "") + with _approval_lock: + if key in _escalation_warned: + return + _escalation_warned.add(key) + try: + from tools.computer_use.cua_backend import _cua_configured_permission_mode + + configured = _cua_configured_permission_mode() + except Exception: + configured = "standard" + logger.warning( + "computer_use: approval bypass (--yolo / -z) escalated the cua-driver " + "permission mode from the configured '%s' to 'unrestricted' for this " + "session. Runtime approval prompts are disabled and the driver's " + "residual ceilings no longer apply. Drop the bypass flag to keep '%s', " + "or declare a version-3 computer_use.capability_manifest to keep a " + "ceiling on bypassed runs.", + configured, + configured, + ) + + def _cua_permission_mode(session_id: str) -> str: """Map Hermes's explicit approval bypass onto Cua's immutable mode. @@ -216,9 +257,11 @@ def _cua_permission_mode(session_id: str) -> str: ) if is_approval_bypass_active_for_session(session_id): + _warn_bypass_escalation(session_id) return "unrestricted" current_key = get_current_session_key(default="") if current_key and is_approval_bypass_active_for_session(current_key): + _warn_bypass_escalation(session_id) return "unrestricted" except Exception: # Approval state must fail closed if it cannot be resolved. @@ -412,6 +455,7 @@ def _shutdown_backend_atexit() -> None: with _approval_lock: _session_auto_approve.clear() _always_allow.clear() + _escalation_warned.clear() for backend, call_lock in unique.values(): try: