
Package Manager Sandboxing (Andrew Nesbitt)
A survey of how package managers confine install-time and build-time code. It covers Homebrew 7.0.0's fetch/install split, Landlock sandbox and signed declarative *_steps. It also covers opam, Nix, Bazel, pacman, Swift PM and Deno, and the open sandbox proposals at pnpm, RubyGems, Spack, Cargo, pip and Zig. It sorts the responses into gating (allowlists), confinement and replacement/removal of hooks. It links the topic to coding-agent sandboxes (Seatbelt, Landlock, bubblewrap) and to Shai-Hulud and nx. It argues the open problem is validating a sandboxed phase's output at the hand-off.
What it is
Andrew Nesbitt's survey of install-time and build-time confinement across package managers, written around Homebrew 7.0.0 (released September 13). The author says they help maintain Homebrew. The useful parts are the per-tool status ledger, the Linux/macOS/Windows primitives gap, and the unsolved hand-off problem.
Status by tool (what is actually on)
Confinement on by default
- opam: since 2.0 (2018). bubblewrap on Linux, sandbox-exec on macOS, network cut, writes limited to the build directory. Windows is unsandboxed.
- Nix: Linux builds in namespaces since 2.2 (2019). The macOS sandbox is still off by default. A 2024 Nix advisory says it is "not primarily intended as a security mechanism". A 2025 pair of CVEs let a local user escalate through the build users of Nix, Lix and Guix.
- Bazel: namespace sandbox on Linux, sandbox-exec on macOS, restricted to declared inputs.
- Swift PM: Seatbelt on manifests and plugins. On Linux it is a no-op ("the command line is returned unmodified").
- pacman 7.0/7.1: downloads only, run as a dedicated user under Landlock and seccomp.
- Homebrew: Landlock on Linux (replaced Bubblewrap, which was removed in August), sandbox-exec on macOS. The Linux opt-out variable is removed with no replacement.
Weak or opt-in
- Gentoo Portage's LD_PRELOAD shim is bypassable by static binaries. Its namespace isolation is a real kernel boundary.
- apt: the seccomp filter has been disabled by default since 2018. dpkg scripts run as root.
- Alpine abuild rootbld is opt-in.
- mise has opt-in Landlock and Seatbelt for tasks.
- RPM 4.20's unshare plugin ships disabled everywhere.
- CocoaPods' sandbox-pod still has
(allow network-outbound). - Deno's permissions are enforced in-process, not by the OS. An approved npm lifecycle script "executes with essentially full host access".
No confinement and none planned
- Poetry closed a Landlock/seccomp request as out of scope.
- uv puts the local machine inside its trust boundary.
- Maven assumes you trust the pom.xml.
- Conan and vcpkg have no proposals.
JavaScript: gating, not confinement
Dependency lifecycle scripts are now blocked by default behind an allowlist in:
- pnpm (January 2025)
- Bun (since 2023)
- Yarn (April 2026)
- npm 12 (July 2026)
Cooldowns on new versions are on by default at pnpm, Yarn and Deno. The pnpm sandbox issue argues an allowlist is binary: approving esbuild's postinstall trusts every future version.
Open proposals
- pnpm: bubblewrap/Landlock/Seatbelt around lifecycle scripts.
- RubyGems: split bundle install into download and install phases.
- Spack: in-process Landlock/Seatbelt between fetch and build.
- Arch: private network namespace for scriptlets (draft MR; CachyOS already ships it).
- Zig: compile build.zig to WebAssembly so it emits only a build graph.
- pip: setup.py sandbox, open since 2017.
- Gradle: sandbox mode, open since 2019.
- Cargo: sandbox issue open since 2018. The WASI approach failed because it cannot spawn processes. The August 2026 arrayref build.rs attack's follow-up issue was closed as a duplicate.
Primitives and where they break
- Linux: Landlock is unprivileged, needs no daemon, and has a versioned ABI. bubblewrap fails inside
container:CI jobs for want of privileged namespaces (opam issue open since 2020), which is why Homebrew moved to Landlock. - macOS: Seatbelt has been deprecated since 2016 but not replaced.
- Windows: no comparable primitive. Codex built a restricted-token sandbox, and Cursor and Claude Code use WSL2.
- CI: a GitHub-hosted job shares filesystem, environment and secrets with every
uses:step and package manager. The shipped control is an allowlist. Harden-Runner (third-party) filters egress.
Caveats and the open problem
- Homebrew's own release notes say tap trust is the primary protection. Sandboxing "cannot make untrusted software safe to run". Vendor .pkg installers still run outside the sandbox.
- Hand-off: a sandboxed phase's output (install manifest, cache entry, step list) is acted on with full privileges. Homebrew's LaunchServices escape was a hand-off bug. None of the pnpm, RubyGems, Deno or Spack proposals yet specify what validates that output.
- Homebrew's answer is signed declarative
*_steps(data is interpreted, not executed).post_installand cask flight blocks are deprecated, with removal in December 2027.
flowchart LR
A[Fetch phase: network, writable cache] --> B[Sandboxed build/install: offline]
B --> C{Hand-off: validate output?}
C -->|schema / signed data| D[Manager interprets it]
C -->|unvalidated manifest or cache| E[Acted on with full privileges]What to do
The page gives no commands. Its implications:
- On npm, pnpm or Yarn, keep script allowlists tight, since they gate by package, not version.
- Do not rely on Swift PM's Linux sandbox or on Deno's permission model as OS-level confinement.
- For CI or agent installs, expect bubblewrap to fail in containers, and treat the runner's secrets as in reach of any install script.
Organisations named here
- JEP 486
- SE-0303
- SE-0332
- Shai-Hulud
- xz-utils
Technologies named here
- Homebrew 7.0.0
- Homebrew sandbox
- Homebrew 6.0.0
- Bubblewrap
- Landlock
- Homebrew vendor .pkg installers
- Seatbelt / sandbox-exec
- npm lifecycle/postinstall scripts
- Claude Code settings file
- seccomp
- Swift Package Manager on Linux
- sandbox-runtime
- opam on Windows
- Codex CLI sandbox
- Cursor agent terminal sandbox
- Nix macOS sandbox
- Gemini CLI
- Nix, Lix and Guix build users
- opam sandboxing
- Gentoo Portage LD_PRELOAD sandbox
- Swift Package Manager Seatbelt sandbox
- Nix and Guix namespace build sandboxes; Bazel sandbox
- RPM 4.20 unshare plugin
- CocoaPods sandbox-pod
- Alpine abuild rootbld
- Deno permission model
- pacman 7.0/7.1 download sandbox
- pip setup.py
- mise task runner sandbox
- Poetry
- Harden-Runner
- uv
- pixi
- Yarn, pnpm, Bun, npm 12 script allowlists
- Cargo build.rs and proc-macros
- Cackle, watt
- Gradle
- Maven
- JDK 24
- NuGet
- Conan recipes and vcpkg portfiles
- Dart native-assets build hooks
- GitHub-hosted Actions runners, self-hosted runners
- Seatbelt/sandbox-exec on macOS
- Homebrew
- pnpm
- npm
- Cargo
- Claude Code
- Linux
- arrayref
- Seatbelt
- sandbox-exec
- Mandiant
Techniques named here
- Sandbox escape via LaunchServices
- CI workflow injection leaking a publishing token
- Gating install code
- Confining install code
- Replacing install code with declarative signed data
- Fetch/install phase split
- Validating the confined phase's output at the hand-off
Threat actors named here
- Shai-Hulud worm
- Shai-Hulud 2.0
- nx compromise
- arrayref attack
- serde_derive precompiled binary incident
- xz backdoor
- s1ngularity
- DPRK
Related in Supply Chain


