~/.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.
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:
2. Consent (at 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 theenv 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-wrapdeclaration 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-wraphook to exec the whole session inside an isolation mechanism entered right fromflox 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 theenvandsidecarhooks 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.
- host-native (macOS Seatbelt)
- srt (cross-platform)
- OCI (Docker)
- OpenShell (policy-gated egress)
Wraps the session with macOS’s built-in The policy is fixed in this version — there is no
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.[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.
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:
LD_PRELOAD) leg builds from
the same sources but is not yet exercised.
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.
plugin-coder — Coder workspaces
plugin-coder — Coder workspaces
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 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.
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.plugin-modal — Modal Sandboxes
plugin-modal — Modal Sandboxes
Bakes an image, compiles grants into Modal’s vocabulary
(Requires the Modal CLI (1.x, authenticated) and Docker. Grant
scoping beyond the domain (access, protocol, binary) is recorded
but not enforceable on Modal.
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.plugin-docker-sbx — Docker Sandboxes
plugin-docker-sbx — Docker Sandboxes
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.plugin-e2b — E2B sandboxes
plugin-e2b — E2B sandboxes
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.plugin-daytona — Daytona sandboxes
plugin-daytona — Daytona sandboxes
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).
plugin-ona — Ona (formerly Gitpod)
plugin-ona — Ona (formerly Gitpod)
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.plugin-cognition-devin — Devin snapshots
plugin-cognition-devin — Devin snapshots
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.plugin-anjuna — confidential computing (TEE)
plugin-anjuna — confidential computing (TEE)
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.plugin-cursor — Cursor agent sandbox
plugin-cursor — Cursor agent sandbox
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.plugin-vercel-sandbox — Vercel Sandbox
plugin-vercel-sandbox — Vercel Sandbox
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.
[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 underetc/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
- Plugins — the framework sandbox plugins are built on, including the full lifecycle-hook contracts
- flox-plugins repository — the sandbox plugin packages and per-plugin READMEs
- Secrets management — the same plugin-class pattern applied to secrets
- Flox vs. containers — where container-based isolation fits relative to Flox environments
- Activating environments — the activation timeline the hooks extend