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) <noreply@anthropic.com>
This commit is contained in:
Francesco Bonacci
2026-08-16 17:02:20 +00:00
committed by Teknium
parent cdbea83e1c
commit 3e0087abe2
2 changed files with 114 additions and 0 deletions
@@ -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
+44
View File
@@ -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: