* feat(cua): preload the JavaScript computer use runtime * refactor(cua): remove the native Wayland backend * feat(cua): move app computer use into the driver * feat(cua): preserve background delivery and port platform fixes * feat(cua): add explicit desktop input and browser runtime facade * feat(cua): package the unified 0.26.0 computer use runtime * fix(cua): finalize cross-platform native reliability * fix(cua): isolate runtime releases from CLI versioning * fix(cua): keep Electron out of root workspace installs * fix(cua): synchronize consumers and async cleanup tests * fix(browser): migrate smoke to preloaded cua runtime * docs(cua): document MCP setup for Qwen Code Claude Code and Codex
20 KiB
Active Permission Adapters and Trusted Per-Session Modes
Historical record, superseded by the 2026-10 X11-only upgrade. Native Wayland implementations, helpers, dependencies and validation lanes described below have been removed; these historical results do not describe current support.
Status: Reviewed; implementation sequencing accepted
Issues: #2385 and #2437
Base: origin/main at 544bff3ac739e51185dbaf465160779817dca230
Date: 2026-07-24
Goal
Finish the permission-mode model in two dimensions:
- A trusted embedding host can run concurrent
standard,bounded, andunrestrictedsessions on one daemon without allowing an agent to choose or widen its own mode. - The daemon can promote reviewed capability groups from risk metadata to real protected-consent enforcement without duplicating policy logic or prompting for every action.
The public session argument remains a user-visible lifecycle label. It is
never authority by itself.
Current state
PermissionModeis a process-globalOnceLockselected at daemon startup.authorize_tool_callevaluates hard invariants, managed/user policy, risk classification, and the process-global bounded manifest.- Since RFC 2447, the public SDK can own a same-process native runtime and MCP,
HTTP, CLI, and daemon transports are downstream adapters. The same-process
CuaDriver::createpath currently invokes the tool registry directly and does not pass throughauthorize_tool_call; authorization therefore has to move into the SDK/native-runtime dispatch boundary rather than remain a daemon-only concern. sessionis caller-declared. The daemon mirrors it into reserved arguments, while the MCP proxy supplies a separate transport session ID.- The only active consent adapter is
browser_prepare(strategy.kind = existing_profile). ProtectedConsentProviderandApprovalBrokerare owned byBrowserEngine, so the canonical dispatch coordinator cannot use them for desktop or file capabilities.- Production registries currently construct
BrowserEnginewithout a protected provider. Standard/bounded existing-profile attachment therefore correctly refuses unless a certified host adapter is installed. - The compatibility
EmbeddedCuaDriverHostowns daemon lifecycle and a private parent-liveness stdin pipe, but its action transport still uses a path-addressed socket. The preferredCuaDriver::createtopology is same-process and needs no shared-daemon session delegation at all.
Non-negotiable security decisions
Daemon ceiling and effective session mode are separate
The daemon starts with an immutable SessionModeCeiling:
- allowed session modes;
- whether unrestricted sessions were explicitly acknowledged;
- managed/user policy hashes;
- maximum session TTL and idle TTL;
- whether trusted session delegation is enabled.
Every call resolves an immutable EffectiveAuthorizationContext containing:
- daemon instance and generation;
- public session;
- transport session;
- effective permission mode;
- optional bounded manifest hash;
- trusted-host execution lease ID;
- policy hashes and expiry.
No context means the call inherits the daemon's legacy process mode. This keeps standalone CLI/MCP/raw-socket behavior compatible.
Session IDs are labels, not credentials
For the certified embedded route, delegated authority is bound to an already-connected session channel rather than represented by a serializable bearer token. The daemon binds the channel to the full authorization context when the trusted host creates it. Authority never enters tool arguments, policy input, logs, telemetry, or responses.
The following can never select a mode:
- public tool arguments or reserved-argument lookalikes;
- MCP metadata or elicitation;
- a caller-selected public or transport session ID;
- ordinary daemon control methods;
- model-visible environment variables or files.
Only a trusted host can mint delegated sessions
The first supported minting route is the Rust-owned embedded host. It uses a dedicated host-control channel inherited at spawn and unavailable to the model-facing MCP stream. The channel is versioned and bound to the daemon generation. EOF revokes every delegated session and shuts the embedded daemon down.
The ordinary daemon listener never exposes create/upgrade mode operations. Future native hosts may implement the same closed control protocol. Hosts that cannot protect this channel must use one daemon per mode.
Bind authority to an authenticated accepted connection
The pre-review recommendation is to avoid serializable bearer tokens entirely for the certified embedded route. The trusted host creates an already-connected session channel and supplies one endpoint to the daemon and the other to the SDK or MCP proxy through an explicit inherited-handle allowlist. Every request arriving on the channel inherits its immutable authorization context; no request field can select it.
The control channel can mint a delegated session, but minting is not binding. Binding action calls without a serializable bearer value requires either #2410's inherited connected action transport or multiplexing action calls onto another authenticated accepted channel. #2410 is therefore one valid implementation, not a blanket prerequisite for the context, ceiling, control, or inventory foundations. A path-addressed same-user socket plus a caller-declared session ID cannot carry delegated authority.
trusted host daemon model-facing proxy
| inherited control pair | |
|-------------------------->| |
| create connected pair | |
| create_session(mode, manifest, host endpoint) |
|-------------------------->| bind context to connection |
| inherit peer endpoint ---------------------------------->| MCP only
| |<==== context-bound calls =====|
| revoke / host EOF ------->| close, revoke, teardown |
On Unix the control plane can pass additional descriptors with SCM_RIGHTS or
create all required channels before spawn. On Windows the host uses an explicit
handle-inheritance/duplication allowlist in the interactive user session. The
implementation must never fall back to a discoverable named endpoint for a
session advertised as protected.
Unrestricted remains suppress-only
An unrestricted session skips Cua runtime consent prompts, but it never widens managed/user policy, hard invariants, identity proofs, cleanup, revocation, or resource scoping. The daemon may host unrestricted sessions only if trusted startup explicitly enabled and acknowledged that ceiling.
Architecture
1. Session authorization registry
Add a process-owned SessionAuthorizationRegistry in core:
- create a delegated session from a trusted control message and bind it to the supplied connected endpoint;
- resolve the connection plus exact public/transport session into an immutable context;
- reject connection substitution, session mismatch, expiry, ended sessions, daemon generation mismatch, and modes outside the daemon ceiling;
- revoke one context or every context owned by a dead host lease;
- register a session-end hook so grants, indicators, browser bindings, and mode authority share one teardown signal.
The first foundation slice stores the context and ceiling while calls continue to inherit the legacy process mode. Certified per-session selection remains disabled until action calls arrive on an authenticated connection.
Per-session bounded also requires replacing the process-global
SessionManifest OnceLock with an immutable per-session manifest store. A
manifest hash in the context is not sufficient. Until that store exists, no
mixed-mode status or documentation may claim concurrent bounded manifests.
The registry exposes no handle or resource contents through health. Status may report counts by mode, allowed modes, provider readiness, and ceiling provenance.
2. Trusted embedded control protocol
Replace the liveness-only stdin reader with a bounded framed control reader. The host writes versioned messages for:
hellowith daemon generation and protocol version;create_sessionwith public session, requested mode, expiry, and optional bounded manifest;revoke_session;- provider decision/indicator lifecycle messages in the later provider slice;
shutdownor EOF.
The daemon sends acknowledgements and provider requests over a separate
inherited response handle. Neither handle is inherited by an MCP proxy or
returned in EmbeddedDriverConnection.
The SDK returns a host-side EmbeddedAuthorizedSession object owning the peer
endpoint. Its MCP launch method explicitly inherits an authenticated action
endpoint into a host-owned proxy process, or multiplexes calls over the
authenticated channel; the model sees ordinary MCP schemas only. Direct SDK
calls use the same accepted connection. A plain command/args/environment
record is insufficient for the protected path because it cannot prove handle
ownership or cleanup.
3. Canonical runtime authorization coordinator
Place the coordinator at the native ToolRegistry::invoke chokepoint (or the
immediately enclosing DriverRuntime::invoke) so the typed SDK, daemon, MCP,
HTTP, CLI, and raw-socket routes cannot bypass it. This corrects an
authorization omission in the RFC 2447 implementation: the canonical
same-process SDK path currently reaches the registry without calling
authorize_tool_call.
Replace the process-global lookup inside authorize_tool_call with:
- sanitize reserved arguments;
- resolve connection-bound authority into
EffectiveAuthorizationContext; - enforce hard invariants;
- intersect managed, user, and optional session policy;
- classify the exact operation;
- route active classifications through the typed enforcement adapter;
- invoke the tool only after the adapter returns an active grant or confirms unrestricted coverage.
The daemon transport supplies authenticated connection context to that runtime coordinator. In-process SDK runtimes use a trusted constructor-owned context and do not pretend to be shared daemons. Transport adapters may reject early but can never mint or satisfy authority.
4. Generic protected grant broker
Move provider ownership out of BrowserEngine into a daemon-owned
AuthorizationCoordinator. Generalize the current broker request with a typed
ProtectedResource enum and a canonical resource digest.
The current ConsentRequest, digest binding, IndicatorLease, and revocation
primitives are retained. The missing work is coordinator ownership and a
certified production host adapter, not reinvention of those primitives.
Initial resources:
ExistingBrowserProfile { pid, window_id, fingerprint, endpoint_owner };UserWindowObservation { pid, window_id };DesktopObservation { display_generation };UserWindowInput { pid, window_id, delivery_ceiling };DesktopInput { display_generation, delivery_ceiling };BrowserFileTransfer { binding, tab, paths_or_destination_digest };BrowserConsequentialAction { binding, tab, action_kind }.
Grants are bound to daemon generation, effective session, transport session, mode, policy hashes, exact resource digest, expiry, indicator lease, and revocation generation. They contain no raw typed text, page content, file contents, or screenshot data.
5. Stable enforcement adapter inventory
Replace the hard-coded status string with a machine-readable inventory. Each entry declares:
- operation selector and risk class;
- state:
active,metadata_only, ornot_exposed; - resource kind and scope keys;
- grant type and TTL;
- indicator requirement;
- revocation triggers;
- stable refusal code;
- supported modes and required provider capability.
tools/list, authorization status, and generated docs derive from the same
inventory. Unknown tools and unrecognized operation variants remain denied.
Adapter rollout for the currently exposed surface
Group A: private observation
Prepare scoped adapters for user-owned get_window_state,
get_accessibility_tree, and get_desktop_state. Keep their public state
metadata_only until #2411 and representative platform certification pass.
- Driver-owned/disposable resources remain prompt-free.
- Standard obtains one window/display grant and reuses it inside scope.
- Bounded requires a manifest entry and activates the indicator without a second prompt.
- Unrestricted skips the prompt but retains session/resource binding.
- Screenshot-to-file arguments are file egress and are covered by Group C, never by an observation-only grant.
Group B: desktop input
Prepare click, typing, key, pointer, scroll, drag, focus, and set-value routes.
Keep user-owned enforcement metadata_only until #2411 and platform
certification pass.
- Window-targeted AX/PX/background/foreground variants map to one exact window-input resource with a delivery ceiling.
- Desktop coordinate input maps to a display resource.
- Foreground delivery is a scope expansion unless already approved.
- Cua consent/indicator UI and known OS security prompts remain hard-denied.
- Coordinate input is semantically opaque; the adapter does not claim to detect purchases, sends, deletes, or account changes.
Group C: browser file and consequential routes
Prepare browser_set_input_files, browser_download, screenshot-to-file,
mutating browser_dialog, and mutating page operations. Keep them
metadata_only until #2411 and platform certification pass.
- Bind file grants to canonical paths and the exact browser binding/tab.
- Prevent aliases, generic page operations, and raw routes from bypassing the typed adapter.
- Operations whose consequence cannot be bounded are refused in standard and bounded rather than silently treated as ordinary input.
Not currently exposed
Microphone, camera, generic shell, and generic network tools are not part of
the current registry. The inventory records them as not_exposed; no dormant
adapter or unsupported public claim is added.
Intervention behavior
- Standard: one approval per exact user-owned window/display/browser-file scope; no prompt for repeated ordinary actions within that grant. Foreground escalation, a new app/window/display, file destination, or consequential action may require a new approval.
- Bounded: one trusted session creation/manifest approval; no runtime prompts inside the manifest, but persistent indication remains mandatory.
- Unrestricted: one trusted host launch/session selection acknowledgement; no Cua runtime prompts.
Delivery sequence
- Canonical SDK/runtime authorization boundary: eliminate the direct-SDK bypass introduced by the new native-core topology while preserving typed SDK behavior.
- Generic coordinator and inventory (#2385 P0): context-aware canonical dispatch, generic broker ownership, generated active/metadata inventory, and stable refusal vocabulary.
- Provider ownership refactor: hoist the existing
ApprovalBrokerout ofBrowserEnginewithout adding a new active group. - Trusted session authority foundation (#2437): context model, daemon ceiling, immutable per-session manifest store, status, teardown, and adversarial tests while mode still inherits legacy configuration.
- Authenticated session minting and action binding: private embedded control channel plus either #2410 or actions on that authenticated channel.
- Embedded protected provider dependency (#2411): exact request/response, persistent indicator leases, Stop, channel-death revocation, and packaged host contract.
- Observation and input adapters (#2385 P1): exact resource extraction, grant reuse, foreground expansion, self-target refusal, and platform tests.
- File/consequential adapters (#2385 P2/P4): browser upload/download, dialog/page mutations, path constraints, and bypass tests.
- Docs and support claims: expected intervention counts, embedding guide, status reference, migration, and unsupported-host fallback.
Keep the slices in dependency order and reviewable. Do not activate a group until its provider, indicator, revocation, bypass, and platform matrix pass.
Verification
Deterministic tests
- mode-ceiling lattice and policy intersection;
- delegated session creation only through the trusted control seam;
- tool/MCP/reserved-argument mode injection denied;
- wrong connection/session/transport/generation, replay, expiry, reconnect, and cross-session substitution denied;
- concurrent standard/bounded/unrestricted calls preserve isolation;
- standard grant reuse stays within exact scope;
- bounded manifests cannot self-amend or auto-accept
ask; - unrestricted cannot widen a denial;
- active inventory matches every registered tool/alias and generated docs;
- indicator failure and host-control EOF revoke before further side effects.
Integration and representative environments
- public MCP, CLI, raw socket, same-process SDK, and compatibility-daemon SDK calls all reach the same coordinator;
- embedded host creates mixed-mode sessions while the model-facing schemas contain no mode/token fields;
- standard without a certified provider returns a stable refusal and produces no side effect;
- macOS logged-in VM validates TCC attribution, protected host UI, Stop, and window/display observation/input scopes;
- Windows runs in a verified interactive RDP session, never Session 0;
- Linux validates X11 and Wayland separately and reports missing protected UI as environment-unavailable before behavior assertions;
- before/after application state, focus, z-order, cursor, and input-isolation oracles prove each background-input row.
Completion bar
- Mixed-mode shared-daemon sessions are supported only through a certified trusted host and cannot be selected from the model/tool channel.
- Every currently exposed sensitive capability is either actively enforced with complete adapter evidence or explicitly listed as metadata-only with a precise reason and no broader security claim.
- The first activated observation/input and file/consequential groups pass exact-head unit, integration, adversarial, and representative-platform tests.
- Documentation and machine-readable status agree on the active inventory and realistic human-intervention count.
- Delivery is split into dependency-ordered release-scoped pull requests; a local plan or unit-only result is not completion.
Independent review outcome
Claude Code Opus reviewed the plan against
544bff3ac739e51185dbaf465160779817dca230. The accepted must-fix findings are:
- close the shipping same-process SDK authorization bypass first;
- keep user-owned capability groups
metadata_onlyuntil #2411 and representative platform certification exist; - bind delegated authority to an authenticated accepted connection, never a public session string;
- hoist provider ownership before generalizing adapters;
- treat #2410 as one action-binding implementation, not a blanket dependency;
- classify screenshot-to-file as egress; and
- make concurrent bounded sessions depend on a real per-session manifest store.
The accepted delivery shape is a dependency-ordered PR series. Neither #2437
as a whole nor any user-owned capability group's switch to active can be
honestly completed in one PR.
Prior baseline evidence
On 849e0db2118e17f80433d7a518bae8815e5ca5f6 (Cua Driver 0.11.0), before
implementation:
cargo test -p cua-driver-core authorization::tests: 10 passed;cargo test -p cua-driver-core consent::tests: 6 passed;cargo test -p cua-driver-core session_manifest::tests: 6 passed;cargo test -p cua-driver-sdk embedded::tests::authorization_modes_require_explicit_acknowledgements: 1 passed.
These are baseline contract checks, not evidence for the new behavior.
The plan must be re-baselined on 544bff3ac739e51185dbaf465160779817dca230
(Cua Driver 0.12.4) after independent review because RFC 2447 changed the
canonical runtime boundary.