Skip to main content
A growing share of what runs inside a developer environment isn’t typed by a developer: coding agents, build tools, and scripts pulled from the ecosystem all execute with your full privileges. By default an activated environment can read ~/.ssh, your browser profiles, and your cloud credentials, and can talk to any host on the network. Sandboxing runs the activated session inside a boundary that limits what it can touch. In Flox, sandboxing is not a CLI feature — it is a family of plugins built on the plugin framework’s lifecycle hooks. Flox core provides generic extension points (wrap the session, inject variables, run a supervised daemon); each sandbox backend is an ordinary installable package that uses them. The same pattern as secrets management — a class of problem solved by a class of plugin — applied to isolation.
Sandbox plugins are a prototype. They require a development build of Flox with lifecycle-hook support, a manifest schema-version of "1.16.0", and the features.plugin_hooks flag enabled (flox config --set features.plugin_hooks true), and the plugin packages are not yet published to the Flox Catalog — they are built from the flox-plugins repository and installed by store path. Expect the details below to change.

The sandboxed activation pattern

The pattern has three phases:

1. Declare (in the manifest)

The environment’s author installs a sandbox plugin (by store path, while the packages are unpublished — see the warning above) and declares it in the typed, top-level [plugin-hooks] section, with any policy the plugin supports in its own [plugins.<name>] table:
The manifest carries the policy — which plugin wraps the session, what the session may reach — versioned with the project like any other manifest content. Policy edits take effect on the next activation. Handing a terminal session to third-party code is gated on the environment’s own author and on the person activating:
  • Only the top-level manifest’s [plugin-hooks] declaration counts — a declaration arriving through composition is dropped, so an included environment can never wrap your session.
  • Auto-activation prompts before entering a wrapping environment, defaulting to No.
  • With the feature flag off, declarations are ignored with a warning and activation proceeds unwrapped — teammates who haven’t opted in aren’t locked out of a shared environment.

3. Enforce (for the session’s lifetime)

For a session-boundary plugin, the hook execs the entire activation under its boundary and never returns: there is no “decline the sandbox and continue unwrapped” path. Every process in the session — your shell, its children, anything an agent spawns — lives inside the boundary until the session exits. Not every sandbox plugin completes phase 3 the same way: advisory mediation replaces the boundary with per-access mediation armed through the env and sidecar hooks, and the hand-off plugins stop after compiling policy and generating launch artifacts, leaving the launch — and with it the enforcement — to an operator on the vendor’s platform.

Key security properties

  • Policy in the manifest, values nowhere — the manifest declares what may be reached, reviewable in a PR like any other change
  • Consent is structural — declarations are typed, top-level, and never inherited through includes; auto-activation asks first and defaults to No
  • One wrapper per environment — the schema makes a second session-wrap declaration unrepresentable
  • Declaration is bound to the package — the hook file must be shipped by the declared plugin’s own locked package; a look-alike package shadowing a plugin’s name is an activation error
  • Fail closed — a wrapping environment activates wrapped or not at all; a sandbox plugin that can’t build its boundary fails the activation rather than silently degrading
  • No sandbox code in Flox core — every backend is a package you can read, pin, replace, or write yourself

Three shapes of sandbox plugin

Sandbox plugins currently come in three shapes:
  • Session boundaries use the session-wrap hook to exec the whole session inside an isolation mechanism entered right from flox activate — an OS-level sandbox (plugin-host-native, plugin-srt) or a container (plugin-oci, plugin-openshell). Strong containment for the wrapped session.
  • Hand-off generators (the hand-off plugins) use the same hook for runtimes flox can’t enter by itself. They compile the manifest’s policy into the vendor’s vocabulary and generate launch artifacts, then deliberately stop the activation; containment is delivered by the vendor’s platform once an operator completes the launch.
  • Advisory mediation (plugin-libsandbox) uses the env and sidecar hooks to arm an in-process interposer instead: the session stays on your host, in your shell, with file and network access from cooperative tools mediated, audited, and — in prompt mode — interactively grantable. Friction plus audit rather than containment.

Session boundaries

Each tab shows the manifest configuration for one boundary plugin and what it does at activation. All of them refuse in-place activation (eval "$(flox activate)" cannot be wrapped). The host-level wrappers (host-native, srt) additionally skip re-wrapping when the same environment re-activates inside its own boundary; the container wrappers carry no flox CLI in the guest, so re-activation inside the boundary does not arise.
Wraps the session with macOS’s built-in sandbox-exec and a generated Seatbelt profile — no third-party tooling at all. The policy denies reading and writing your home directory (the files agents most often leak: ~/.ssh, browser profiles, cloud credentials) while re-allowing the project itself, ~/.cache, ~/.config, ~/.local, and Flox’s own state directories. .env files are denied even inside the project. Everything outside the home directory is untouched, and network egress is unrestricted.
The policy is fixed in this version — there is no [plugins.plugin-host-native] table yet.Because the boundary is the kernel’s, it holds even for SIP-protected system binaries that bypass advisory (preload-based) sandboxes.macOS only; the hook errors on other systems and points at plugin-srt for a cross-platform boundary.

Advisory mediation: libsandbox

plugin-libsandbox is the in-process alternative: no container, no re-exec, no session hand-off. The shell you get is the shell you asked for — with a libc interposer (DYLD_INSERT_LIBRARIES on macOS, LD_PRELOAD on Linux) armed in every shell, mediating file and network access outside the project from cooperative tools. It is the only current plugin built on the env and sidecar hooks rather than session-wrap:
  • the env hook composes the engine’s policy environment (the preload variable, allow-sets folded with saved grants, the mode) at activation start and every attach;
  • the sidecar hook runs a prompt broker for the activation’s lifetime, answering allow/deny verdicts and serving the review CLI.
The modes: warn permits everything but records out-of-policy access to an audit log (a dry run); enforce fails it with a permission error; prompt denies and queues the access for live approval from a second terminal. Grants and the audit trail persist as files under .flox/cache/plugins/plugin-libsandbox/, and a companion flox sandbox subcommand — a separately installed flox-sandbox executable shipped in the same package, which flox dispatches as flox sandbox through an experimental extension mechanism — lists, grants, revokes, and reviews them:
Approval is deliberately out-of-band: the broker refuses grant requests originating from inside the sandboxed session itself, so an agent cannot approve its own access. Validated end to end on macOS; the Linux (LD_PRELOAD) leg builds from the same sources but is not yet exercised.
Advisory means advisory: only libc entry points are mediated, so raw syscalls and statically linked binaries bypass it, and on macOS SIP-protected shells escape mediation for their own built-ins. The target is an agent or build running cooperative tools — friction plus an audit trail, not containment. Use a session boundary when you need containment.

Hand-off sandboxes

Ten more plugins target sandboxes flox can’t enter by itself: agent platforms (Cursor, Devin), cloud sandboxes (Modal, E2B, Daytona, Vercel, Ona), development platforms (Coder, Docker Sandboxes), and confidential computing (Anjuna). Entering these needs something the hook can’t do on its own — a registry push, an account or license, special hardware, or launching the vendor’s own tool. (plugin-coder comes closest to a full boundary: it runs its whole loop locally and is currently blocked at the final workspace entry by a guest-image gap, rather than stopping by design.) Each hand-off plugin does everything that can be done locally — runs the preflights the runtime supports (vendor CLI and auth checks where they exist; Ona, Devin, and Anjuna need neither, and their CLIs are at most presence-probed to word the hand-off), bakes the environment image where the runtime can consume one, compiles the manifest’s [[plugins.<name>.network]] grants into the runtime’s own policy vocabulary — then writes a launch artifact and deliberately stops the activation at the launch boundary with the remaining operator steps. Where a runtime’s vocabulary can’t express a grant, the plugin declares the lossiness (refusing a grant it would have to widen, recording scoping it can’t enforce) rather than silently dropping policy.
The whole loop on one machine: bakes a Docker image, pushes a minimal Terraform template to a local Coder server, creates a workspace from it, and execs the session over coder ssh. Currently blocked at that last step — Coder’s stock agent init script needs coreutils the baked image doesn’t carry — so the workspace comes up but the session isn’t entered.
Requires the Coder CLI (2.x), Docker, and a logged-in Coder server. Network grants are declined outright — the local Docker provider has no egress vocabulary.
Bakes an image, compiles grants into Modal’s vocabulary (block_network deny-all, or a TLS/443 domain allowlist), and generates a complete Modal launch program under .flox/cache/plugins/plugin-modal/. Stops at the launch boundary: Modal ingests images by registry reference only, so the operator pushes the image and runs modal run on the artifact.
Requires the Modal CLI (1.x, authenticated) and Docker. Grant scoping beyond the domain (access, protocol, binary) is recorded but not enforceable on Modal.
Preflights the sbx CLI (0.32+) and Docker, bakes an image, compiles grants into the kit’s HTTP/HTTPS domain allowlist, and writes a sandbox kit manifest to .flox/cache/plugins/plugin-docker-sbx/spec.yaml. Stops before sbx kit load/sbx run: sbx’s base-image contract (a non-root agent user at uid 1000) isn’t satisfied by the flox bake, so the kit is a base for manual adaptation.
Preflights the E2B CLI (1.x, authenticated) and Docker, bakes an image, and writes e2b.Dockerfile and e2b.toml at the project root (meant to be committed) with deny-by-default egress — E2B’s platform default is open, so the template sets allow_internet_access = false explicitly. Stops with push-and-e2b template build instructions.
Preflights the Daytona CLI (0.9+, authenticated) and Docker, bakes an image, compiles grants into Daytona’s domain allowlist, and writes a Python launch program that registers the image as a snapshot and creates the sandbox. Stops at the launch boundary — pushing the image and calling the Daytona API need credentials the host can’t supply automatically.
No grants compiles to deny-all. CIDR-shaped grants are declined (mutually exclusive with domain grants on Daytona).
Bakes an image and writes .devcontainer/devcontainer.json at the project root (meant to be committed — Ona builds workspaces from it), recording a compiled deny-by-default egress allowlist for the operator to wire into Ona’s enterprise network policy. Stops at the launch boundary: opening the workspace needs an Ona account and registry push. The Ona CLI is never executed.
Devin boots snapshots from git-backed blueprints rather than OCI images, so the hook bakes the image as the reproducible substrate, compiles grants into a per-domain allowlist (no grants → explicit deny-all), and writes the blueprint to .devin/blueprint.yaml (committed to the repo). Stops naming the two prerequisites: registry push and a Devin subscription.
Targets Anjuna Security’s enclave runtimes (AWS Nitro Enclaves, AMD SEV-SNP, Intel SGX). Bakes an image and generates an enclave-converter config carrying the compiled allowlist and an attestation-binding note tying the enclave measurement to the flox lockfile hash, plus a build-enclave.sh with the anjuna-nitro-cli invocation. Stops at the launch boundary: the enclave build needs a commercial license and TEE hardware.
A policy compiler, not a launch wrapper: Cursor’s agent CLI runs its own host-kernel sandbox (Seatbelt on macOS, Landlock on Linux), configured through settings files. The hook compiles network grants into Cursor’s project-scoped permission config at .cursor/cli.json — web-fetch domain allowances plus a fixed deny list for .env* and *.key files — then stops with instructions to run agent in the project yourself. Nothing is baked; nothing leaves the laptop.
Vercel Sandboxes boot fixed stock runtimes (Firecracker microVMs) and can’t ingest a baked image, so this is the bootstrap-shaped wrapper: no Docker at all. The hook writes a bootstrap script that installs Flox in-sandbox and activates the environment from FloxHub, plus a Node launcher that creates the sandbox and streams output. Stops at the launch boundary; the operator runs the launcher. Requires the environment to be pushed to FloxHub first.
Network grants are declined — the Vercel SDK has no per-sandbox egress allowlist.
For the hand-off plugins with autobake, allow-stale, image, and registry settings, the conventions match plugin-oci: prompt before a bake on a tty (fail fast otherwise), and every setting has a FLOX_PLUGIN_<NAME>_* environment-variable override for CI.

Sandbox plugin reference

Choosing a sandbox

  • Quickest meaningful protection on a Mac: plugin-host-native — zero dependencies, kernel-enforced, home directory protected.
  • Cross-platform, network locked down too: plugin-srt.
  • Full filesystem isolation: plugin-oci — the host filesystem simply isn’t there.
  • Isolation plus controlled egress (an agent that must reach exactly your API and nothing else): plugin-openshell.
  • Observe first, contain later — audit what a session reaches outside the project, approve access interactively, keep your own shell: plugin-libsandbox.
  • Your organization already runs on a hosted platform: the matching hand-off plugin compiles your manifest’s policy into that platform’s vocabulary and generates the launch artifacts.
These compose with ordinary Flox workflows: because policy lives in the manifest, a repository can ship a sandboxed-by-default environment, and switching backends is a small manifest edit — swap the installed plugin package, update the [plugin-hooks] declaration, and carry over the plugin’s [plugins.<name>] policy table where it has one.

Writing your own sandbox plugin

There is nothing privileged about the plugins above — each is a directory in flox-plugins containing a Flox build environment, hook executables under etc/flox/hooks/, and a README. A new backend is a new package: implement the session-wrap contract (read the context file, build your boundary, exec the activation inside it) or the env/sidecar contracts for an advisory design, and test it with a store-path install — no publishing required. See Lifecycle hooks for the full protocol.

Further reading