Skip to content

macOS security architecture: signing, sandbox, SIP, TCC

Six interlocking layers — code signing, AMFI, entitlements, sandbox profiles, SIP, TCC, and the SEP — that together decide what code is allowed to do on a Mac.

Published 8 min read

macOS doesn't have a security system. It has six, layered, each enforcing a different thing. Code can pass one and fail another; a hole in one is usually plugged by the next. This article maps the layers and how they interact, with pointers to the open-source bits where they exist.

The layers, roughly bottom to top:

  1. Code signing — was this binary signed by someone, and is its signature intact?
  2. AMFI — is this signed code allowed to load and run on this machine, by this user?
  3. Entitlements — what privileges does the signature grant this binary?
  4. Sandbox — what files / IPC / network is this process allowed to touch?
  5. SIP — is the kernel allowing this even if root said yes?
  6. TCC — has the user explicitly consented to camera / microphone / files / location?

Plus the Secure Enclave (SEP) sitting alongside as the root of trust for keys.

1. Code signing — the foundation

Every Mach-O binary on a modern Mac is signed. The signature is embedded in the binary itself (in the LC_CODE_SIGNATURE load command), not in a separate file. It includes:

  • A SHA-256 hash of every code page.
  • The signing identity's certificate chain.
  • A blob of entitlements (more on these below).
  • A timestamp from Apple's timestamp authority.
apple-oss-distributions/SecurityOSX/libsecurity_codesigning/lib/SecCode.cppThe userspace side of code-signing — what `codesign -dv` queries.View on GitHub(line )

The kernel computes page hashes lazily as pages fault in, comparing against the signature. If a page's hash doesn't match (someone modified the binary on disk), the page fault fails with a kill signal to the process — you can't run modified signed code without re-signing.

Signing identities come in tiers:

  • Apple-signed system binaries (everything under /System).
  • Developer ID (third-party apps notarized by Apple after a malware scan).
  • App Store (the App Store's own pipeline, which also requires entitlement vetting).
  • Ad-hoc / self-signed (developer builds, open-source software users compile themselves).
  • Unsigned (rare; refused to run by default since Gatekeeper).

2. AMFI — Apple Mobile File Integrity

AMFI (yes, mobile — it was first an iOS thing) is the kernel extension that enforces what signed code is allowed to load. It's the gatekeeper between the signature on disk and the right to be exec'd on this device.

What AMFI does:

  • Refuses to load binaries whose signature is missing or invalid.
  • Maps the signing identity to a team identifier and stores it on the process.
  • Enforces library validation: a process can only load dylibs signed by Apple or by the same team.
  • On iOS, refuses to mark pages executable that weren't loaded from a signed binary (no JIT for arbitrary code).

AMFI lives in AppleMobileFileIntegrity.kext and is not open source. Its consequences are everywhere — every exec, every dlopen, every mprotect to RWX is gated through it.

3. Entitlements — granular privileges baked into the signature

An entitlement is a key-value pair inside the code signature. The kernel and various daemons read them to grant capabilities a process otherwise wouldn't have:

  • com.apple.security.network.client — may make outbound network connections.
  • com.apple.security.files.user-selected.read-write — may access files the user picked in an open/save panel.
  • com.apple.private.tcc.allow — may bypass certain TCC prompts. (Apple-only.)
  • com.apple.security.cs.allow-jit — may map pages as RWX (for a JavaScript engine, for example).

Crucially, entitlements are signed into the binary. You can't add an entitlement after the fact without invalidating the signature. This is what stops malware from giving itself privileges: it would have to re-sign, but it doesn't have Apple's (or your developer ID's) key.

The full vocabulary of entitlements is documented at developer.apple.com; many are restricted to Apple-internal use.

4. Sandbox — fine-grained policy enforcement

Every App Store app and many system daemons run sandboxed. The sandbox is a kernel-enforced policy engine evaluated on every controlled syscall.

Profiles are written in a Scheme-like DSL called SBPL (Sandbox Profile Language). A simplified example:

(version 1)
(deny default)
(allow process-fork)
(allow file-read* (subpath "/usr/lib"))
(allow file-read* (subpath "/System/Library"))
(allow mach-lookup (global-name "com.apple.distributed_notifications"))
apple-oss-distributions/xnubsd/kern/policy_check.cMAC framework hooks — where sandbox decisions get made on each controlled syscall.View on GitHub(line )

The kernel side (Sandbox.kext) compiles SBPL into bytecode the kernel evaluates. Like AMFI, the kext itself isn't open source, but the MAC (Mandatory Access Control) framework it plugs into is — see XNU's bsd/kern/policy_check.c and the mac_* hooks scattered through every subsystem.

Apps choose a profile via the sandbox_init libSystem call or an Info.plist key (com.apple.security.app-sandbox). Helper processes get tighter profiles than what the user could grant — a Safari renderer process can't even open arbitrary files, only ones the main browser process forwards descriptors for.

5. SIP — the kernel says no, even to root

System Integrity Protection is a runtime restriction the kernel enforces against all processes, including those running as root. SIP forbids:

  • Writing to anything under /System, /usr (except /usr/local), /bin, /sbin.
  • Loading unsigned kexts.
  • Attaching a debugger to Apple-signed processes.
  • Modifying the boot-args nvram values that control kernel security.
  • Setting certain process flags that would let it bypass other policies.

SIP is configured via csrutil in recoveryOS — you can't disable it from the running system, and disabling it requires booting into recovery and opting in explicitly.

On macOS 11+, SIP is reinforced by the Sealed System Volume: even if SIP were somehow bypassed, the cryptographic seal over /System means a modified system file won't boot. SIP is the runtime gate; SSV is the load-time cryptographic guarantee.

apple-oss-distributions/xnubsd/sys/csr.hcsrutil's kernel interface — flags that say which SIP protections are active.View on GitHub(line )

The "Apps would like to access your Contacts" dialogs come from TCC — Transparency, Consent, Control. TCC is a daemon (tccd) plus a SQLite database (~/Library/Application Support/com.apple.TCC/TCC.db) that records every grant/deny decision.

The protected categories include camera, microphone, location, contacts, calendars, full disk access, accessibility, screen recording, and several dozen others. Each is keyed to a process's bundle ID (or, for non-bundled binaries, its signing identity).

TCC interactions:

  • An app calls a TCC-protected API.
  • The kernel/framework checks if the process's bundle ID has a grant in TCC.db.
  • If not, the framework asks tccd to prompt the user.
  • The user's decision is persisted; next time, no prompt.

Apps can't query TCC.db themselves — only tccd can write to it. Re-signing a binary with a different identity makes it appear as a "new" app and prompts the user again.

The Secure Enclave — the trust anchor

The SEP is a separate ARM core on every modern Apple device, with its own RAM and its own OS (a stripped-down L4-family microkernel). Its job is to hold cryptographic keys and perform operations on them without the main CPU ever seeing the key material.

What lives there:

  • The device's UID — a per-device 256-bit key fused at manufacture, used to derive every other key.
  • Keychain items marked kSecAccessControlBiometryAny (Touch ID / Face ID-protected secrets).
  • The FileVault disk encryption key (wrapped by user password derivation).
  • The keys backing Apple Pay tokens.
  • Attestation keys for App Attest and DeviceCheck.

The main CPU talks to the SEP over a dedicated mailbox interface. Requests are bounded — "decrypt this", "sign this challenge", "release the FileVault key after the user types their password" — and the SEP enforces every policy without trusting the host.

This is why even a kernel exploit on the main CPU doesn't immediately compromise FileVault: the disk encryption key is unwrapped by the SEP and held only in dedicated hardware-isolated memory.

How the layers compose

A real example: an App Store app tries to write to /etc/hosts.

  1. Code signing validates the binary is intact. ✓
  2. AMFI validates the App Store team signed it. ✓
  3. Entitlements are checked — the app doesn't have any that would help here.
  4. Sandbox evaluates the syscall: an App Store app's profile denies all writes to /etc/*. ✗ — the write returns EPERM.

The user never sees a prompt; the write just fails. If the same write happened from a Developer ID-signed app not in the sandbox, layer 4 wouldn't apply, but layer 5 (SIP, if /etc is protected — it is for some files) would still block it.

What surprises newcomers

  • Most security on a Mac is not about being root. Root can't write to /System, can't disable SIP, can't attach to Apple-signed processes, can't read TCC-protected data — the policies are above root.
  • Notarization is malware scanning, not code review. Apple's notarization service runs a scanner on every Developer ID-signed app at submission; it doesn't audit code.
  • Library validation is what stops dylib injection. Even with root, you can't DYLD_INSERT_LIBRARIES a signed app — the dylib's team identifier has to match.
  • The "Allow apps from anywhere" Gatekeeper option went away in Sequoia. The replacement requires right-clicking → Open and confirming in System Settings.

For the security framework's open-source side:

apple-oss-distributions/SecurityOSX/libsecurity_codesigning/lib/SecCode.cppcodesign(1)'s actual implementation.View on GitHub(line ) apple-oss-distributions/SecurityOSX/libsecurity_keychain/lib/Keychain.cppKeychain — the high-level storage SEP-protected keys are exposed through.View on GitHub(line )

And read the BSD personality article — many of the kernel-side enforcement points are MAC framework hooks scattered through BSD's syscall handlers.