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:
connect()— all outbound TCP connections pass through a transparent proxy that checks domains against your blocklist. The process doesn't know it's being proxied. HTTP, gRPC, WebSocket, raw TCP, SSH — they all callconnect().posix_spawn()/execve()— every command the agent spawns is checked against your exec block patterns before it runs. The command string is inspected, not just the binary name.open()/openat()— file access is fenced. Reads from blocked paths are denied. Writes outside allowed paths are denied.
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? Becausesandbox-execis itself a SIP-protected binary that stripsDYLD_INSERT_LIBRARIESfrom 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:
- Exec block rules — same case-insensitive substring match as the DYLD layer
- Network domains — parsed from URLs (
https://evil.com), scp syntax (user@evil.com:/path), ssh syntax (user@evil.com), and bare domains (dig evil.com). Subdomain matching included.
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:
- Novel exfiltration techniques not in your block list
- Context-dependent threats ("this
curlis to an internal API" vs "thiscurlis posting secrets") - Multi-step attacks where individual commands look benign
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
- macOS only — uses DYLD or sandbox-exec, both macOS-specific
- DYLD mode: IPv4 TCP only — IPv6 and UDP pass through. Child processes that are platform binaries won't have hooks.
- Seatbelt mode: No domain-level network filtering or exec inspection — falls through to adversary automatically.
- Pattern matching catches known bad patterns, not novel attacks — pair with adversary review.
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.