Skip to content

Sandbox implementation

Local sandboxing is an execution constraint applied after a tool request has been approved. In Copilot CLI 1.0.71, the effective sandbox config is shared by shell execution, selected built-in file/search tools, LSP servers, MCP servers, and web_fetch; native runtime APIs build and spawn the policy-constrained process.

Read Shell command execution events for sync/async/detached shell task behavior and Tool, path, and URL permissions for approval before execution.

This document answers a narrow reverse-engineering question: does the extracted Copilot CLI implement sandboxing, and if so, how is it wired?

The answer is yes. The current bundle exposes persistent settings, /sandbox, one-run --sandbox/--no-sandbox overrides, per-operation bypass requests, and native policy/spawn APIs. The package no longer ships the old mxc-bin helper tree; that implementation is retained below only as historical 1.0.54 context.

However, the implementation is easy to misread because the word “sandbox” appears in three different contexts:

  1. Local command sandboxing: /sandbox enable toggles sandbox.enabled, which can route shell sessions through the MXC sandbox spawn path.
  2. Cloud sandbox sessions: hidden --cloud creates or connects to a remote/cloud sandbox session. This is a separate session-provisioning feature.
  3. Prompt-level sandbox wording: some system prompts say the agent is or is not in a sandboxed environment. These strings describe context for the model; they are not the enforcement layer.

The rest of this document focuses on local command sandboxing and calls out where it differs from cloud sessions, permissions, and prompt text.

BoundaryThis page says
Local command sandboxSettings and shell config can route shell spawn through MXC.
Cloud sandboxSeparate remote/cloud session provisioning path; not the same enforcement layer.
Prompt wordingContext text only; not enforcement.
Permission serviceApproval happens before spawn; sandbox constrains the approved process afterward.

Source anchors

app.js is bundled and minified, so this document uses semantic aliases in prose and diagrams. Minified names are kept only in this table for searching the analyzed @github/copilot artifact and may shift across releases.

AreaSemantic aliasMinified anchorApprox. lineRole
Effective configSandbox normalizationZ2(...), FT(...), sandboxConfigsEqual122Resolves enabled state and compares live configuration.
One-run CLI overrideRoot options--sandbox, --no-sandbox5821, 5899Overrides the current session without changing saved settings.
Shell planNative shell managertoolShellPlanExecution, requestSandboxBypass206-207Plans normal, sandboxed, or approved-bypass execution.
Native policy spawnSandbox process adapterS.sandboxBuildPolicy(...).spawn(...)122Spawns through the native policy runtime and adapts stdio handles.
Web fetch enforcementQMe.resolveSandbox()requestSandboxBypass, sandbox_denied140Applies active network policy to outbound/local fetches.
LSP enforcementLSP sandbox configX2(...), sandboxLspServers, renameApplied122, 216-222Starts LSPs in the effective sandbox and checks read/rename effects.
MCP enforcementMCP sandbox confighG(...), sandboxMcpServers122, 2603Applies or restarts MCP servers when sandbox policy changes.
Bypass UISandbox approval dialogrequestSandboxBypass, disable sandbox for session3628Supports one-time bypass or disabling the sandbox for the live session when policy allows.
Alpine guardmusl checkSandboxed shell execution is not supported on Alpine Linux/musl608Rejects an unsupported runtime/platform combination explicitly.

What exists and what does not

QuestionFinding
Is there a sandbox implementation?Yes. There is config schema, a slash command, runtime propagation, a shell spawn guard, MXC policy/config construction, and bundled sandbox binaries.
Is there a normal root --sandbox CLI flag?Yes. --sandbox and --no-sandbox override the saved value for one session and do not persist it.
Is /sandbox always visible?No. The slash command is filtered by the SANDBOX feature gate, which defaults to off in the static gate table.
Is local sandboxing the same as --cloud?No. --cloud provisions a cloud sandbox session; /sandbox toggles local shell-command sandbox settings.
Does the prompt text enforce sandboxing?No. Prompt wording can describe a sandboxed environment, but enforcement happens at shell process spawn time.
Does it replace the permission service?No. Permissions decide whether a request may execute; the sandbox constrains what the spawned process can access after execution starts.

Current 1.0.71 enforcement map

flowchart TD
Settings[settings sandbox config] --> Override{CLI override?}
Override -->|--sandbox| On[enabled for this session]
Override -->|--no-sandbox| Off[disabled for this session]
Override -->|none| Effective[persisted effective config]
On --> Policy[native sandbox policy]
Effective --> Policy
Policy --> Shell[shell manager]
Policy --> LSP[LSP processes and file effects]
Policy --> MCP[MCP server processes]
Policy --> Fetch[web_fetch network policy]
Policy --> Files[built-in edit/search operations]
Shell --> Bypass{operation requests bypass?}
Fetch --> Bypass
Files --> Bypass
Bypass -->|approved and allowBypass| Direct[run outside policy once]
Bypass -->|not approved| Constrained[run constrained or deny]

The policy is no longer only a shell-session choice. Session option updates propagate sandbox changes to shell, LSP, and MCP managers; web_fetch resolves the live config on each call. Tools can request a bypass only when sandbox.allowBypass is enabled, and the permission UI records whether the operation ran sandboxed or bypassed.

The extracted package now delegates core sandbox behavior to the native runtime (prebuilds/linux-x64/runtime.node) rather than package-local mxc-bin helpers. Runtime source still exposes policy construction, spawn, equality, telemetry, and denial helpers through S.sandbox* APIs, but the implementation behind those calls is outside app.js.

Historical 1.0.54 MXC/PTY path

The sections below document the old package-local helper and PTY architecture for delta research. They are not the active 1.0.71 implementation.

Historical terminology map

flowchart TD
SandboxWord["The word sandbox"] --> Local["Local command sandbox"]
SandboxWord --> Cloud["Cloud sandbox session"]
SandboxWord --> Prompt["Prompt-level wording"]
Local --> LocalEntry["/sandbox enable or settings.sandbox.enabled"]
Local --> LocalEnforce["MXC-backed shell process spawn"]
Cloud --> CloudEntry["hidden --cloud option"]
Cloud --> CloudProvision["remote task and sandbox provisioning"]
Prompt --> PromptStrings["system prompt context text"]
Prompt --> NoEnforce["no process isolation by itself"]

Historical entry points and visibility

Local command sandboxing starts from configuration, not from a root CLI flag.

The settings schema accepts a sandbox object with these observed fields:

SettingMeaning in the traced flow
sandbox.enabledMain toggle. When true, the TUI session receives sandboxConfig.
sandbox.filesystem.readwritePathsAdditional paths added to the generated read-write filesystem policy.
sandbox.filesystem.readonlyPathsAdditional paths added to the generated read-only filesystem policy.
sandbox.filesystem.deniedPathsPaths passed through as denied filesystem paths.
sandbox.filesystem.clearPolicyOnExitControls whether the generated filesystem policy is cleaned up on exit; defaults to true in base policy construction.
sandbox.policyRaw policy object. The runtime can convert it to an MXC config and inject the shell command.
sandbox.configRaw MXC config object. The runtime can inject the shell command and working directory directly.
sandbox.addCurrentWorkingDirectoryIf not false, the explicit-policy path ensures the current working directory is included as read-write.

The /sandbox slash command is the user-facing toggle when the gate is active:

  • /sandbox with no argument reports the current status.
  • /sandbox enable writes sandbox.enabled: true.
  • /sandbox disable writes sandbox.enabled: false.
  • It returns runtimeSettingsChanged: true, signaling that runtime settings have changed.
flowchart TD
StaticGate["FEATURE_AVAILABILITY_TIERS\nSANDBOX = off"] --> GateResolver["feature flag resolution"]
GateResolver --> SlashBuilder["SlashCommandBuilder"]
SlashBuilder --> Visible{"SANDBOX enabled?"}
Visible -->|no| Hidden["/sandbox omitted from slash list"]
Visible -->|yes| Slash["/sandbox command visible"]
Slash --> Arg{"argument"}
Arg -->|none| Status["read sandbox.enabled"]
Arg -->|enable| Enable["write sandbox.enabled = true"]
Arg -->|disable| Disable["write sandbox.enabled = false"]
Enable --> Changed["runtimeSettingsChanged = true"]
Disable --> Changed

Historical runtime propagation

Once enabled in settings, sandbox configuration is carried into the session and then into the shell tool configuration.

sequenceDiagram
autonumber
participant Settings as Runtime settings
participant TUI as Interactive session options
participant Session as Session runtime
participant Factory as ShellConfigFactory
participant ShellConfig as ShellConfig
participant Tools as ShellToolsFactory
Settings->>TUI: sandbox.enabled and sandbox settings
TUI->>Session: sandboxConfig when enabled
Session->>Session: store sandboxConfig and clear resolvedShellConfig
Session->>Factory: getOrCreateShellConfig()
Factory->>Factory: choose PowerShell on Windows, Bash otherwise
Factory->>ShellConfig: withSandbox(sandboxConfig)
ShellConfig-->>Session: shell config with sandbox object
Session->>Tools: toolConfig.shellConfig
Tools->>Tools: use sandbox-capable PTY path when sandbox.enabled

Two details matter here:

  1. ShellConfigFactory defaults to PowerShell on Windows and Bash on non-Windows platforms.
  2. The shell tools factory only takes the alternate non-PTY spawn backend when sandboxing is not enabled. With sandboxing enabled, it stays on the interactive shell session creation path that can invoke the sandbox adapter.

For the full command lifecycle around this branch — including PTY vs non-TTY backend selection, sync/async/detached execution, background promotion, and output buffering — see Shell command execution events.

Historical shell execution flow

The enforcement point is shell session creation. When sandboxing is disabled, the runtime spawns a normal PTY shell. When sandboxing is enabled, it first checks the shell type.

flowchart TD
ToolCall["shell tool request"] --> ShellContext["ShellToolsFactory / shell context"]
ShellContext --> SessionFactory["create shell session"]
SessionFactory --> Enabled{"shellConfig.sandbox.enabled?"}
Enabled -->|no| Normal["normal node-pty spawn"]
Enabled -->|yes| ShellType{"shell type is PowerShell?"}
ShellType -->|no| Error["throw: sandboxing is only supported for PowerShell shells"]
ShellType -->|yes| Adapter["SandboxedShellSpawner"]
Adapter --> LoadMxc["load MXC adapter and set MXC_BIN_DIR"]
LoadMxc --> Support{"platform supported?"}
Support -->|no| Unsupported["throw unsupported-platform error"]
Support -->|yes| Policy["build or accept sandbox config/policy"]
Policy --> Spawn["spawnSandboxFromConfig"]

The observed guard is explicit: local command sandboxing is only supported for PowerShell shells in this shell runner. Because the default non-Windows shell config is Bash, a default Linux CLI session with sandbox.enabled would not start a sandboxed Bash shell; it would hit the PowerShell-only error path.

This caveat should be read carefully. The bundled MXC library contains Linux/LXC support, but the CLI shell-session branch traced here requires PowerShell before calling the sandbox adapter.

Historical MXC adapter and platform support

The CLI embeds and loads an MXC sandbox package. Before spawning, it sets MXC_BIN_DIR to the package-local mxc-bin directory if that directory exists. The extraction manifest confirms the package includes sandbox-related binaries, including:

Asset familyExamples from extraction manifest
Linux LXC executorcopilot-cli-pkg/mxc-bin/arm64/lxc-exec, copilot-cli-pkg/mxc-bin/x64/lxc-exec
Windows executorcopilot-cli-pkg/mxc-bin/arm64/wxc-exec.exe, copilot-cli-pkg/mxc-bin/x64/wxc-exec.exe
Windows sandbox helperswxc-windows-sandbox-daemon.exe, wxc-windows-sandbox-guest.exe for arm64 and x64
WSL-related assetswslcsdk.dll for arm64 and x64

The platform support helper reports:

  • macOS: unsupported;
  • Linux: supported only if LXC is available, with lxc as the available method;
  • Windows: supported on suitable Windows builds, with appcontainer as the available method.

The policy-to-config converter can create different containment configs, including the default process containment, Linux LXC, Windows AppContainer, WSL container, and an explicit microVM path. The microVM path is Windows-only and has additional constraints.

For the normal local shell path on Windows, the default shell is PowerShell, so the PowerShell-only guard can pass and the default process containment path maps to Windows AppContainer behavior.

Binary-level evidence about packaged MXC helper fingerprints is kept with repository research notes outside the final wiki.

Historical policy construction

The sandboxed shell spawner has three configuration modes.

flowchart TD
Start["SandboxedShellSpawner"] --> RawConfig{"sandbox.config provided?"}
RawConfig -->|yes| Config["clone raw config"]
Config --> InjectConfig["inject process.commandLine and cwd"]
InjectConfig --> SpawnConfig["spawn explicit config"]
RawConfig -->|no| RawPolicy{"sandbox.policy provided?"}
RawPolicy -->|yes| Policy["clone raw policy"]
Policy --> AddCwd{"addCurrentWorkingDirectory !== false?"}
AddCwd -->|yes| EnsureCwd["ensure cwd is read-write"]
AddCwd -->|no| KeepPolicy["preserve provided filesystem policy"]
EnsureCwd --> Normalize["realpath existing read/write and read-only paths"]
KeepPolicy --> Normalize
Normalize --> Convert["createConfigFromPolicy"]
Convert --> SpawnPolicy["spawn explicit policy"]
RawPolicy -->|no| Base["build base policy"]
Base --> ToolPaths["available tool paths as read-only"]
Base --> UserProfile["selected user-profile paths as read-only"]
Base --> Cwd["current cwd as read-write"]
Base --> Temp["temporary-files path as read-write"]
Base --> Custom["merge custom filesystem paths"]
Custom --> BasePolicy["network outbound allowed; UI windows allowed"]
BasePolicy --> SpawnBase["spawn generated policy"]

Explicit raw config

When sandbox.config is present, the runtime clones it and injects the command line for the shell process. If the config lacks process.cwd, it fills in the current working directory. It then calls the MXC spawn function directly.

This mode gives the caller the most control because the config is already in MXC’s lower-level shape.

Explicit raw policy

When sandbox.policy is present, the runtime clones it, optionally ensures the current working directory is read-write, normalizes existing filesystem paths through realpath where possible, converts the policy into an MXC config, injects the command line, and spawns.

If the policy has no filesystem section and addCurrentWorkingDirectory is not false, the runtime creates a filesystem section with the cwd as a read-write path.

Generated base policy

When neither raw config nor raw policy is present, the runtime builds a base policy:

Policy fieldObserved behavior
versionUses policy version 0.5.0-alpha.
network.allowOutboundSet to true in the generated base policy.
ui.allowWindowsSet to true in the generated base policy.
filesystem.readonlyPathsExisting paths from available tools, user-profile policy, and custom readonlyPaths.
filesystem.readwritePathsExisting paths from current cwd, temp-file policy, and custom readwritePaths.
filesystem.deniedPathsCustom deniedPaths, if any.
filesystem.clearPolicyOnExitCustom value, defaulting to true.

Path normalization filters to existing directories and realpaths them when possible. That makes the policy less dependent on symlinks and non-existent paths.

Historical local sandbox, cloud sandbox, and permissions

flowchart LR
UserIntent["user or model action"] --> Permission["PermissionService"]
Permission -->|approved| LocalShell["local shell command"]
Permission -->|denied| Stop["request denied before execution"]
LocalShell --> SandboxEnabled{"local sandbox enabled?"}
SandboxEnabled -->|no| NormalShell["normal shell process"]
SandboxEnabled -->|yes| Mxc["MXC sandboxed shell process"]
UserIntent --> CloudFlag["hidden --cloud"]
CloudFlag --> Cloud["remote cloud sandbox session"]
PromptText["prompt says sandboxed"] --> ModelContext["model context only"]

These layers answer different questions:

LayerQuestion it answers
Permission serviceMay this requested tool/path/URL/shell operation be attempted?
Local command sandboxIf a shell process runs, what filesystem/process/network/UI access does the spawned process get?
Cloud sandbox sessionShould the whole session run against remote sandbox-backed compute rather than only the local workspace?
Prompt wordingWhat environment assumptions should the model follow?

The layers are complementary. Enabling a sandbox does not automatically approve shell commands, and approving a shell command does not imply the spawned process is sandboxed.

Historical platform and exposure caveats

  • The SANDBOX gate defaults to off, so /sandbox is not part of the default slash-command list unless enabled through feature-flag mechanisms.
  • A public root --sandbox flag was not observed in captured help or root option construction.
  • Local command sandboxing is enforced through the shell spawn path, not through file tools or the permission service directly.
  • The traced shell runner requires PowerShell for local sandboxed shell sessions.
  • On Linux, the default CLI shell config is Bash. Therefore, a default Linux local session with sandbox.enabled would hit the PowerShell-only guard before reaching the MXC adapter.
  • The embedded MXC platform-support code has Linux/LXC logic, but that does not override the CLI shell runner’s PowerShell guard.
  • --cloud is a separate, hidden cloud-session feature controlled by cloud-session logic and the CLI_CLOUD_SESSIONS gate, not by /sandbox.
  • The generated base local sandbox policy allows outbound network and UI windows; it is not a no-network/no-UI sandbox by default.

Historical takeaways

The extracted CLI has a real sandbox implementation, but it is not a broad, always-on OS isolation layer.

In this bundle, local command sandboxing is best understood as:

  1. feature-gated UI through /sandbox;
  2. settings-backed state through sandbox.enabled and related filesystem/policy/config fields;
  3. session-shell plumbing through sandboxConfig and ShellConfig.withSandbox(...);
  4. PowerShell-gated shell spawning through the interactive shell session creator;
  5. MXC-backed enforcement through generated or explicit sandbox policies.

For nearby systems, see tool-path-url-permissions.md for approval semantics, sessions-remote-cloud.md for cloud sandbox sessions, feature-gates.md for gate resolution, tui-and-slash-commands.md for slash-command hosting, prompt-sources.md for prompt wording, and Main feature map for the broader feature map.

Created and maintained by Yingting Huang.