From 55b8a1db92fdaf21ffaba326754a5e3a28616ab6 Mon Sep 17 00:00:00 2001 From: Gabriel Fonseca Date: Wed, 11 Nov 2026 00:52:13 +0000 Subject: [PATCH] feat(deploy): refuse host-side agents unless explicitly allowed - The server refuses FRONTAL_CODE_SERVER_LANE_TRANSPORT=tools-agent unless FRONTAL_CODE_SERVER_ALLOW_HOST_AGENT=1 is set. local-docker stays the default. - Managed deploy configs (Fly, Kubernetes, Terraform, deploy.yaml) set the opt-in, because their VM or pod is the isolation boundary. - The compose default is local-docker. It needs the docker-worker override for the socket proxy. - docs/CONTAINERS.md describes the default and the opt-in. Co-Authored-By: Claude Haiku 5.5 --- crates/code-server/src/lib.rs | 21 +++++++++++++++++++ deploy/deploy.yaml | 3 +++ deploy/fly.toml | 2 ++ docs/CONTAINERS.md | 2 ++ infrastructure/compose/.env.example | 2 +- infrastructure/compose/docker-compose.yml | 2 +- infrastructure/kubernetes/base/configmap.yaml | 2 ++ .../terraform/frontal-code-server.tf | 2 ++ 8 files changed, 34 insertions(+), 2 deletions(-) diff --git a/crates/code-server/src/lib.rs b/crates/code-server/src/lib.rs index 0d0e1c8..49f22ba 100644 --- a/crates/code-server/src/lib.rs +++ b/crates/code-server/src/lib.rs @@ -182,6 +182,14 @@ fn parse_lane_transport_kind(raw: &str) -> Result { } } +/// Refuses `tools-agent` unless the operator opted in with `FRONTAL_CODE_SERVER_ALLOW_HOST_AGENT`. +fn check_host_agent_opt_in(kind: LaneTransportKind, allowed: bool) -> Result<(), String> { + if kind == LaneTransportKind::ToolsAgent && !allowed { + return Err("FRONTAL_CODE_SERVER_LANE_TRANSPORT=tools-agent runs agents with the server's privileges; set FRONTAL_CODE_SERVER_ALLOW_HOST_AGENT=1 to allow it, or use local-docker".to_string()); + } + Ok(()) +} + impl Default for ServerConfig { fn default() -> Self { Self { @@ -231,6 +239,12 @@ impl ServerConfig { if let Ok(kind) = env::var("FRONTAL_CODE_SERVER_LANE_TRANSPORT") { config.lane_transport_kind = parse_lane_transport_kind(&kind)?; } + // `tools-agent` runs the agent inside the server process with its privileges. It is + // refused unless the operator opts in, for example because the server already runs + // inside an isolated VM. `local-docker` (the default) isolates each task in a container. + let host_agent_allowed = env::var("FRONTAL_CODE_SERVER_ALLOW_HOST_AGENT") + .is_ok_and(|value| matches!(value.trim(), "1" | "true")); + check_host_agent_opt_in(config.lane_transport_kind, host_agent_allowed)?; if let Ok(api_key) = env::var("FRONTAL_CODE_SERVER_API_KEY") { let api_key = api_key.trim(); @@ -5284,6 +5298,13 @@ mod tests { } } + #[test] + fn host_agent_transport_requires_an_explicit_opt_in() { + assert!(check_host_agent_opt_in(LaneTransportKind::ToolsAgent, false).is_err()); + assert!(check_host_agent_opt_in(LaneTransportKind::ToolsAgent, true).is_ok()); + assert!(check_host_agent_opt_in(LaneTransportKind::LocalDocker, false).is_ok()); + } + use super::*; use axum::body::to_bytes; use axum::body::Body; diff --git a/deploy/deploy.yaml b/deploy/deploy.yaml index d617320..982a388 100644 --- a/deploy/deploy.yaml +++ b/deploy/deploy.yaml @@ -6,6 +6,7 @@ env: FRONTAL_CODE_SERVER_HOST: "0.0.0.0" FRONTAL_CODE_SERVER_PORT: "8788" FRONTAL_CODE_SERVER_LANE_TRANSPORT: "tools-agent" + FRONTAL_CODE_SERVER_ALLOW_HOST_AGENT: "1" FRONTAL_CODE_SERVER_STATE_FILE: "/var/lib/frontal-code/server/state.json" FRONTAL_CODE_SERVER_WORKSPACE_ROOT: "/workspace/workspaces" FRONTAL_CODE_SERVER_RECONCILE_INTERVAL_SECS: "15" @@ -36,6 +37,7 @@ machines: FRONTAL_CODE_SERVER_HOST: "0.0.0.0" FRONTAL_CODE_SERVER_PORT: "8788" FRONTAL_CODE_SERVER_LANE_TRANSPORT: "tools-agent" + FRONTAL_CODE_SERVER_ALLOW_HOST_AGENT: "1" FRONTAL_CODE_SERVER_STATE_FILE: "/var/lib/frontal-code/server/state.json" FRONTAL_CODE_SERVER_WORKSPACE_ROOT: "/workspace/workspaces" FRONTAL_CODE_SERVER_RECONCILE_INTERVAL_SECS: "15" @@ -59,6 +61,7 @@ machines: FRONTAL_CODE_SERVER_HOST: "0.0.0.0" FRONTAL_CODE_SERVER_PORT: "8788" FRONTAL_CODE_SERVER_LANE_TRANSPORT: "tools-agent" + FRONTAL_CODE_SERVER_ALLOW_HOST_AGENT: "1" FRONTAL_CODE_SERVER_STATE_FILE: "/var/lib/frontal-code/server/state.json" FRONTAL_CODE_SERVER_WORKSPACE_ROOT: "/workspace/workspaces" FRONTAL_CODE_SERVER_RECONCILE_INTERVAL_SECS: "15" diff --git a/deploy/fly.toml b/deploy/fly.toml index bc89d2b..dd0d534 100644 --- a/deploy/fly.toml +++ b/deploy/fly.toml @@ -8,6 +8,8 @@ primary_region = "iad" FRONTAL_CODE_SERVER_HOST = "0.0.0.0" FRONTAL_CODE_SERVER_PORT = "8788" FRONTAL_CODE_SERVER_LANE_TRANSPORT = "tools-agent" + # Fly runs this server in its own microVM, which is the isolation boundary for host-side agents. + FRONTAL_CODE_SERVER_ALLOW_HOST_AGENT = "1" FRONTAL_CODE_SERVER_STATE_FILE = "/var/lib/frontal-code/server/state.json" FRONTAL_CODE_SERVER_WORKSPACE_ROOT = "/workspace/workspaces" FRONTAL_CODE_SERVER_RECONCILE_INTERVAL_SECS = "15" diff --git a/docs/CONTAINERS.md b/docs/CONTAINERS.md index b138f8f..abd5099 100644 --- a/docs/CONTAINERS.md +++ b/docs/CONTAINERS.md @@ -185,6 +185,8 @@ docker manifest push frontal-code/cli:multiarch `code-server` runs each hosted task in its own worker container, built from `docker/worker.Dockerfile`. The image installs both command names, `frontal-code` and `code`; the hosted task runs `frontal-code hosted task run`. This is the default lane transport (`FRONTAL_CODE_SERVER_LANE_TRANSPORT=local-docker`). +`FRONTAL_CODE_SERVER_LANE_TRANSPORT=tools-agent` runs agents inside the server process with its privileges. The server refuses it unless `FRONTAL_CODE_SERVER_ALLOW_HOST_AGENT=1` is set. The managed deploy configs in `deploy/` and `infrastructure/` set that opt-in, because the platform VM or pod is their isolation boundary. + ```bash # Build the worker image from the workspace (the context is limited by .dockerignore) docker build -f docker/worker.Dockerfile -t frontal-code-worker:local . diff --git a/infrastructure/compose/.env.example b/infrastructure/compose/.env.example index d482ff1..8e4e29b 100644 --- a/infrastructure/compose/.env.example +++ b/infrastructure/compose/.env.example @@ -2,7 +2,7 @@ FRONTAL_CODE_SERVER_HOST=0.0.0.0 FRONTAL_CODE_SERVER_PORT=8788 FRONTAL_CODE_SERVER_PUBLISH_ADDR=127.0.0.1 -FRONTAL_CODE_SERVER_LANE_TRANSPORT=tools-agent +FRONTAL_CODE_SERVER_LANE_TRANSPORT=local-docker FRONTAL_CODE_SERVER_API_KEY=replace-with-a-long-random-api-key FRONTAL_CODE_SERVER_WORKSPACE_ROOT=/workspace/workspaces FRONTAL_CODE_SERVER_DOCKER_IMAGE=frontal-code-worker:local diff --git a/infrastructure/compose/docker-compose.yml b/infrastructure/compose/docker-compose.yml index 14df73c..3ffdbe4 100644 --- a/infrastructure/compose/docker-compose.yml +++ b/infrastructure/compose/docker-compose.yml @@ -8,7 +8,7 @@ services: environment: FRONTAL_CODE_SERVER_HOST: ${FRONTAL_CODE_SERVER_HOST:-0.0.0.0} FRONTAL_CODE_SERVER_PORT: ${FRONTAL_CODE_SERVER_PORT:-8788} - FRONTAL_CODE_SERVER_LANE_TRANSPORT: ${FRONTAL_CODE_SERVER_LANE_TRANSPORT:-tools-agent} + FRONTAL_CODE_SERVER_LANE_TRANSPORT: ${FRONTAL_CODE_SERVER_LANE_TRANSPORT:-local-docker} FRONTAL_CODE_SERVER_API_KEY: ${FRONTAL_CODE_SERVER_API_KEY:-} FRONTAL_CODE_SERVER_WORKSPACE_ROOT: ${FRONTAL_CODE_SERVER_WORKSPACE_ROOT:-/workspace/workspaces} FRONTAL_CODE_SERVER_DOCKER_IMAGE: ${FRONTAL_CODE_SERVER_DOCKER_IMAGE:-frontal-code-worker:local} diff --git a/infrastructure/kubernetes/base/configmap.yaml b/infrastructure/kubernetes/base/configmap.yaml index e0d549a..e93b8b4 100644 --- a/infrastructure/kubernetes/base/configmap.yaml +++ b/infrastructure/kubernetes/base/configmap.yaml @@ -6,6 +6,8 @@ data: FRONTAL_CODE_SERVER_HOST: "0.0.0.0" FRONTAL_CODE_SERVER_PORT: "8788" FRONTAL_CODE_SERVER_LANE_TRANSPORT: "tools-agent" + # The pod is the isolation boundary for host-side agents; set to "0" to refuse them. + FRONTAL_CODE_SERVER_ALLOW_HOST_AGENT: "1" FRONTAL_CODE_SERVER_RECONCILE_INTERVAL_SECS: "15" FRONTAL_CODE_SERVER_ORPHAN_APPROVAL_DELAY_SECS: "0" FRONTAL_CODE_SERVER_ORPHAN_AUTO_RETRY_SECS: "0" diff --git a/infrastructure/terraform/frontal-code-server.tf b/infrastructure/terraform/frontal-code-server.tf index 0289015..b16cd04 100644 --- a/infrastructure/terraform/frontal-code-server.tf +++ b/infrastructure/terraform/frontal-code-server.tf @@ -99,6 +99,8 @@ resource "kubernetes_config_map" "frontal-code_server_config" { "FRONTAL_CODE_SERVER_HOST" = "0.0.0.0" "FRONTAL_CODE_SERVER_PORT" = "8788" "FRONTAL_CODE_SERVER_LANE_TRANSPORT" = "tools-agent" + # The managed instance is the isolation boundary for host-side agents. + "FRONTAL_CODE_SERVER_ALLOW_HOST_AGENT" = "1" "FRONTAL_CODE_SERVER_RECONCILE_INTERVAL_SECS" = "15" "FRONTAL_CODE_SERVER_ORPHAN_APPROVAL_DELAY_SECS" = "0" "FRONTAL_CODE_SERVER_ORPHAN_AUTO_RETRY_SECS" = "0"