Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions libs/cua-driver/docs/private-envelope-http.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,27 @@ value returned at creation. Missing generations fail with HTTP 400, absent
connections with 404, and mismatches with 409. A client must not reopen a
connection automatically after those failures.

Creation binds a Standard session with a one-hour maximum lifetime and a
five-minute idle lifetime. The immutable runtime ceiling still applies:
incompatible runtimes refuse creation. This slice does not inherit unrestricted
mode or accept permission modes, manifests, or arbitrary session options from
the wire. Ordinary typed calls use `session=None`; operations requiring a
session label use the returned `public_session`.
Creation binds a Standard session by default, with a one-hour maximum lifetime
and a five-minute idle lifetime. The immutable runtime ceiling still applies:
incompatible runtimes refuse creation. This slice does not implicitly inherit
unrestricted mode or accept permission modes, manifests, or arbitrary session
options from the wire. Ordinary typed calls use `session=None`; operations
requiring a session label use the returned `public_session`.

For an explicitly authorized disposable or trusted environment, the launcher
can set `CUA_DRIVER_ENVELOPE_PERMISSION_MODE=unrestricted` and launch the daemon
with `--permission-mode unrestricted --dangerously-bypass-approvals`. Both the
carrier opt-in and the existing runtime risk acknowledgement are required.
The carrier reads the setting once at startup; clients cannot change it.
The default remains `standard`, even on an unrestricted daemon. Other values,
including `bounded`, fail startup. A carrier with a host capability manifest
also fails startup: this first slice does not support manifest configuration.
The SDK treats compatibility-call and trusted-session manifests separately;
this carrier neither inherits the former nor exposes the latter. This is a
carrier limitation, not a change to the SDK's per-session manifest contract.
Managed and user policies remain binding.
This option does not authorize public exposure, alter Fleet authorization,
or change existing computer-server sessions.

`capabilities` contains `minimum_envelope_version`, `maximum_envelope_version`,
and `supports_cancellation`. This carrier supports envelope version 1 and
Expand Down
166 changes: 163 additions & 3 deletions libs/cua-driver/rust/crates/cua-driver/src/driver_service_http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
//! Connection IDs and generations are routing/lifecycle markers, not credentials.
//! Each connection already has a host-bound root session; independent bound
//! sessions are unsupported. No request supplies permission options or paths.
//! This first slice requests Standard sessions only; incompatible runtime
//! ceilings refuse creation. It does not inherit or widen the runtime mode.
//! Standard is the default. Unrestricted sessions require a separate trusted
//! launcher opt-in and an already acknowledged unrestricted runtime.

use cua_driver_sdk::remote::DriverRequestEnvelope;
use cua_driver_sdk::remote_receiver::DriverEnvelopeReceiver;
Expand Down Expand Up @@ -348,11 +348,53 @@ impl Drop for Server {
}
}

fn select_session_mode(
requested: Option<&str>,
host: cua_driver_core::authorization::PermissionMode,
has_manifest: bool,
) -> anyhow::Result<cua_driver_sdk::SessionPermissionMode> {
use cua_driver_core::authorization::PermissionMode;
use cua_driver_sdk::SessionPermissionMode;
// This carrier cannot propagate a host manifest into its bound session.
anyhow::ensure!(
!has_manifest,
"envelope sessions with a host capability manifest are unsupported"
);
match requested {
None | Some("standard") => Ok(SessionPermissionMode::Standard),
Some("unrestricted") => {
anyhow::ensure!(
host == PermissionMode::Unrestricted,
"unrestricted envelope sessions require an acknowledged unrestricted host"
);
Ok(SessionPermissionMode::Unrestricted)
}
Some(_) => {
anyhow::bail!("CUA_DRIVER_ENVELOPE_PERMISSION_MODE must be standard or unrestricted")
}
}
}

fn configured_session_mode() -> anyhow::Result<cua_driver_sdk::SessionPermissionMode> {
let requested = match std::env::var("CUA_DRIVER_ENVELOPE_PERMISSION_MODE") {
Ok(value) => Some(value),
Err(std::env::VarError::NotPresent) => None,
Err(error) => return Err(error.into()),
};
let host =
cua_driver_core::authorization::configured_permission_mode().map_err(anyhow::Error::msg)?;
let has_manifest = cua_driver_core::session_manifest::configured_capability_manifest()
.map_err(anyhow::Error::msg)?
.is_some();
select_session_mode(requested.as_deref(), host, has_manifest)
}

pub async fn start(sdk: Arc<crate::sdk_adapter::SdkAdapter>, port: u16) -> anyhow::Result<Server> {
let mode = configured_session_mode()?;
let listener = TcpListener::bind((std::net::Ipv4Addr::LOCALHOST, port)).await?;
let service = Arc::new(Service {
entries: Mutex::new(HashMap::new()),
factory: Arc::new(move || sdk.create_envelope_receiver()),
factory: Arc::new(move || sdk.create_envelope_receiver(mode)),
exchanges: tokio::sync::Semaphore::new(MAX_EXCHANGES),
});
let task = tokio::spawn(async move {
Expand Down Expand Up @@ -393,6 +435,124 @@ mod tests {
use cua_driver_sdk::{remote_receiver::DriverEnvelopeExecutor, DriverError};
use std::sync::atomic::{AtomicUsize, Ordering};

#[test]
fn envelope_mode_never_implicitly_inherits_unrestricted() {
use cua_driver_core::authorization::PermissionMode as Host;
for host in [Host::Standard, Host::Bounded, Host::Unrestricted] {
for requested in [None, Some("standard")] {
assert_eq!(
select_session_mode(requested, host, false).unwrap(),
cua_driver_sdk::SessionPermissionMode::Standard
);
}
}
}

#[test]
fn envelope_unrestricted_requires_matching_host_and_no_manifest() {
use cua_driver_core::authorization::PermissionMode as Host;
assert_eq!(
select_session_mode(Some("unrestricted"), Host::Unrestricted, false).unwrap(),
cua_driver_sdk::SessionPermissionMode::Unrestricted
);
for host in [Host::Standard, Host::Bounded] {
assert!(select_session_mode(Some("unrestricted"), host, false).is_err());
}
assert!(select_session_mode(Some("unrestricted"), Host::Unrestricted, true).is_err());
assert!(select_session_mode(None, Host::Standard, true).is_err());
assert!(select_session_mode(Some("standard"), Host::Standard, true).is_err());
}

#[test]
fn envelope_mode_rejects_unknown_and_bounded_values() {
use cua_driver_core::authorization::PermissionMode;
for value in ["", "bounded", "UNRESTRICTED", " unrestricted", "inherit"] {
assert!(select_session_mode(Some(value), PermissionMode::Unrestricted, false).is_err());
}
}

#[test]
fn envelope_startup_mode_requires_explicit_acknowledgement() {
const CHILD: &str = "CUA_TEST_ENVELOPE_MODE_CHILD";
if let Ok(expected) = std::env::var(CHILD) {
let selected = configured_session_mode();
match expected.as_str() {
"standard" => assert_eq!(
selected.unwrap(),
cua_driver_sdk::SessionPermissionMode::Standard
),
"unrestricted" => assert_eq!(
selected.unwrap(),
cua_driver_sdk::SessionPermissionMode::Unrestricted
),
"error" => assert!(selected.is_err()),
"manifest_error" => {
assert!(
cua_driver_core::session_manifest::configured_capability_manifest()
.unwrap()
.is_some()
);
assert!(selected.is_err());
}
_ => panic!("unknown synthetic test expectation"),
}
return;
}
// Startup mode is process-cached; each case must get a fresh process.
for (requested, host, acknowledged, manifest, expected) in [
(None, "standard", false, false, "standard"),
(None, "unrestricted", true, false, "standard"),
(Some("unrestricted"), "standard", false, false, "error"),
(Some("unrestricted"), "unrestricted", false, false, "error"),
(
Some("unrestricted"),
"unrestricted",
true,
false,
"unrestricted",
),
(Some("bounded"), "unrestricted", true, false, "error"),
(None, "standard", false, true, "manifest_error"),
(
Some("unrestricted"),
"unrestricted",
true,
true,
"manifest_error",
),
] {
use std::io::Write;
let mut file = tempfile::NamedTempFile::new().unwrap();
file.write_all(b"version: 3\nallow:\n tools: [get_config]\n")
.unwrap();
let mut command = std::process::Command::new(std::env::current_exe().unwrap());
command
.args([
"--exact",
"driver_service_http::tests::envelope_startup_mode_requires_explicit_acknowledgement",
"--nocapture",
])
.env(CHILD, expected)
.env("CUA_DRIVER_PERMISSION_MODE", host)
.env("CUA_DRIVER_DANGEROUSLY_BYPASS_APPROVALS", if acknowledged { "1" } else { "0" })
.env_remove("CUA_DRIVER_ENVELOPE_PERMISSION_MODE")
.env_remove("CUA_DRIVER_CAPABILITY_MANIFEST_FILE")
.env_remove("CUA_DRIVER_SESSION_POLICY_FILE");
if manifest {
command.env("CUA_DRIVER_CAPABILITY_MANIFEST_FILE", file.path());
}
if let Some(requested) = requested {
command.env("CUA_DRIVER_ENVELOPE_PERMISSION_MODE", requested);
}
let output = command.output().unwrap();
assert!(
output.status.success(),
"startup mode case failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
}

struct Fake(Arc<AtomicUsize>);
#[async_trait::async_trait]
impl DriverEnvelopeExecutor for Fake {
Expand Down
7 changes: 4 additions & 3 deletions libs/cua-driver/rust/crates/cua-driver/src/sdk_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,20 @@ pub struct SdkAdapter {
impl SdkAdapter {
pub fn create_envelope_receiver(
&self,
mode: cua_driver_sdk::SessionPermissionMode,
) -> Result<
(
Arc<cua_driver_sdk::remote_receiver::DriverEnvelopeReceiver>,
String,
),
String,
> {
// The private HTTP slice requests only Standard; the runtime's immutable
// ceiling rejects incompatible hosts rather than widening their policy.
// Only the trusted launcher selects this mode. The runtime's immutable
// ceiling still rejects incompatible sessions.
let public_session = format!("http-{}", uuid::Uuid::new_v4());
let options = TrustedSessionOptions {
public_session: public_session.clone(),
mode: cua_driver_sdk::SessionPermissionMode::Standard,
mode,
ttl_seconds: 3600,
idle_ttl_seconds: 300,
capability_manifest_path: None,
Expand Down
9 changes: 5 additions & 4 deletions libs/python/cua-sandbox/cua_sandbox/interfaces/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@ def _sdk() -> Any:
class Driver:
"""Accessor for optional typed Driver sessions; does not replace Sandbox transport.

Each connection is a host-bound Standard session. Use typed inputs with
``session=None`` where optional. For required session fields, obtain the
bound name with ``sandbox.driver.session_name(driver)``. Creating or
Each connection is a host-bound session with a launcher-selected permission
mode (Standard by default). Remote clients cannot select its authority.
Use typed inputs with ``session=None`` where optional. For required session
fields, obtain the bound name with ``sandbox.driver.session_name(driver)``. Creating or
rebinding trusted sessions is unsupported. Remote cleanup is best effort
with a bounded timeout; failures warn and do not block claim release.
"""
Expand Down Expand Up @@ -283,7 +284,7 @@ async def negotiate(self):

async def bind_session(self, options):
raise self.fail(
"Fleet Driver connections use a host-bound Standard session; rebinding is unsupported"
"Fleet Driver connections use a host-bound session; rebinding is unsupported"
)

async def exchange(self, request):
Expand Down
7 changes: 5 additions & 2 deletions libs/python/cua-sandbox/docs/typed-driver-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ async def inspect_guest(pool):
`driver` is the generated `cua_driver.CuaDriver`, not a parallel desktop API.
Use `session=None` for optional session fields. For required session fields,
`session_name(driver)` returns the active connection's host-bound label. The
carrier creates a Standard session; remote permission grants, new trusted
sessions, and session rebinding are not supported.
carrier creates a Standard session by default. Only the trusted launcher can
opt into Unrestricted mode, with an explicitly acknowledged Unrestricted daemon;
remote clients cannot select session authority. Bounded mode and carrier manifest
configuration are unsupported, as are remote permission grants, new trusted
sessions, and session rebinding.

## Failure and cleanup behavior

Expand Down
2 changes: 1 addition & 1 deletion libs/python/cua-sandbox/tests/test_typed_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ async def test_cancelled_request_never_dispatches_and_cancel_after_close_is_loca

async def test_bind_session_is_unsupported_without_dispatch(sandbox):
async with sandbox.driver.connect() as driver:
with pytest.raises(ChannelError, match="Standard.*unsupported"):
with pytest.raises(ChannelError, match="rebinding is unsupported"):
await driver.channel.bind_session(object())
assert len(sandbox._transport.events) == 1

Expand Down
Loading