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:
committed by
Teknium
parent
cdbea83e1c
commit
3e0087abe2
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user