diff --git a/README.md b/README.md index a61a3f3a..d2c67f2b 100644 --- a/README.md +++ b/README.md @@ -310,7 +310,7 @@ By default a remote run uses **credential routing**: only requests that need a S stashbase agent run --remote --remote-mode full --profile coding -- codex ``` -`--remote-mode full` sends all agent traffic through the remote proxy, so the remote side sees and enforces policy on everything. Which modes you can use depends on your plan (Free: local only, Pro: credential routing, Scale: credential routing or full) and on workspace policy, which can enforce a mode. If a mode is not allowed, the CLI says which one is. In credential mode, requests to a host that is not routed cannot carry a credential placeholder; the relay refuses them (`proxy.credential_host_not_routed`) instead of sending the placeholder upstream. The audit log records the session's routing mode and, for each event, whether it went `remote` or `direct`; `stashbase agent audit --group-by route` summarizes the split. A remote HTTPS tunnel is opaque to the local relay, so for remote hosts the local log shows the connection and the requests inside it appear in the remote-side logs, while direct hosts get full per-request local logging. If Stashbase does not return a host list (an older service), the CLI falls back to `full` and says so. +`--remote-mode full` sends all agent traffic through the remote proxy, so the remote side sees and enforces policy on everything. Both modes are available on paid plans, and workspace policy can enforce one of them. A session also runs in `full` when a credential binding covers every host (`hosts = ["*"]` in the binding, not `egress_hosts = ["*"]`, which doesn't affect routing) and so can't be listed. If a mode is not allowed, the CLI says which one is. In credential mode, requests to a host that is not routed cannot carry a credential placeholder; the relay refuses them (`proxy.credential_host_not_routed`) instead of sending the placeholder upstream. The audit log records the session's routing mode and, for each event, whether it went `remote` or `direct`; `stashbase agent audit --group-by route` summarizes the split. A remote HTTPS tunnel is opaque to the local relay, so for remote hosts the local log shows the connection and the requests inside it appear in the remote-side logs, while direct hosts get full per-request local logging. If Stashbase does not return a host list (an older service), the CLI falls back to `full` and says so. Remote Agent Proxy is not a general network sandbox; it relays supported HTTP traffic for supported coding-agent workflows. SSH, raw TCP, and arbitrary third-party integrations are unsupported. diff --git a/src/handlers/entry/root.rs b/src/handlers/entry/root.rs index 2f949d9e..d4474848 100644 --- a/src/handlers/entry/root.rs +++ b/src/handlers/entry/root.rs @@ -1164,7 +1164,7 @@ pub async fn handle_cli(args: Cli) -> Exit { } }; if !silent { - eprintln!("{}", routing_summary(&routing, session_request.routing_mode, session.route_hosts.is_some())); + eprintln!("{}", routing_summary(&routing, session_request.routing_mode, session.routing_mode.is_some())); } crate::telemetry::set_agent_run_remote_mode(routing.mode); let routing = Arc::new(RwLock::new(routing)); @@ -2138,7 +2138,7 @@ fn ensure_replacement_session_is_compatible( fn routing_summary( routing: &crate::handlers::run::routing::RemoteRouting, requested: Option, - sent_route_hosts: bool, + backend_sent_mode: bool, ) -> String { use crate::handlers::run::routing::RemoteMode; match routing.mode { @@ -2153,7 +2153,7 @@ fn routing_summary( "Routing: credential ({count} {noun} {verb} the Remote Agent Proxy; other traffic goes direct)" ) } - RemoteMode::Full if requested == Some(RemoteMode::Credential) && !sent_route_hosts => { + RemoteMode::Full if requested == Some(RemoteMode::Credential) && !backend_sent_mode => { "Routing: full (this Remote Agent Proxy does not support credential routing yet)" .to_owned() } @@ -2916,6 +2916,12 @@ mod tests { super::routing_summary(&RemoteRouting::full(), Some(RemoteMode::Full), true), "Routing: full (all traffic uses the Remote Agent Proxy)" ); + // The service chose `full` itself (for example a binding that covers every + // host), so it does support credential routing. + assert_eq!( + super::routing_summary(&RemoteRouting::full(), Some(RemoteMode::Credential), true), + "Routing: full (all traffic uses the Remote Agent Proxy)" + ); assert_eq!( super::routing_summary(&RemoteRouting::full(), Some(RemoteMode::Credential), false), "Routing: full (this Remote Agent Proxy does not support credential routing yet)"