← Documentation index Architecture guide › sandbox

Metnos

Executor sandbox
How an execution is contained on the server and on paired devices.

The sandbox limits what an executor process can see and modify. Its resources come from the signed manifest and, where needed, the concrete invocation. This layer complements Vaglio, identity checks, and application controls: it does not replace them, nor does it grant permission to perform an action by itself.

Contents

  1. Scope
  2. Module interfaces
  3. Resources exposed on the server
  4. Execution without bubblewrap
  5. Runtime integration
  6. Autonomy and policy
  7. Verification
  8. Diagnostics and installation
  9. Security boundaries
  10. Sandboxing on paired devices

1. Scope

When an executor runs on the Linux server, runtime/sandbox.py can prefix its Python command with bwrap. Bubblewrap builds a separate filesystem view, provides a private /tmp, separates selected namespaces, and adds --unshare-net when the executor does not require network access. There is no separate daemon: a profile is built for every invocation.

When work is placed on a paired device, the Rust client applies the mechanism available on that operating system. The server selects the device and checks that it belongs to the user; the client runs the command and returns the containment level it actually used.

2. Module interfaces

FunctionResponsibility
bwrap_available, sandbox_disabledDetect whether bwrap is present and whether METNOS_SANDBOX explicitly disables it.
wrap_commandReturn a command wrapped by bwrap, or the original command when the wrapper is inactive.
filesystem_extrasResolve only arguments named by a signed fs:read capability in the form arg:<name>. The argument may narrow the scope, never widen it.
undo_history_extrasMake writable only the current turn's managed backup directory when the manifest declares the supported reverse pattern.
invocation_skills, skill_extrasIdentify provider bindings required by this invocation, expose only that integration's home, and enable network access. Declared executors use provider:access; older manifests retain a compatibility path.
mail_extrasFor mail:read, expose the selected accounts' credential files read-only and enable network access. It does not mount the whole credential vault.
dialog_extrasFor dialog.user_input, make writable only the authenticated sender's dialog directory.
python_package_rootsFind the package roots used by the current interpreter without exposing the whole home directory.
statusReturn the path, availability, explicit-disable flag, and effective state of bwrap.

3. Resources exposed on the server

3.1 Execution base

The profile mounts existing system paths read-only from among /usr, /bin, /sbin, the library directories, /etc, /opt, /var/lib/python3, and /sys. It also adds:

--unshare-user, --unshare-ipc, --unshare-uts, and --die-with-parent are always requested. The server profile currently adds neither a PID namespace nor Landlock or a custom seccomp filter.

3.2 Capability-derived resources

DeclarationEffect on the profile
fs:read / fs:writeRead-only or read-write mount. Fixed glob-like scopes are reduced to their root; arg: scopes resolve the concrete value only when the path exists.
metnos:read, metnos:write, metnos:createTranslate closed semantic resources—such as spreadsheets, the persons registry, or proposals—to canonical Metnos-managed paths.
index:readRead-only access to the canonical root of the declared index. An unknown name grants nothing.
systemd:read and system:readExpose only known resources required for status inspection. Requesting network-interface information keeps the host network available.
provider:access or skill:<name>Read-write access to the integration home, including what OAuth refresh needs, plus host network access.
mail:readRead-only access to individual credential files for the requested account, plus host network access.

network:* and net:* capabilities, external integrations, and selected system-inspection requests prevent --unshare-net from being added. The process then receives the host network; bubblewrap does not restrict it to one host or provider. Application controls and narrowly scoped credentials must enforce that boundary.

4. Execution without bubblewrap

If bwrap is not on PATH, or if METNOS_SANDBOX is 0, off, no, or false, wrap_command returns the command unchanged. There is no third branch that automatically converts a detection exception into an unavailable result.

The signed invocation-scope preflight still runs before the subprocess, and Vaglio and executor-specific controls remain in force. They are not equivalent to operating-system containment. Local executor results do not automatically carry a field that reports the absence of bwrap; operators must inspect status() or service diagnostics.

5. Runtime integration

invoke_executor is the common execution point. Its relevant order is:

  1. derive actor, channel, and turn from the authenticated context;
  2. evaluate remote placement using only devices owned by that user;
  3. keep provider-backed invocations on the server that holds the integration;
  4. check signed path scope even when bwrap is unavailable;
  5. prepare undo journaling and only the invocation-specific extra resources;
  6. run sys.executable—the interpreter of the current Metnos environment—through wrap_command.

The public entry point also passes through the central scheduler, which applies concurrency limits without changing placement, arguments, or the result contract.

6. Autonomy and policy

wrap_command accepts an autonomy parameter, but it is currently informational and does not select different profiles. Containment comes from manifest capabilities and resources resolved for the invocation. Consent, criticality, and admission belong to policy and Vaglio, not to the sandbox.

7. Verification

The documentation does not freeze a test count. Dedicated suites cover runtime and Python-environment mounts, i18n databases, provider integrations, mail credentials, and downgrade paths. The main suites are:

tests/runtime/safety/test_sandbox_runtime_bind.py
tests/runtime/safety/test_sandbox_i18n_bind.py
tests/runtime/safety/test_sandbox_skill_backed.py

The Rust modules also contain tests next to the common, Linux, Windows, and AppContainer implementations. Argument-construction tests can run without starting a real sandbox; system tests separately verify the mechanism available on the target platform.

8. Diagnostics and installation

Call sandbox.status() to inspect the current state. It checks PATH at call time and returns bwrap_available, bwrap_path, disabled_via_env, and active; the module does not cache that result.

# Debian and Ubuntu
sudo apt install bubblewrap

# Fedora and RHEL
sudo dnf install bubblewrap

# Arch Linux
sudo pacman -S bubblewrap

After installation, verify both status() and a real invocation in the service environment. A different PATH or METNOS_SANDBOX value can make the service behave differently from an administrator's shell.

9. Security boundaries

10. Sandboxing on paired devices

The remote client uses a common result contract, while containment depends on the platform. The sandbox field names the level actually applied; sandbox_downgrade_reason, when present, explains why a weaker level was used.

PlatformCurrent implementationDowngrade behaviour
LinuxBubblewrap with mount and PID namespaces, network separation when not requested, a private temporary directory, and a dedicated process group. Custom Landlock and seccomp are not implemented.If bwrap is missing or disabled, execution is direct and logged; the result reports sandbox:"none". The process group still supports tree termination at the deadline.
WindowsAppContainer with capability-derived grants, itself placed inside a Job Object. AppContainer is attempted by default when supported.System subprocesses, unanchored paths, scopes too broad for safe ACL changes, or an unavailable container cause an explicit downgrade to the Job Object. If the manifest requires AppContainer as its minimum, the invocation must not silently downgrade.
macOSThere is currently no native sandbox_macos.rs module. The common Unix path has no operational bubblewrap equivalent on macOS.The run must not be presented as isolated: its effective level is none, although process-group deadline handling remains.

The Job Object limits per-process memory, the number of active processes, and the lifetime of the tree. The process is created suspended, assigned to the Job Object, and only then resumed; a deadline terminates the whole job. AppContainer adds filesystem and network isolation through grants computed for the invocation.

Before remote execution, the client verifies the signed package and uses a provisioned, verified Python interpreter. These checks protect code provenance; they do not replace the containment level declared in the result.