From c21a65b7774918c9ca6f1436b17e7147da43c5fe Mon Sep 17 00:00:00 2001 From: Audrey Sage Lorberfeld <18585860+aulorbe@users.noreply.github.com> Date: Tue, 26 May 2026 16:27:01 -0700 Subject: [PATCH] feat(local): warn users before executing commands on their machine (#3433) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Replaces the openclaw-only local installation warning with a universal warning that applies to **all** agents spawned locally - Presents users with a 3-option selector before proceeding: - **Ok** — execute directly on this machine (no change in behavior) - **Sandbox** — inject `--beta sandbox` so the agent runs inside a Docker container instead of on the host - **Cancel** — abort the operation - Warning is skipped when sandbox mode is already active or in non-interactive/headless mode (`SPAWN_NON_INTERACTIVE=1`) - When "Sandbox" is selected, `SPAWN_BETA` env var is updated *before* agent/runner resolution so the Docker-wrapped runner is used correctly ## Test plan - [x] `biome lint` — 203 files, 0 issues - [x] `biome check` — formatting clean - [x] Build compiles without errors - [x] 97 orchestrate/sandbox tests pass - [ ] Manual: `spawn local` shows warning with Ok/Sandbox/Cancel - [ ] Manual: selecting Ok proceeds normally - [ ] Manual: selecting Sandbox enables Docker container mode - [ ] Manual: selecting Cancel exits cleanly - [ ] Manual: `--beta sandbox` skips the warning entirely - [ ] Manual: headless/non-interactive mode skips the warning --- packages/cli/src/local/run.ts | 59 +++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/packages/cli/src/local/run.ts b/packages/cli/src/local/run.ts index 0611002f..9a671a55 100644 --- a/packages/cli/src/local/run.ts +++ b/packages/cli/src/local/run.ts @@ -37,6 +37,45 @@ import { * host (the `local` cloud). */ export async function runLocalAgent(agentName: string, useSandbox: boolean): Promise { + // Warn that local spawning executes commands directly on the user's machine. + // Skip in non-interactive mode (headless / CI) and when sandbox is already active. + if (!useSandbox && process.env.SPAWN_NON_INTERACTIVE !== "1") { + process.stderr.write("\n"); + logWarn("⚠ Local execution warning"); + logWarn(" Spawning locally will execute commands directly on this machine."); + logWarn(" The agent will have full access to your filesystem, shell, and network.\n"); + + const action = await p.select<"ok" | "sandbox" | "cancel">({ + message: "How would you like to proceed?", + options: [ + { + value: "ok", + label: "Ok", + hint: "proceed — execute directly on this machine", + }, + { + value: "sandbox", + label: "Sandbox", + hint: "run inside a Docker container instead", + }, + { + value: "cancel", + label: "Cancel", + hint: "abort the operation", + }, + ], + }); + + if (p.isCancel(action) || action === "cancel") { + p.log.info("Operation cancelled."); + process.exit(0); + } + + if (action === "sandbox") { + return runLocalAgent(agentName, true); + } + } + const baseRunner = { runServer: runLocal, uploadFile: async (l: string, r: string) => uploadFile(l, r), @@ -55,26 +94,6 @@ export async function runLocalAgent(agentName: string, useSandbox: boolean): Pro await ensureDocker(); } - // Warn about security implications of installing OpenClaw locally - // (skip warning in sandbox mode — the container provides isolation) - if (agentName === "openclaw" && !useSandbox && process.env.SPAWN_NON_INTERACTIVE !== "1") { - process.stderr.write("\n"); - logWarn("⚠ Local installation warning"); - logWarn(` This will install ${agent.name} directly on your machine.`); - logWarn(" The agent will have full access to your filesystem, shell, and network."); - logWarn(" For isolation, consider running on a cloud VM instead.\n"); - - const confirmed = await p.confirm({ - message: "Continue with local installation?", - initialValue: true, - }); - - if (p.isCancel(confirmed) || !confirmed) { - p.log.info("Installation cancelled."); - process.exit(0); - } - } - const cloud: CloudOrchestrator = { cloudName: "local", cloudLabel: useSandbox ? "local (sandboxed)" : "local",