Documentation Index
Fetch the complete documentation index at: https://docs.boxlite.ai/llms.txt
Use this file to discover all available pages before exploring further.
This guide explains how to debug sandbox policy issues when developing or troubleshooting BoxLite’s macOS sandbox isolation.
Overview
BoxLite uses macOS’s built-in sandbox system (Seatbelt) to isolate the boxlite-shim process. The sandbox uses SBPL (Sandbox Profile Language) policies that whitelist specific operations. When an operation is denied, macOS logs the denial to the system log.
Quick Reference
# Real-time monitoring (recommended during development)
log stream --predicate 'eventMessage CONTAINS "Sandbox:" AND eventMessage CONTAINS "boxlite"'
# Check recent denials (last 5 minutes)
log show --last 5m --predicate 'eventMessage CONTAINS "Sandbox:" AND eventMessage CONTAINS "deny"'
Debugging Workflow
Enable real-time log monitoring
Open a separate terminal and start monitoring sandbox messages:# Watch all sandbox denials
log stream --predicate 'subsystem == "com.apple.sandbox"' --level error
# Or filter for boxlite specifically
log stream --predicate 'eventMessage CONTAINS "boxlite-shim" AND eventMessage CONTAINS "deny"'
Run your test
In another terminal, run the operation that is failing:source .venv/bin/activate
python -c "
import asyncio
import boxlite
async def test():
async with boxlite.SimpleBox(image='alpine:latest') as box:
result = await box.exec('echo', 'hello')
print(result.stdout)
asyncio.run(test())
"
Analyze denials
Sandbox denials appear in the format:kernel: (Sandbox) Sandbox: boxlite-shim(PID) deny(1) OPERATION TARGET
Common denial types:| Operation | Target | Meaning |
|---|
file-read-data | /path/to/file | Process tried to read file contents |
file-read-metadata | /path | Process tried to stat/access file metadata |
file-write-data | /path/to/file | Process tried to write to file |
file-write-create | /path/to/file | Process tried to create new file |
sysctl-read | kern.bootargs | Process tried to read sysctl value |
mach-lookup | com.apple.service | Process tried to connect to mach service |
network-outbound | *:443 | Process tried to make network connection |
iokit-open | IOHIDFamily | Process tried to access IOKit device |
Update the policy
Based on the denial, add the appropriate rule to the SBPL policy:; For file-read-data /var
(allow file-read* (literal "/var"))
; For sysctl-read kern.bootargs
(allow sysctl-read (sysctl-name "kern.bootargs"))
; For mach-lookup com.apple.service
(allow mach-lookup (global-name "com.apple.service"))
Rebuild and test
After updating .sbpl files, rebuild to pick up changes:# The .sbpl files are embedded via include_str!
# Touch the Rust file to force recompilation
make dev:python
# Or for just the Rust library
cargo clean -p boxlite && cargo build -p boxlite
Log Commands Reference
Real-time Streaming
# All sandbox messages
log stream --predicate 'subsystem == "com.apple.sandbox"'
# Only errors (denials)
log stream --predicate 'subsystem == "com.apple.sandbox"' --level error
# Specific process
log stream --predicate 'eventMessage CONTAINS "boxlite-shim"'
# Combined: boxlite denials only
log stream --predicate 'eventMessage CONTAINS "Sandbox:" AND eventMessage CONTAINS "boxlite" AND eventMessage CONTAINS "deny"'
Historical Queries
# Last N minutes
log show --last 5m --predicate 'eventMessage CONTAINS "Sandbox:"'
# Time range (replace with your target timestamps)
log show --start "2025-01-01 10:00:00" --end "2025-01-01 10:05:00" \
--predicate 'subsystem == "com.apple.sandbox"'
# Count denials by type
log show --last 10m \
--predicate 'eventMessage CONTAINS "Sandbox:" AND eventMessage CONTAINS "deny"' \
| grep -oE 'deny\(1\) [^ ]+' | sort | uniq -c | sort -rn
Filtering Tips
# Exclude noisy system processes
log show --last 5m --predicate 'eventMessage CONTAINS "Sandbox:" AND eventMessage CONTAINS "deny" AND NOT eventMessage CONTAINS "imagent" AND NOT eventMessage CONTAINS "bluetoothd"'
# Only kernel messages (most reliable)
log show --last 5m --predicate 'senderImagePath == "/kernel" AND eventMessage CONTAINS "Sandbox:"'
SBPL Policy Syntax
Basic Structure
(version 1)
; Deny everything by default
(deny default)
; Allow specific operations
(allow process-exec)
(allow file-read* (subpath "/usr/lib"))
(allow sysctl-read (sysctl-name "hw.ncpu"))
Common Patterns
; Allow reading entire directory tree
(allow file-read* (subpath "/path/to/dir"))
; Allow reading single file only
(allow file-read* (literal "/path/to/file"))
; Allow reading files matching pattern
(allow file-read* (regex #"^/Users/[^/]+/\.boxlite/"))
; Allow multiple sysctls
(allow sysctl-read
(sysctl-name "hw.ncpu")
(sysctl-name "hw.memsize")
(sysctl-name-prefix "kern.proc."))
; Allow mach service lookup
(allow mach-lookup
(global-name "com.apple.CoreServices.coreservicesd")
(global-name "com.apple.system.logger"))
Testing Syntax
# Test if policy syntax is valid
sandbox-exec -p '(version 1)(deny default)(allow process-exec)' /bin/echo "Policy OK"
# Test with a file
sandbox-exec -f /path/to/policy.sbpl /bin/echo "Policy OK"
BoxLite Policy Files
BoxLite’s sandbox policy is split into multiple files:
| File | Purpose |
|---|
seatbelt_base_policy.sbpl | Process ops, sysctls, mach services, IOKit |
seatbelt_file_read_policy.sbpl | Static system paths for reading |
seatbelt_file_write_policy.sbpl | Static paths for writing (/tmp) |
seatbelt_network_policy.sbpl | Network access (optional) |
macos.rs | Dynamic paths (binary, volumes, box_dir) |
Viewing the Generated Policy
To see the complete generated policy:
use boxlite::jailer::{SecurityOptions, write_sandbox_profile};
let security = SecurityOptions::default();
let box_dir = Path::new("/tmp/test-box");
let binary_path = Path::new("/path/to/boxlite-shim");
write_sandbox_profile(
Path::new("/tmp/debug-policy.sbpl"),
&security,
box_dir,
binary_path,
).unwrap();
Then inspect /tmp/debug-policy.sbpl.
Common Issues
1. Changes Not Taking Effect
The .sbpl files are embedded at compile time via include_str!. After modifying them:
# Force recompilation
cargo clean -p boxlite
cargo build -p boxlite
# For Python SDK
make dev:python
2. Path Canonicalization
macOS uses symlinks (/var -> /private/var, /tmp -> /private/tmp). Use canonical paths in your policy:
; Wrong - /tmp is a symlink
(allow file-write* (subpath "/tmp"))
Use the canonical path instead:; Correct - use canonical path
(allow file-write* (subpath "/private/tmp"))
3. Duplicate Denials
The log may show “X duplicate reports for…”. This means the same denial happened multiple times. Fix the root cause, not each duplicate.
4. Silent Failures
Some denials do not appear in logs immediately. If the process hangs or crashes without logged denials:
Check for crash reports
ls ~/Library/Logs/DiagnosticReports/*shim*
Verify process started
Check host logs to ensure the process actually started.
Run without sandbox
Try running without the sandbox to isolate whether the issue is sandbox-related.
5. Permissions vs Sandbox
Not all failures are sandbox-related. Also check:
- File permissions (
ls -la)
- Directory existence
- Hypervisor.framework entitlements
Debugging Checklist
Use this checklist when investigating sandbox denials:
Further Reading