~/.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
plugin built on the plugin framework’s
session-wrap hook. Flox core provides
one generic hook (wrap the session); a sandbox is an ordinary
installable package that uses it. The same pattern as
secrets management — a class of problem
solved by a class of plugin — applied to isolation. The first such
plugin is OpenShell.
The sandboxed activation pattern
The pattern has three phases:1. Declare (in the manifest)
The environment’s author installs the sandbox plugin (by store path, while the package is unpublished — see the warning above) and declares it in the typed, top-level[plugin-hooks] section, with the 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 with a notice naming the include, 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, the declaration is 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)
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. A hook that can’t build its boundary exits non-zero instead, and the activation fails with it.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 exits non-zero, failing the activation rather than silently degrading
- No sandbox code in Flox core — the backend is a package you can read, pin, replace, or write yourself
OpenShell
plugin-openshell runs the session inside an
NVIDIA OpenShell sandbox with
deny-by-default network egress, enforced at layer 7. At activation,
its session-wrap hook:
- bakes the environment into a Docker image with
flox containerize; - layers OpenShell’s guest requirements on top — the
sandboxuser, writable home and runtime directories,/bin/sh, and trustedipandnsenterbinaries from a tools environment bundled in the plugin package; - compiles the manifest’s
[[plugins.plugin-openshell.network]]grants into an OpenShell policy; - execs
openshell sandbox create, which runs the image’s entrypoint — the environment’s own activation — under OpenShell’s supervisor.
sandbox user with only the
project directory bind-mounted, read-write, at its host path. The rest
of the host filesystem, your home directory included, does not exist
inside the boundary, and no network endpoint is reachable unless the
manifest grants it. The sandbox deletes itself when the session exits.
Requirements
- The OpenShell CLI, 0.0.62 or later, on
PATH(validated against 0.0.82) - Docker — the CLI and a running daemon
- A reachable OpenShell gateway (
openshell statusmust succeed) using the Docker compute driver withenable_bind_mounts = true
Installing and configuring the plugin
Build the package from theopenshell directory of the flox-plugins
checkout, then install it into the environment by store path, keeping
the default install ID plugin-openshell:
[[plugins.plugin-openshell.network]] entries means no egress at
all. Grants are scoped per endpoint, access mode, protocol, and
requesting binary. binary names a package in [install], resolved
through the lockfile to its store path for the guest system (or an
absolute path inside the guest). In practice every rule needs one:
OpenShell 0.0.8x runs its policy engine in binary-identity mode, and
endpoint grants without a binary are denied.
Each [plugins.plugin-openshell] setting has an environment-variable
override for CI: FLOX_PLUGIN_OPENSHELL_AUTOBAKE,
FLOX_PLUGIN_OPENSHELL_ALLOW_STALE, and FLOX_PLUGIN_OPENSHELL_IMAGE.
Images and caching
The image is tagged with a digest of the lockfile with the plugin’s own footprint stripped — its[plugin-hooks] declaration, its
[plugins.plugin-openshell] table, and its [install] entry — so
policy edits and plugin upgrades never invalidate the image; only real
environment changes do. The compiled policy is written to
.flox/cache/plugins/plugin-openshell/openshell-policy.yaml on every
activation and handed to OpenShell at launch, which is why network
grants apply on the next activation without a rebake.
When no image exists for the current digest, the hook prompts to bake
on a terminal and fails otherwise; autobake = true bakes without
asking, and allow-stale = true runs an existing image from a previous
digest instead. The images are the cache: after a rebake the plugin
removes superseded <env>-openshell tags (keeping the current digest
and latest), and the base flox containerize images are never
removed.
Limitations
- No
floxCLI inside the guest. The session runs the environment’s activation, butfloxcommands and[services]are unavailable in-session. - Host environment variables are not forwarded. The guest sees the image’s baked configuration, not your shell’s variables.
- In-place activation is refused.
eval "$(flox activate)"cannot be wrapped, as with any session wrapper. - Other store-path installs fail the bake. A host-only store path in
[install]— other than the plugin itself, which is stripped — cannot be realized for the guest. - Backslashes in
-- <cmd>arguments are stripped somewhere in OpenShell’ssandbox create … -- <cmd>transport (observed with 0.0.82:curl -w '%{http_code}\n'arrives as%{http_code}n). Avoid backslash escapes inflox activate -- <cmd>arguments; a quotedbash -cstring is unaffected in practice for common cases. - Validated on macOS only, with a local gateway. The Linux host leg has not been exercised.
Writing your own session boundary
There is nothing privileged about the OpenShell plugin — it is a directory in flox-plugins containing a Flox build environment, one executable atetc/flox/hooks/session-wrap.d/plugin-openshell, a small pre-locked
tools environment the guest image needs, 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 — 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
session-wrapcontract - flox-plugins repository — the OpenShell plugin package and its README
- 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 hook extends