packages/cua-driver/docs/permission-adapters-and-session-modes-plan.md
Status: Reviewed; implementation sequencing accepted
Issues: #2385 and #2437
Base: origin/main at 544bff3ac739e51185dbaf465160779817dca230
Date: 2026-07-24
Finish the permission-mode model in two dimensions:
standard, bounded, and
unrestricted sessions on one daemon without allowing an agent to choose or
widen its own mode.The public session argument remains a user-visible lifecycle label. It is
never authority by itself.
PermissionMode is a process-global OnceLock selected at daemon startup.authorize_tool_call evaluates hard invariants, managed/user policy, risk
classification, and the process-global bounded manifest.CuaDriver::create path currently invokes the tool registry directly and
does not pass through authorize_tool_call; authorization therefore has to
move into the SDK/native-runtime dispatch boundary rather than remain a
daemon-only concern.session is caller-declared. The daemon mirrors it into reserved arguments,
while the MCP proxy supplies a separate transport session ID.browser_prepare(strategy.kind = existing_profile).ProtectedConsentProvider and ApprovalBroker are owned by BrowserEngine,
so the canonical dispatch coordinator cannot use them for desktop or file
capabilities.BrowserEngine without a
protected provider. Standard/bounded existing-profile attachment therefore
correctly refuses unless a certified host adapter is installed.EmbeddedCuaDriverHost owns daemon lifecycle and a private
parent-liveness stdin pipe, but its action transport still uses a
path-addressed socket. The preferred CuaDriver::create topology is
same-process and needs no shared-daemon session delegation at all.The daemon starts with an immutable SessionModeCeiling:
Every call resolves an immutable EffectiveAuthorizationContext containing:
No context means the call inherits the daemon's legacy process mode. This keeps standalone CLI/MCP/raw-socket behavior compatible.
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:
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.
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.
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.
Add a process-owned SessionAuthorizationRegistry in core:
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.
Replace the liveness-only stdin reader with a bounded framed control reader. The host writes versioned messages for:
hello with daemon generation and protocol version;create_session with public session, requested mode, expiry, and optional
bounded manifest;revoke_session;shutdown or 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.
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:
EffectiveAuthorizationContext;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.
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.
Replace the hard-coded status string with a machine-readable inventory. Each entry declares:
active, metadata_only, or not_exposed;tools/list, authorization status, and generated docs derive from the same
inventory. Unknown tools and unrecognized operation variants remain denied.
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.
Prepare click, typing, key, pointer, scroll, drag, focus, and set-value routes.
Keep user-owned enforcement metadata_only until #2411 and platform
certification pass.
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.
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.
ApprovalBroker out of
BrowserEngine without adding a new active group.Keep the slices in dependency order and reviewable. Do not activate a group until its provider, indicator, revocation, bypass, and platform matrix pass.
ask;Claude Code Opus reviewed the plan against
544bff3ac739e51185dbaf465160779817dca230. The accepted must-fix findings are:
metadata_only until #2411 and
representative platform certification exist;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.
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.