From ed76a9c6178209dd1df8297d01e2a96ba5ef0040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radim=20H=C3=B6fer?= Date: Mon, 5 Oct 2026 09:38:56 +0200 Subject: [PATCH 1/2] fix(agent): only say credential routing is unsupported when the service sent no mode The Routing line blamed the service for lacking credential routing whenever route_hosts was missing. A service that supports it also answers full for a binding that covers every host, with no route_hosts. Key the message off routing_mode instead, and drop the per-plan routing modes from the README since both modes are available on paid plans. --- README.md | 2 +- src/handlers/entry/root.rs | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a61a3f3a..618cb9eb 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 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)" From 1aa1c236d23616d2f3952a419d00b5f9df783ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radim=20H=C3=B6fer?= Date: Mon, 5 Oct 2026 09:45:56 +0200 Subject: [PATCH 2/2] docs(agent): say which wildcard forces full routing A * in a binding's hosts forces full routing; egress_hosts = ["*"] does not affect it. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 618cb9eb..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. 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 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-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.