The Map

"The next clue is on the back of the Declaration of Independence. But the real treasure… is the enforcement stack."

How the Layers Work

sandpit uses a defense-in-depth strategy. Four independent enforcement layers, each catching what the others miss. Like the puzzles in the National Archives — you have to solve all of them to get through.

Layer 1: DYLD Interpose — The Invisible Ink

On macOS, DYLD_INSERT_LIBRARIES lets you load a shared library into a process before it starts. sandpit's dylib hooks C library functions at the lowest level:

The dylib is compiled as a separate crate (sandpit-dylib) and embedded into the main binary at build time. At runtime it's extracted to ~/.sandpit/lib/libsandpit.dylib.

Layer 2: Seatbelt — The Backup Vault

Some binaries can't be DYLD-hooked. SIP-protected system binaries (like /usr/bin/python3) and hardened-runtime apps strip DYLD_INSERT_LIBRARIES before the process starts.

For these, sandpit falls back to sandbox-exec (Apple's seatbelt framework). This generates a SBPL profile at runtime from your config:

(version 1)
(allow default)
(deny file-read* (subpath "/Users/you/.ssh"))
(deny file-write* (subpath "/etc"))

Seatbelt enforcement is kernel-level — it covers every syscall including symlink(), truncate(), link() that DYLD doesn't hook. But it can only do file operations — no domain-level network filtering, no exec argument inspection.

Why not both? Because sandbox-exec is itself a SIP-protected binary that strips DYLD_INSERT_LIBRARIES from children. They're mutually exclusive on macOS.

Layer 3: Command Shims — The Decoy

Enabled with sandpit run --shims, this layer generates tiny wrapper scripts that shadow dangerous commands by prepending ~/.sandpit/shims/ to the child's PATH. When the agent calls curl, the shell finds the shim first.

Each shim is a two-line shell script that calls back into sandpit itself:

#!/bin/sh
exec sandpit exec curl "$@"

The sandpit exec command loads your config, checks the command against exec block rules and network domain policy in native Rust (~38ms), then either exec()s the real binary or blocks with an error.

What gets shimmed: curl, wget, nc, ssh, scp, rsync, socat, dig, and more — plus any command extracted from your [exec] block patterns. Only commands actually installed on the system get shims.

What gets checked:

Shims are particularly valuable when DYLD isn't available — for SIP/hardened binaries where seatbelt handles files but nothing handles network or exec. They're opt-in via --shims until tested with your specific agent workflow.

Limitation: PATH-based interception. If the agent uses an absolute path like /usr/bin/curl, the shim isn't hit. That's what DYLD and the adversary are for.

Layer 4: Adversary Review — The Last Guardian

An LLM that reviews every tool call before it executes. It reads your [adversary].rules policy and makes a judgment call: ALLOW or DENY.

The adversary catches what patterns can't:

The adversary always gets all rules regardless of mode — it's the backstop.

Auto-Detection

sandpit inspects the target binary at launch and picks the best enforcement mode automatically:

Binary Type Mode What's Enforced
Homebrew / user-built (Node, Rust, Go) DYLD interpose Everything — network, exec, files
SIP / hardened runtime binaries Seatbelt Kernel file blocks; network + exec → shims (with --shims) + adversary
Neither available Shims + adversary Shims handle exec/network (with --shims); adversary reviews everything
$ sandpit check -- goose
🔍 Binary: /opt/homebrew/bin/goose
   DYLD_INSERT_LIBRARIES: ✅ supported
   sandbox-exec:          ✅ available

✅ Mode: DYLD interpose (full enforcement)

$ sandpit check -- /usr/bin/python3
🔍 Binary: /usr/bin/python3
   DYLD_INSERT_LIBRARIES: ❌ not supported (SIP)
   sandbox-exec:          ✅ available

⚠️  Mode: seatbelt (kernel file blocks only)
   Falls through to adversary:
   • Network domain filtering
   • Exec argument patterns

Shebang Resolution

Many agents are scripts with #!/usr/bin/env python3 shebangs. /usr/bin/env is SIP-protected and strips DYLD. sandpit detects this and resolves the interpreter via PATH, spawning the Homebrew python3 directly instead. This turns a seatbelt-mode launch into a DYLD-mode launch.

The Enforcement Matrix

Capability DYLD Shims (--shims) Seatbelt Adversary
Network domain filtering ✅ proxy + rDNS ✅ domain check on args ❌ ✅ LLM review
Exec argument inspection ✅ substring match ✅ substring match ❌ ✅ LLM review
File block_read ✅ userspace hook ❌ ✅ kernel (all syscalls) ✅ LLM review
File block_write ✅ userspace hook ❌ ✅ kernel (all syscalls) ✅ LLM review
File allow_write ✅ userspace hook ❌ ❌ ✅ LLM review
Catches absolute paths ✅ syscall level ❌ PATH-based only ✅ kernel level ✅ LLM review
Catches novel attacks ❌ ❌ ❌ ✅ judgment

Limitations

Roadmap

Linux: LD_PRELOAD + bubblewrap

Same layered approach but stronger — bwrap doesn't strip LD_PRELOAD, so both layers run together. Network namespace isolation via --unshare-net, filesystem isolation via --ro-bind / --tmpfs.

Windows: Detours + AppContainer

Microsoft's Detours for function hooking (the DYLD equivalent), AppContainer for kernel isolation. No existing agent sandbox covers Windows.

.app Bundle Wrapping

sandpit wrap /Applications/Cursor.app → creates a sandboxed launcher. Useful for Electron apps which support DYLD and get full enforcement.