What does the App Sandbox actually change in a running Mac process, and how do you test it without a paid account?

20.8.1 · L3 · Interview

Predict first

What do the unsandboxed and sandboxed runs report?

// === main.swift ===
import Foundation
let fm = FileManager.default
print("home in container:", NSHomeDirectory().contains("/Library/Containers/"))
let outside = URL(fileURLWithPath: "/Users/Shared/e20-probe-\(getpid()).txt")
print("write /Users/Shared:", (try? Data("x".utf8).write(to: outside)) != nil ? "ok" : "denied")
try? fm.removeItem(at: outside)
print("write own tmp:", (try? Data("x".utf8).write(to: fm.temporaryDirectory.appendingPathComponent("p.txt"))) != nil ? "ok" : "denied")
print("read /etc/hosts:", (try? Data(contentsOf: URL(fileURLWithPath: "/etc/hosts"))) != nil ? "ok" : "denied")
print("container id:", ProcessInfo.processInfo.environment["APP_SANDBOX_CONTAINER_ID"] ?? "none")
// === Info.plist ===
<?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>
<key>CFBundleExecutable</key><string>S</string><key>CFBundleIdentifier</key><string>test.e20.sbx</string><key>CFBundleName</key><string>S</string><key>CFBundlePackageType</key><string>APPL</string><key>CFBundleVersion</key><string>1</string>
</dict></plist>
// === sb.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>
<key>com.apple.security.app-sandbox</key><true/>
</dict></plist>
  • Unsandboxed: real home, /Users/Shared ok. Sandboxed: container home, /Users/Shared denied, tmp ok, /etc/hosts ok
  • Both runs identical
  • Sandboxed run cannot write anywhere

Mental model. A hotel room keycard: it opens your room and the lobby, and every other door needs a keycard that someone explicitly encoded for you.

Compile one small program and run it twice. Unsandboxed, it sees the real home directory, can write to /Users/Shared, and can read /etc/hosts. After signing the same binary, wrapped in an app bundle with a bundle identifier, with just com.apple.security.app-sandbox, the home directory becomes ~/Library/Containers/<bundle id>/Data, the write to /Users/Shared is denied, its own temporary directory is still writable, reading /etc/hosts is still allowed (system files are readable) and the environment variable APP_SANDBOX_CONTAINER_ID is set. Everything else you want, the network, the camera, a user-chosen folder, needs its own entitlement (com.apple.security.network.client, files.user-selected.read-write), and any grant from the user is persisted with a bookmark (Q842). The check signs ad hoc (codesign -s -), which is enough to test sandbox behaviour; it needs no developer account.

Two practical lessons from running it. A bare command-line binary signed with the sandbox entitlement is killed (exit status 133, a trap) when launched directly, so test sandboxing in an .app bundle with an Info.plist; and the first sandboxed launch creates a container directory that the system protects, so you cannot simply rm it afterwards (use a stable test bundle identifier so you create it once). Use the denial as a test: assert that your app writes only under its container or a chosen folder, and read the sandbox violations in Console (“deny file-write-create”) when a feature fails only in the sandboxed build. Not run: a Mac App Store submission, a Developer ID build, and entitlements that need provisioning (iCloud, Keychain access groups).

The entitlement moves home into the container and denies writes outside it, while system files stay readable.

Common traps

  • Developing unsandboxed and enabling the sandbox at submission. File, network and helper-tool code fails late; develop with the sandbox on.
  • Testing the sandbox with a bare executable. It traps; use an app bundle with a bundle identifier.

They might follow up with

  • Is the sandbox required outside the Mac App Store? No, but Hardened Runtime and notarization are expected for direct distribution (Q841).
  • Where do I read violations? Console, filtered by the sandbox subsystem.

Related questions

  • 20.3.2 What does a Mac app need that an iOS app does not: the menu bar, windows, the App Sandbox, Hardened Runtime…
  • 20.8.3 What is the difference between a stored path and a bookmark, and how do security-scoped bookmarks work in a…
  • 20.8.2 What do the Hardened Runtime flag and its exception entitlements change, and what can you inspect about a…

What do the Hardened Runtime flag and its exception entitlements… →