Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

App Sandboxing

Sandboxing is macOS’s containment mechanism that restricts what resources an application can access. A sandboxed app operates in a constrained environment with limited access to the file system, network, hardware, and system services. While App Store apps are required to be sandboxed, understanding sandboxing is valuable for security testing, developing distributed software, and restricting untrusted processes.

How Sandboxing Works

┌─────────────────────────────────────────────────────────────────┐
│                    macOS Kernel (XNU)                            │
│                          │                                       │
│              ┌──────────────────────────┐                       │
│              │   Sandbox Kernel Module   │                       │
│              │       (Seatbelt)          │                       │
│              └──────────────────────────┘                       │
│                          │                                       │
│         ┌────────────────┼────────────────┐                     │
│         ↓                ↓                ↓                     │
│  ┌────────────┐   ┌────────────┐   ┌────────────┐              │
│  │ Sandbox    │   │ Sandbox    │   │ Sandbox    │              │
│  │ Container  │   │ Container  │   │ Container  │              │
│  │            │   │            │   │            │              │
│  │  App A     │   │  App B     │   │  App C     │              │
│  │            │   │            │   │            │              │
│  │ ~/Library/ │   │ ~/Library/ │   │ ~/Library/ │              │
│  │ Containers/│   │ Containers/│   │ Containers/│              │
│  │ com.a.app/ │   │ com.b.app/ │   │ com.c.app/ │              │
│  └────────────┘   └────────────┘   └────────────┘              │
└─────────────────────────────────────────────────────────────────┘

Each sandboxed app gets its own container directory and can only access:

  • Its container directory
  • Resources explicitly granted via entitlements
  • User-selected files (via Open/Save dialogs)
  • Resources granted through security-scoped bookmarks

Sandbox Containers

Sandboxed apps store their data in containers:

# List sandbox containers
$ ls ~/Library/Containers/
com.apple.Notes
com.apple.Safari
com.example.myapp

# View a container's structure
$ ls ~/Library/Containers/com.apple.Safari/
Data

$ ls ~/Library/Containers/com.apple.Safari/Data/
Documents
Library

# Each container has standard directories
$ ls ~/Library/Containers/com.apple.Safari/Data/Library/
Application Support
Caches
HTTPStorages
Preferences
Saved Application State

# Container metadata
$ ls -la ~/Library/Containers/com.apple.Safari/
total 0
drwx------  3 david  staff   96 Jan 15 10:00 .
drwx------  5 david  staff  160 Jan 15 10:00 ..
drwx------  5 david  staff  160 Jan 15 10:00 Data
lrwxr-xr-x  1 david  staff   72 Jan 15 10:00 .com.apple.containermanagerd.metadata.plist -> ...

Group Containers

Apps can share data through group containers:

# Group containers
$ ls ~/Library/Group\ Containers/
group.com.apple.notes
group.com.example.shared

# Apps with the same app group entitlement can access shared containers

The sandbox-exec Command

The sandbox-exec command runs processes with sandbox restrictions. While primarily used for testing, it demonstrates sandbox capabilities:

Basic Usage

# Run a command with no network access
$ sandbox-exec -n no-network curl https://example.com
curl: (6) Could not resolve host: example.com

# Run with no file write access
$ sandbox-exec -n no-write touch /tmp/test
touch: /tmp/test: Operation not permitted

# List built-in profiles (may vary by macOS version)
$ ls /System/Library/Sandbox/Profiles/
# Note: Many profiles are embedded and not visible as files

Built-in Sandbox Profiles

macOS includes several built-in profiles:

ProfileDescription
no-internetBlocks all network access
no-networkBlocks network access
no-writeBlocks file writes
no-write-except-temporaryAllows writes only to temp
pure-computationMinimal access for computation only
# Examples using built-in profiles
$ sandbox-exec -n no-internet python3 -c "import urllib.request; print(urllib.request.urlopen('https://example.com').read())"
# Fails with network error

$ sandbox-exec -n no-write-except-temporary bash -c 'echo test > /tmp/ok.txt && echo test > ~/fail.txt'
# First write succeeds, second fails

Custom Sandbox Profiles

Create custom profiles using Scheme-like syntax (SBPL - Sandbox Profile Language):

;; my-sandbox.sb - Custom sandbox profile
(version 1)

;; Start with deny-all
(deny default)

;; Allow reading most files
(allow file-read*)

;; Allow writing to specific directories
(allow file-write*
    (subpath "/tmp")
    (subpath (param "TMPDIR")))

;; Allow specific network access
(allow network-outbound
    (remote tcp "example.com:443"))

;; Allow process execution
(allow process-exec)
(allow process-fork)

;; Allow reading system libraries
(allow file-read-data
    (subpath "/usr/lib")
    (subpath "/System/Library"))

;; Allow mach services for basic functionality
(allow mach-lookup
    (global-name "com.apple.system.logger"))

Use the custom profile:

$ sandbox-exec -f my-sandbox.sb /path/to/command

Profile Variables

Pass variables to sandbox profiles:

# Profile using parameter
# (allow file-write* (subpath (param "WRITEPATH")))

$ sandbox-exec -f my-profile.sb -D WRITEPATH=/tmp/myapp /path/to/command

Debugging Sandbox Profiles

# Enable sandbox tracing (verbose)
$ sandbox-exec -f my-profile.sb -D _TRACE=1 /path/to/command

# View sandbox violations in system log
$ log show --predicate 'subsystem == "com.apple.sandbox"' --last 5m

# Stream sandbox violations
$ log stream --predicate 'subsystem == "com.apple.sandbox"'

App Sandbox Entitlements

Apps declare their sandbox capabilities through entitlements:

Common Sandbox Entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <!-- Enable App Sandbox -->
    <key>com.apple.security.app-sandbox</key>
    <true/>

    <!-- File Access -->
    <key>com.apple.security.files.user-selected.read-write</key>
    <true/>

    <key>com.apple.security.files.downloads.read-write</key>
    <true/>

    <!-- Network Access -->
    <key>com.apple.security.network.client</key>
    <true/>

    <key>com.apple.security.network.server</key>
    <true/>

    <!-- Hardware Access -->
    <key>com.apple.security.device.camera</key>
    <true/>

    <key>com.apple.security.device.microphone</key>
    <true/>

    <key>com.apple.security.device.usb</key>
    <true/>

    <!-- Personal Information -->
    <key>com.apple.security.personal-information.addressbook</key>
    <true/>

    <key>com.apple.security.personal-information.calendars</key>
    <true/>

    <key>com.apple.security.personal-information.location</key>
    <true/>

    <!-- Temporary Exception (for legacy code) -->
    <key>com.apple.security.temporary-exception.files.absolute-path.read-write</key>
    <array>
        <string>/usr/local/</string>
    </array>
</dict>
</plist>

Viewing App Entitlements

# View entitlements of a running app
$ codesign -d --entitlements - /Applications/Safari.app

# Check if an app is sandboxed
$ codesign -d --entitlements - /Applications/Notes.app 2>&1 | grep "app-sandbox"
[Key] com.apple.security.app-sandbox
[Value]
    [Bool] true

# View entitlements as XML
$ codesign -d --entitlements :- /Applications/Safari.app

File Access Entitlements

EntitlementAccess Granted
files.user-selected.read-onlyRead files user opens
files.user-selected.read-writeRead/write user-opened files
files.downloads.read-onlyRead ~/Downloads
files.downloads.read-writeRead/write ~/Downloads
files.pictures.read-onlyRead ~/Pictures
files.pictures.read-writeRead/write ~/Pictures
files.music.read-onlyRead ~/Music
files.music.read-writeRead/write ~/Music
files.movies.read-onlyRead ~/Movies
files.movies.read-writeRead/write ~/Movies

Network Entitlements

<!-- Outgoing connections -->
<key>com.apple.security.network.client</key>
<true/>

<!-- Incoming connections (server) -->
<key>com.apple.security.network.server</key>
<true/>

Security-Scoped Bookmarks

Sandboxed apps can maintain access to user-selected files across launches using security-scoped bookmarks:

# Security-scoped bookmarks are created programmatically
# They allow persistent access to files outside the container

# View bookmark data (binary format)
$ xxd -l 100 ~/Library/Containers/com.example.app/Data/Library/Application\ Support/bookmarks.data

This is primarily an API feature, but understanding it helps debug sandbox behavior.

Checking Sandbox Status

For Running Processes

# Check if a process is sandboxed
$ ps -p <PID> -o flags
# Look for sandbox flag

# More detailed check using asctl (App Sandbox status)
$ asctl sandbox check --pid <PID>

# List sandboxed processes
$ ps aux | while read line; do
    pid=$(echo "$line" | awk '{print $2}')
    if sandbox-check 2>/dev/null; then
        echo "$line"
    fi
done

For Applications

# Check if an app bundle is sandboxed
$ codesign -d --entitlements - /path/to/app.app 2>&1 | grep -A1 "app-sandbox"

# Quick script to check sandbox status
#!/bin/bash
check_sandbox() {
    local app="$1"
    if codesign -d --entitlements - "$app" 2>&1 | grep -q "com.apple.security.app-sandbox"; then
        echo "Sandboxed: $app"
    else
        echo "Not sandboxed: $app"
    fi
}

check_sandbox /Applications/Safari.app
check_sandbox /Applications/iTerm.app

Sandbox Violations

When a sandboxed app tries to do something it’s not allowed to, a violation occurs:

Viewing Violations

# View recent sandbox violations
$ log show --predicate 'subsystem == "com.apple.sandbox" AND category == "violation"' --last 1h

# Stream violations in real-time
$ log stream --predicate 'subsystem == "com.apple.sandbox" AND category == "violation"'

# Example violation log:
# Sandbox: MyApp(1234) deny(1) file-read-data /private/etc/passwd

Common Violation Patterns

# File access violation
# Sandbox: app(pid) deny file-read-data /path/to/file

# Network violation
# Sandbox: app(pid) deny network-outbound

# Mach service violation
# Sandbox: app(pid) deny mach-lookup com.apple.some.service

# Hardware access violation
# Sandbox: app(pid) deny device-camera

Testing Sandbox Restrictions

Create test environments to verify sandbox behavior:

#!/bin/bash
# test-sandbox.sh - Test sandbox restrictions

# Test no-write sandbox
echo "Testing no-write sandbox..."
sandbox-exec -n no-write bash -c 'echo test > /tmp/test.txt' 2>&1 && echo "FAIL" || echo "PASS: Write blocked"

# Test no-network sandbox
echo "Testing no-network sandbox..."
sandbox-exec -n no-network curl -s https://example.com 2>&1 | grep -q "Could not resolve" && echo "PASS: Network blocked" || echo "FAIL"

# Test custom profile
cat > /tmp/test-sandbox.sb << 'EOF'
(version 1)
(deny default)
(allow file-read* (subpath "/"))
(allow file-write* (subpath "/tmp"))
(deny file-write* (subpath "/tmp/restricted"))
(allow process-exec)
(allow process-fork)
EOF

mkdir -p /tmp/restricted

echo "Testing custom sandbox..."
sandbox-exec -f /tmp/test-sandbox.sb bash -c 'echo test > /tmp/ok.txt' 2>&1 && echo "PASS: /tmp write allowed" || echo "FAIL"
sandbox-exec -f /tmp/test-sandbox.sb bash -c 'echo test > /tmp/restricted/fail.txt' 2>&1 && echo "FAIL" || echo "PASS: /tmp/restricted blocked"

rm /tmp/test-sandbox.sb /tmp/ok.txt 2>/dev/null
rmdir /tmp/restricted 2>/dev/null

Container Management

Viewing Container Contents

# List all containers
$ ls ~/Library/Containers/

# Find container for a specific app
$ ls -la ~/Library/Containers/ | grep Safari

# View container size
$ du -sh ~/Library/Containers/com.apple.Safari/

# Find containers using significant space
$ du -sh ~/Library/Containers/* 2>/dev/null | sort -hr | head -10

Resetting Containers

# Remove an app's container (resets app to fresh state)
# WARNING: Deletes all app data
$ rm -rf ~/Library/Containers/com.example.app/

# The container is recreated when the app launches

Sandboxed apps see a different filesystem view:

# Inside a sandboxed app, ~ points to the container
# ~/Library is actually ~/Library/Containers/com.example.app/Data/Library

# Some directories are symlinked to real locations
$ ls -la ~/Library/Containers/com.apple.Safari/Data/
# Desktop -> /Users/david/Desktop (if allowed by entitlements)

Sandbox Profiles for System Services

Many macOS system services run sandboxed:

# View sandbox profiles in use
$ sudo find /System -name "*.sb" -type f 2>/dev/null

# System sandbox profiles location
$ ls /System/Library/Sandbox/Profiles/ 2>/dev/null

# Many profiles are now embedded in the Sandbox kernel extension
# and not visible as separate files

Practical Examples

Restricting a Build Process

# Run a build with restricted file access
$ cat > build-sandbox.sb << 'EOF'
(version 1)
(deny default)
(allow file-read*)
(allow file-write*
    (subpath "/tmp")
    (subpath (param "BUILD_DIR")))
(allow process-exec)
(allow process-fork)
(allow mach-lookup)
EOF

$ sandbox-exec -f build-sandbox.sb -D BUILD_DIR="$(pwd)/build" make

Running Untrusted Scripts

# Run an untrusted script with minimal permissions
$ cat > untrusted-sandbox.sb << 'EOF'
(version 1)
(deny default)
(allow file-read*
    (subpath "/usr/lib")
    (subpath "/usr/bin")
    (subpath "/bin")
    (subpath "/System/Library"))
(allow file-read-data (literal "/dev/null"))
(allow file-write-data (literal "/dev/null"))
(allow process-exec)
(allow process-fork)
EOF

$ sandbox-exec -f untrusted-sandbox.sb bash untrusted-script.sh

Summary

Sandboxing provides containment for macOS applications:

ComponentPurpose
App SandboxIsolates Mac App Store apps
ContainersPer-app data directories
EntitlementsDeclare required capabilities
sandbox-execRun commands with restrictions
Sandbox profilesDefine access rules

Key concepts:

  • Sandboxed apps have limited filesystem access
  • Access must be declared via entitlements
  • User can grant additional access via file dialogs
  • Violations are logged and can be monitored
  • System services also use sandboxing
# Check if app is sandboxed
$ codesign -d --entitlements - /path/to/app | grep app-sandbox

# Run command with restrictions
$ sandbox-exec -n no-network /path/to/command

# View sandbox violations
$ log show --predicate 'subsystem == "com.apple.sandbox"' --last 10m

Sandboxing is a critical defense-in-depth layer that limits the damage a compromised application can do, even if other security measures fail.