cl-exec-sandbox is a policy-driven process sandboxing library for Common
Lisp. Its policy model provides:
- read, write, and deny filesystem rules
- literal paths, deny globs, and portable special roots
- more-specific nested policy overrides
- read-only host and workspace-write presets
- protected project metadata such as
.git - full, isolated, and managed proxy-only network modes
- process, user, IPC, UTS, and network namespace isolation on Linux
- fresh
/procand minimal/devmounts - child timeouts and optionally bounded captured output
- explicit capability discovery
One policy is translated by a per-host backend. A policy a backend cannot
enforce signals sandbox-unavailable naming the missing capability. Query
sandbox-capabilities or sandbox-supported-p before relying on a capability.
The policy surface and Linux enforcement model were checked against OpenAI
Codex commit 2e1607ee2fa8099a233df7437adee5f16a741905. Codex is a reference.
The Linux backend targets x86-64 and SBCL. It uses the system bwrap binary
for filesystem and namespace isolation, plus a private helper for
no_new_privs, seccomp, and managed proxy routing. The helper lives beside
the Lisp sources.
The macOS backend runs commands under Seatbelt through /usr/bin/sandbox-exec,
generating a profile from the same policy. Resolved rules are emitted from the
broadest to the most specific, so Seatbelt’s last-match-wins resolution
reproduces nested overrides and protected metadata. Whole-root rules are
emitted before device access, so a read-only root still permits /dev. A
rule naming a single file becomes a literal filter.
On macOS:
:enabledand:isolatednetworking worksandbox-capabilitiesreports:process-namespacesas false- process separation is an accidental-damage boundary
Deny globs need rg on either host. Seatbelt installations outside /usr/bin
can set CL_EXEC_SANDBOX_SEATBELT to an absolute executable path.
The experimental Windows backend combines a fresh AppContainer identity with
an invocation-specific restricted-token SID for deny rules and a kill-on-close
Job Object for descendants. Run it as a standard Windows user. Build
scripts/build-windows-helper.ps1 from a Visual Studio SDK developer shell
with Clang installed, then ship
build/cl-exec-sandbox-windows.exe beside the Lisp sources. Alternatively, set
CL_EXEC_SANDBOX_WINDOWS_HELPER to its absolute path outside writable scopes.
Use appcontainer-sandbox-policy with explicit existing :read-roots and
:workspace-roots. Include the command’s binaries and dependencies in the read
scopes. Windows also supplies its normal AppContainer system-resource access
and private profile storage. Networking is isolated, including loopback.
(let ((policy
(cl-exec-sandbox:appcontainer-sandbox-policy
:workspace-roots (list #P"C:/work/project/")
:read-roots (list #P"C:/tools/"))))
(cl-exec-sandbox:run-sandboxed
"C:/tools/tool.exe" '("--version")
:policy policy :working-directory #P"C:/work/project/" :timeout 30))The backend rejects whole-host policies, network modes other than :isolated,
proc mounts, PID namespaces, globs, drive roots, UNC/device paths, reparse
points, hard-linked files, and paths of 240 characters
or more. The default protected metadata names are .git, .agents, and .codex.
This backend is not policy-equivalent to the Linux whole-host presets.
Filesystem grants are temporary SID-specific ACL entries. Native execution
and ACL changes are serialized within a Windows session. Serialize calls with
overlapping roots as well, including plan construction and cleanup of missing
metadata directories. Keep the trees and ACLs free of concurrent changes by
other host processes during a run.
Normal completion and run-sandboxed cancellation remove the grants and profile;
cleanup failures are reported. Terminating both the Lisp supervisor and helper
can leave stale SID entries and a profile. This prototype has no persistent
crash-recovery journal. When launching a plan yourself, call
sandbox-plan-cleanup after terminating and waiting for its helper.
Run (asdf:test-system :cl-exec-sandbox/windows-tests) after compiling
tests/windows-child.c to build/windows-child.exe. CI runs these enforcement
tests under a disposable non-administrator account.
(let ((policy
(cl-exec-sandbox:workspace-write-sandbox-policy
:workspace-roots (list #P"/work/project/")
:network :isolated)))
(cl-exec-sandbox:run-sandboxed
"/bin/sh"
'("-c" "git status --short")
:policy policy
:working-directory #P"/work/project/"
:output-limit 65536
:error-output-limit 65536))When a limit is exceeded, the returned prefix is available through
sandbox-result-output or sandbox-result-error-output. Inspect
sandbox-result-output-truncated-p and
sandbox-result-error-output-truncated-p before presenting captured output.
Build the private Linux helper and run the complete test suite:
./checkSeatbelt profile translation is a pure function of a policy, so its tests run on any host. They assert the generated profile text.
Applications which vendor this system should run scripts/build-helper during
their build and ship build/cl-exec-sandbox-process-group beside the Lisp
sources. Linux applications using restricted networking must also ship
build/cl-exec-sandbox-helper. Set CL_EXEC_SANDBOX_PROCESS_GROUP_HELPER or
CL_EXEC_SANDBOX_HELPER to alternate absolute helper paths when the installed
layout differs. Packaged Bubblewrap installations outside /usr/bin and /bin
can set CL_EXEC_SANDBOX_BWRAP to its absolute executable path.
Part of the Lambda Symbolics library shelf.