Skip to content

feat: a :sandbox keyword for parenting-spawn - #1

Merged
clhodapp merged 1 commit into
mainfrom
feat/sandbox-spawn
Sep 8, 2026
Merged

clhodapp merged 1 commit into
mainfrom
feat/sandbox-spawn

Conversation

@clhodapp

@clhodapp clhodapp commented Sep 8, 2026

Copy link
Copy Markdown
Owner

What

Adds a :sandbox keyword to parenting-spawn. When non-nil it is a plist describing a bubblewrap jail; parenting-spawn derives the command-wrapper, and the socket and library-directory crossing, from it, so a caller gets an isolated child without hand-assembling a bwrap line.

Spec fields, all optional:

  • :ro-binds — extra read-only binds (paths or (SRC . DEST)), also the field for project directories the child may read.
  • :rw-binds — extra read-write binds, same shape.
  • :environment — a list of env var NAMES to carry into the otherwise-cleared jail; unset names are dropped.
  • :network — non-nil to share the host network (default: none).

:sandbox is mutually exclusive with :command-wrapper, :child-socket-path, and :child-library-directory, since it sets all three; passing any alongside it signals an error. Every other spawn keyword still applies.

How the socket and sources cross the namespace

The socket's private directory is bound read-write at the same path, so the in-jail socket path equals the parent's and child-socket-path keeps its default. The parenting sources are bound read-only at the same path, so child-library-directory stays nil and the bootstrap's readability check stays on. Under :sandbox only command-wrapper is actually rebound; the rest falls out of existing defaults.

New symbols

  • parenting--sandbox-command-wrapper (spec socket-directory library-directory) — the pure bwrap-line builder (--die-with-parent --unshare-all, --share-net only with :network, ro-binds of the existing system dirs, --ro-bind /nix/store /nix/store when present, --tmpfs /tmp --proc /proc --dev /dev, --clearenv then baseline env, HOME on the tmpfs, pass-through vars).
  • parenting--sandbox-bind-args (flag binds) — bind-list helper.
  • Defcustoms parenting-sandbox-program, parenting-sandbox-system-directories, parenting-sandbox-default-environment.

Tests

Five pure unit tests over the generated bwrap argument list (core flags, network toggle, nix-store bind, environment pass-through, extra binds), a negative test for the mutual-exclusion error, and a guarded integration test that spawns a real child under the jail and evals in it, skipping via ert-skip when bwrap cannot create a user namespace.

Run locally with emacs and bubblewrap present: Ran 54 tests, 51 as expected. The three failures are parenting-remote-daemon-child, parenting-spawn-daemon-child-is-interactive, and parenting-spawned-interactive-child-exits-on-close, which fail identically on clean main (44/47) in a headless environment with no display; all eight new sandbox tests pass and the integration test ran rather than skipped. Byte-compiles clean under byte-compile-error-on-warn.

🤖 Generated with Claude Code

Turn a bwrap-jail spec into the command-wrapper, child-socket-path and
child-library-directory for the caller, so a child can be launched
under bubblewrap without hand-assembling any of that.

The jail runs with --die-with-parent, a minimal read-only root, a
private /tmp, /proc and /dev, and a cleared environment. The control
socket's directory is bound read-write at its own path so the socket
crosses the mount namespace, and the parenting sources read-only at
their own path so the child loads them and the parent can still verify
they are readable. :sandbox is mutually exclusive with the three
keywords it derives.

The bwrap line is built by parenting--sandbox-command-wrapper, a pure
function unit-tested without spawning. A guarded integration test
spawns a real child under the jail and skips cleanly where
unprivileged user namespaces are unavailable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@clhodapp clhodapp self-assigned this Sep 8, 2026
@clhodapp
clhodapp merged commit 8909ffb into main Sep 8, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant