fix(computer-use): fail closed on unverified CuaDriver.app + background launch

Hardening on top of the TCC daemon-identity salvage:
- _validate_cua_driver_app_signature: codesign -dv gate requiring EXACT
  Identifier=com.trycua.driver and the official team (4YEC26S9KF) before
  /usr/bin/open hands the bundle to LaunchServices — the identity fix must
  not double as a launcher for arbitrary/impostor bundles (suffixed
  identifiers and wrong teams rejected; unsigned dev builds only via
  computer_use.allow_unsigned_driver: true in config.yaml).
- _resolve_cua_driver_app_path: derive the bundle ONLY from the resolved
  driver binary — the /Applications fallback could launch a DIFFERENT
  install than the manifest resolved.
- open -n -g: don't activate/steal focus when launching the daemon.
- 7 new tests incl. sabotage-verified exact-match assertions.

Grafted from #76433's review direction (@Chadmc9889's original fail-closed
validation requirement).

Co-authored-by: Chadmc9889 <Chadmc9889@users.noreply.github.com>
This commit is contained in:
Teknium
2026-08-26 00:58:07 -07:00
parent 4746f614be
commit 45db70a80a
3 changed files with 170 additions and 15 deletions

View File

@@ -3646,6 +3646,12 @@ DEFAULT_CONFIG = {
# flags when it launches the runtime. See
# https://cua.ai/docs/reference/cua-driver/permission-modes
"capability_manifest": "",
# macOS only: allow launching an UNSIGNED (ad-hoc / TeamIdentifier
# not set) CuaDriver.app for the private-session daemon. The default
# (false) fails closed unless the bundle is signed with the official
# cua-driver identity (com.trycua.driver / team 4YEC26S9KF). Enable
# only when developing the driver locally from source.
"allow_unsigned_driver": False,
# Pre-authorize existing-profile browser attachment in standard mode
# (cua-driver's trusted-launcher `--grant existing-profile`). When
# true, the agent can attach to your already-running, signed-in

View File

@@ -248,7 +248,9 @@ def test_schema_does_not_expose_approval_token():
# ── bounded embedded daemon ─────────────────────────────────────────────
def test_macos_embedded_daemon_launches_through_cuadriver_app():
def test_macos_embedded_daemon_launches_through_cuadriver_app(monkeypatch):
validated = []
monkeypatch.setattr(cb, "_validate_cua_driver_app_signature", lambda app: validated.append(app))
command = cb._embedded_daemon_spawn_command(
"/tmp/cua-driver",
["serve", "--embedded", "--socket", "/tmp/private.sock"],
@@ -256,9 +258,12 @@ def test_macos_embedded_daemon_launches_through_cuadriver_app():
app_path="/Applications/CuaDriver.app",
)
# Signature validation is mandatory before any launch command is built.
assert validated == ["/Applications/CuaDriver.app"]
assert command == [
"/usr/bin/open",
"-n",
"-g",
"-a",
"/Applications/CuaDriver.app",
"--args",
@@ -269,6 +274,83 @@ def test_macos_embedded_daemon_launches_through_cuadriver_app():
]
def _codesign_proc(returncode=0, stderr=""):
import subprocess as _sp
return _sp.CompletedProcess(["codesign"], returncode, stdout="", stderr=stderr)
def _patch_codesign(monkeypatch, proc):
monkeypatch.setattr(cb.shutil, "which", lambda name: "/usr/bin/codesign")
monkeypatch.setattr(cb.subprocess, "run", lambda *a, **kw: proc)
def test_driver_signature_valid_official_identity(monkeypatch):
_patch_codesign(
monkeypatch,
_codesign_proc(stderr="Identifier=com.trycua.driver\nTeamIdentifier=4YEC26S9KF\n"),
)
cb._validate_cua_driver_app_signature("/Applications/CuaDriver.app") # no raise
def test_driver_signature_rejects_suffixed_identifier(monkeypatch):
import pytest
_patch_codesign(
monkeypatch,
_codesign_proc(stderr="Identifier=com.trycua.driver.evil\nTeamIdentifier=4YEC26S9KF\n"),
)
with pytest.raises(RuntimeError, match="identifier"):
cb._validate_cua_driver_app_signature("/Applications/CuaDriver.app")
def test_driver_signature_rejects_wrong_team(monkeypatch):
import pytest
_patch_codesign(
monkeypatch,
_codesign_proc(stderr="Identifier=com.trycua.driver\nTeamIdentifier=EVIL000000\n"),
)
with pytest.raises(RuntimeError, match="team"):
cb._validate_cua_driver_app_signature("/Applications/CuaDriver.app")
def test_driver_signature_unsigned_rejected_by_default(monkeypatch):
import pytest
_patch_codesign(
monkeypatch,
_codesign_proc(stderr="Identifier=com.trycua.driver\nTeamIdentifier=not set\n"),
)
monkeypatch.setattr(cb, "_computer_use_cfg", lambda: {})
with pytest.raises(RuntimeError, match="team"):
cb._validate_cua_driver_app_signature("/Applications/CuaDriver.app")
def test_driver_signature_unsigned_allowed_by_config_opt_in(monkeypatch):
_patch_codesign(
monkeypatch,
_codesign_proc(stderr="Identifier=com.trycua.driver\nTeamIdentifier=not set\n"),
)
monkeypatch.setattr(cb, "_computer_use_cfg", lambda: {"allow_unsigned_driver": True})
cb._validate_cua_driver_app_signature("/Applications/CuaDriver.app") # no raise
def test_driver_signature_rejects_unsigned_bundle(monkeypatch):
import pytest
_patch_codesign(monkeypatch, _codesign_proc(returncode=1, stderr="code object is not signed at all"))
with pytest.raises(RuntimeError, match="not code-signed"):
cb._validate_cua_driver_app_signature("/Applications/CuaDriver.app")
def test_resolve_app_path_has_no_applications_fallback(tmp_path):
# A driver binary OUTSIDE any .app bundle must resolve to None — the old
# /Applications fallback could launch a DIFFERENT install than the
# resolved driver.
assert cb._resolve_cua_driver_app_path(str(tmp_path / "cua-driver")) is None
def test_non_macos_embedded_daemon_keeps_direct_binary_launch():
command = cb._embedded_daemon_spawn_command(
"/tmp/cua-driver",

View File

@@ -593,25 +593,90 @@ def _wsl_windows_path_to_posix(path: str) -> str:
def _resolve_cua_driver_app_path(driver_cmd: str) -> Optional[str]:
"""Return the installed CuaDriver.app carrying *driver_cmd*, if present."""
"""Return the CuaDriver.app bundle that CARRIES *driver_cmd*, if any.
Deliberately derived from the resolved driver binary path only — no
/Applications or ~/Applications fallback. A fallback candidate can be a
DIFFERENT install than the driver the manifest resolved (stale copy,
side-by-side version), and launching it would run code the resolution
chain never validated. If the resolved driver does not live inside an
app bundle, the caller fails closed with install guidance.
"""
marker = ".app/Contents/MacOS/"
candidates: List[str] = []
marker_index = driver_cmd.find(marker)
if marker_index >= 0:
candidates.append(driver_cmd[: marker_index + len(".app")])
candidates.extend(
[
"/Applications/CuaDriver.app",
os.path.expanduser("~/Applications/CuaDriver.app"),
]
)
for candidate in candidates:
executable = os.path.join(candidate, "Contents", "MacOS", "cua-driver")
if os.path.isfile(executable) and os.access(executable, os.X_OK):
return candidate
if marker_index < 0:
return None
candidate = driver_cmd[: marker_index + len(".app")]
executable = os.path.join(candidate, "Contents", "MacOS", "cua-driver")
if os.path.isfile(executable) and os.access(executable, os.X_OK):
return candidate
return None
# The only bundle identity the private daemon may launch through, and the
# team that signs official cua-driver releases. Exact matches only: a
# suffixed identifier ("com.trycua.driver.evil") or a different non-empty
# team is an impostor bundle, not a variant.
_CUA_DRIVER_BUNDLE_ID = "com.trycua.driver"
_CUA_DRIVER_TEAM_ID = "4YEC26S9KF"
def _validate_cua_driver_app_signature(app_path: str) -> None:
"""Fail closed unless *app_path* is the genuinely-signed CuaDriver.app.
Launching via ``/usr/bin/open`` hands LaunchServices whatever bundle sits
at the path, so the TCC-identity fix must not become a launcher for
arbitrary apps: require ``codesign -dv`` to report EXACTLY
``Identifier=com.trycua.driver`` and the expected TeamIdentifier.
``TeamIdentifier=not set`` (unsigned/ad-hoc dev builds) is allowed only
when ``computer_use.allow_unsigned_driver: true`` is set in config.yaml —
the escape hatch for local driver development, never the default. Raises
RuntimeError on any mismatch or when codesign is unavailable/fails.
"""
codesign = shutil.which("codesign")
if not codesign:
raise RuntimeError(
"codesign is required to verify CuaDriver.app before launching it."
)
try:
proc = subprocess.run(
[codesign, "-dv", app_path],
capture_output=True,
text=True,
timeout=15,
)
except (OSError, subprocess.TimeoutExpired) as exc:
raise RuntimeError(f"could not verify CuaDriver.app signature: {exc}") from exc
if proc.returncode != 0:
raise RuntimeError(
f"CuaDriver.app at {app_path} is not code-signed; refusing to launch it "
f"({(proc.stderr or '').strip()})"
)
# codesign -dv reports on stderr.
fields = {}
for line in (proc.stderr or "").splitlines():
key, sep, value = line.partition("=")
if sep:
fields.setdefault(key.strip(), value.strip())
identifier = fields.get("Identifier", "")
team = fields.get("TeamIdentifier", "")
if identifier != _CUA_DRIVER_BUNDLE_ID:
raise RuntimeError(
f"CuaDriver.app at {app_path} has identifier {identifier!r}, "
f"expected {_CUA_DRIVER_BUNDLE_ID!r}; refusing to launch it."
)
if team == _CUA_DRIVER_TEAM_ID:
return
if team in ("", "not set") and _computer_use_cfg().get("allow_unsigned_driver") is True:
return
raise RuntimeError(
f"CuaDriver.app at {app_path} is signed by team {team!r}, expected "
f"{_CUA_DRIVER_TEAM_ID!r}; refusing to launch it. (Set "
"computer_use.allow_unsigned_driver: true in config.yaml only for "
"local unsigned driver builds.)"
)
def _embedded_daemon_spawn_command(
driver_cmd: str,
serve_args: List[str],
@@ -628,9 +693,11 @@ def _embedded_daemon_spawn_command(
"CuaDriver.app is required for private computer-use sessions on macOS. "
"Run `hermes computer-use install` to restore it."
)
_validate_cua_driver_app_signature(resolved_app)
return [
"/usr/bin/open",
"-n",
"-g",
"-a",
resolved_app,
"--args",