Series · 4 partsmacOS Security BoundariesPart 1 · You are here
  1. 1The App Was Sandboxed. The XPC Boundary Still Needed Authorization.You are here
  2. 2The Permission Was Granted. The Data Use Still Needed a Policy.
  3. 3The Helper Was Registered. Its Lifetime Outlived the App.
  4. 4The Event Was Visible. The Detection Still Needed Context.

The macOS boundary in 60 seconds

A macOS application is rarely one process with one set of permissions. The visible app may be sandboxed, notarized, signed, and protected by the Hardened Runtime. Its bundle may also contain an XPC service, login item, launch agent, launch daemon, system extension, command-line tool, or shared container. Each component can run with a different identity, lifetime, sandbox profile, entitlement set, and access to user or system resources.

That architecture is often good security engineering. Privileged or failure-prone work can be moved out of the user interface process and exposed through a narrow interface. But process separation does not create authorization by itself.

The receiving service must still decide:

  • which process is connected;
  • which code-signing identity that process satisfies;
  • whether the caller is allowed to invoke this operation;
  • whether the arguments name resources the caller is allowed to control;
  • whether the requested action preserves path, ownership, state, and user-consent invariants;
  • what authority and persistence remain after the visible app exits.

The core question is therefore not “is the app sandboxed?” It is:

Where does untrusted application intent become a file, process, preference, credential, or system-level effect?

That conversion point is the macOS authority boundary.

Seven protections that answer different questions

macOS layers several security mechanisms. They reinforce one another, but they are not synonyms.

MechanismPrimary question it answersWhat it does not prove
Code signingWhich signed code identity does this executable satisfy?That the code is safe or this caller is authorized for every operation
Notarization and GatekeeperDid distributed software pass platform distribution and launch checks?That runtime business logic and local IPC authorization are correct
Hardened RuntimeWhich process-integrity protections and exceptions apply?That files, services, tenants, or XPC methods enforce application policy
App SandboxWhich resources may this executable access after compromise?That another component in the bundle has the same sandbox or authority
TCC and user consentDid the user or administrator permit access to protected data or services?That the process uses the granted access only for the intended workflow
XPC and Service ManagementHow are separate processes launched and connected?That the listener authenticates the peer or authorizes each requested effect
SIP and platform protectionsWhich system locations and processes receive operating-system protection?That third-party application data, helpers, and custom protocols are correct

Apple describes App Sandbox as a kernel-enforced access-control technology that restricts an app to the capabilities it declares. Its purpose is containment: it reduces the harm available to compromised code. The App Sandbox documentation also makes clear that a sandboxed app can use separately configured tools and services when its design requires them.

The Hardened Runtime protects process integrity against classes of code injection, library loading, and memory-space tampering. Exceptions are expressed as entitlements on executables. It is a different boundary from file, privacy, IPC, and business authorization.

This distinction prevents a common reporting failure: listing platform protections as though their presence closes every path. A strong review records what each control actually constrains, then looks for the authority that crosses between components.

The app bundle is a graph of executables

visible applicationUSER · SANDBOX bundled XPC serviceSEPARATE PROCESS launch agentUSER SESSION launch daemonSYSTEM · ROOT system extensionRESTRICTED ENTITLEMENT authority inventoryUID · SANDBOX · RIGHTS message boundaryPEER · METHOD · INPUT system effectFILE · PROCESS · CONFIG negative controlWRONG PEER · DENY UNIT OF REVIEWone executable, one identity, one interface, one resulting effect
The product name is not the security principal. Every executable and service boundary needs its own code identity, entitlements, caller policy, accepted inputs, and observable effects.

Apple’s current XPC documentation describes three important service types:

  • a bundled XPC service is normally one process per client and follows the client’s lifetime;
  • a launch agent runs for the logged-in user and can communicate within that user session;
  • a launch daemon is system-wide, may start before login, and runs as root.

Those are different security propositions. Even two binaries signed by the same developer can have different entitlements and access. A review that extracts only the main application’s signature misses the components that may create the most significant effect.

Start with a component and authority inventory

Before exercising an interface, inventory the application bundle and every persistent component associated with it. On a customer-supplied test Mac, read-only inspection can begin with standard platform tools:

APP_PATH="/Applications/Example.app"

codesign -dvvv --entitlements :- "$APP_PATH"
find "$APP_PATH/Contents" -maxdepth 4 -type d \
  \( -name "XPCServices" -o -name "LoginItems" -o -name "LaunchAgents" \
  -o -name "LaunchDaemons" -o -name "SystemExtensions" \) -print

Repeat signature and entitlement inspection for every Mach-O executable, not just the outer bundle. Record the designated requirement, Team ID, signing identifier, Hardened Runtime state, sandbox entitlement, App Groups, Keychain groups, privacy-sensitive entitlements, runtime exceptions, and restricted framework entitlements.

Then inspect the component’s declared role:

plutil -p "$APP_PATH/Contents/Info.plist"
otool -L "$APP_PATH/Contents/MacOS/Example"

For launch items, preserve the relevant property list, executable location, label, arguments, working directory, environment, user context, and registration state. On macOS 13 and later, Service Management provides the supported model for registering login items, launch agents, and launch daemons from an app bundle. The presence of a helper in Contents/Library describes packaging; the service configuration and runtime identity describe authority.

A useful inventory has one row per executable:

FieldQuestion
Bundle-relative pathWhere is this executable packaged and updated?
Code requirementWhich signing identifier and Team ID does it satisfy?
Runtime identityWhich UID, session, and launch domain execute it?
Sandbox and entitlementsWhich platform restrictions and exceptions apply to this executable?
PersistenceIs it app-bound, login-bound, on-demand, or system-wide?
IPC names and interfacesWhich XPC, Mach, socket, Apple Event, URL, or file interfaces reach it?
Accepted callersHow does the receiver determine peer identity and authorization?
Privileged operationsWhich files, processes, settings, credentials, or devices can it affect?
Input ownershipWhich paths, identifiers, bookmarks, handles, and state values come from caller?
Evidence and uncertaintyWhat proves each property, and what remains inferred?

The output is an authority graph. A root process with no useful callable operation may be lower risk than a user process with Full Disk Access and a broad message interface. UID is one dimension, not the risk score.

Evidence plate showing a Photos.app bundle subset with the main application and a bundled XPC service as separate executable identities.
Evidence / 01 Two Mach-O identities inside one product bundle. Packaging is not a single security principal.

Entitlements are capabilities, not business authorization

Entitlements tell macOS which protected capability an executable may request or which exception applies. They do not say that every caller of that executable should receive that capability.

Suppose a helper can update a protected configuration file. Its signature and entitlement may be correct. The system may launch it exactly as designed. The helper must still decide:

  1. Is this connection from an approved client identity?
  2. Is this client allowed to request this operation now?
  3. Does the user or administrator authorize the business action?
  4. Are all referenced paths and objects owned by the expected principal?
  5. Will the helper open and validate the final resource safely?
  6. Is the resulting effect bounded, logged, and reversible?

The entitlement answers “may this executable attempt the operation?” The service method answers “may this caller cause this particular effect?”

This is also why TCC and sandbox conclusions must remain separate. User consent can grant a process access to protected data. App Sandbox can restrict filesystem or device access. Neither mechanism automatically validates the semantics of an XPC request or prevents an already-authorized process from misusing a broad operation.

Evidence plate comparing minimized entitlement subsets for a sandboxed main application and its bundled XPC service.
Evidence / 02 Effective entitlement subsets differ inside one bundle. The outer app is not a proxy for the service capability set.

Authenticate the peer, then authorize the operation

XPC gives the client and service a structured communication channel. The service still needs a caller policy. A process name, PID, bundle path, or client-supplied identifier is not a durable code identity.

Modern platform APIs allow an XPC listener or connection to enforce code-signing requirements. Apple documents that xpc_listener_set_peer_requirement checks incoming messages against a peer code-signing requirement and drops requests that do not satisfy it. Foundation’s setCodeSigningRequirement provides the corresponding peer requirement control for NSXPCConnection. You can experiment with these validation primitives and observe policy verdicts in the interactive macOS Code Requirement & Entitlement Evaluator.

Peer identity is the first gate, not the final policy:

XPC requestMETHOD · ARGS peer identitySIGNING REQUIREMENT operation policyRIGHT · STATE · USER argument policyTYPE · RANGE · OWNER resource resolutionFINAL OBJECT · HANDLE bounded effectCHANGE · AUDIT effect verificationEXPECTED · ACTUAL negative controlWRONG PEER · DENY TRUST IS NOT TRANSITIVEsigned client ≠ authorized method ≠ safe resource effect
A correct signing identity admits a peer to the protocol. Method-level authorization, argument ownership, final resource validation, and effect verification still belong to the service.
Evidence plate showing ALLOW for an approved client and DENY for a mismatched client under the same synthetic XPC request.
Evidence / 03 Same synthetic request, different peer identities: approved client reaches the method; mismatched client is denied before effect.

A robust decision chain is:

  1. Peer requirement: accept only the designated client identities and expected signing team.
  2. Connection context: bind the accepted connection to an immutable server-observed identity.
  3. Method allowlist: expose narrow operations rather than a generic command, path, or selector.
  4. Right evaluation: decide whether this caller and user context may request the operation.
  5. Typed arguments: reject ambiguous types, unexpected keys, excessive sizes, and invalid state.
  6. Resource binding: resolve caller-controlled references into approved resources without trusting textual paths alone.
  7. Effect check: confirm the actual file, process, preference, or system state changed exactly as intended.
  8. Audit and revocation: preserve caller identity, operation, target, outcome, and policy decision; invalidate authority when the app, account, or configuration changes.

The security property belongs to the whole chain. Improving only one gate can leave the same effect reachable through another interface.

The PID check fails in a way a black-box test cannot see

The recommendation above has a measurable consequence, so it is worth measuring rather than asserting.

The lab that accompanies this post now runs two servers that expose the same method and differ in exactly one respect. One resolves the peer with SecCodeCopyGuestWithAttributes and kSecGuestAttributePid, taking the PID from connection.processIdentifier. The other calls setCodeSigningRequirement and lets the platform evaluate the peer against the connection’s audit token.

Under normal conditions the two are indistinguishable from outside. Both accept the approved client and deny the mismatched one. Any assessment that consists of connecting with the wrong identity and checking whether it is refused will score both implementations as sound.

The difference appears only when the caller stops being honest about which binary holds the PID. A client signed under the denied identity puts its request in flight, then replaces its own process image with a decoy carrying the approved identity, using posix_spawn with POSIX_SPAWN_SETEXEC. The PID does not change. The server performs its check afterwards and resolves that PID to the decoy:

scenario                     client    server   result
normal                       allowed   weak     ALLOW
normal                       denied    weak     DENY
normal                       allowed   strict   ALLOW
normal                       denied    strict   DENY
PID-reuse race               denied    weak     ALLOW   <-- bypass
PID-reuse race               denied    strict   DENY

The weak server reports the bypassed request in the same terms it uses for a legitimate one, and then performs the effect for a caller whose own signature it had refused moments earlier:

peer=APPROVED decision=ALLOW gate=code-requirement
effect=BOUNDED input=synthetic

The strict server never reaches that state. setCodeSigningRequirement is evaluated by the platform against the audit token, which is a kernel-held property of the connection rather than a number the caller can recycle.

Two conclusions follow, and the second is the one that changes how a review is run. First, a peer check written against a PID is not a weaker version of a code-signing check; it is a different check that happens to agree with the correct one whenever nobody is attacking it. Second, you cannot audit this from the outside without attempting the race or reading the implementation. A service that refuses your unsigned client has told you nothing about whether it would refuse a client that lies.

The bench, the racer, and the decoy are in labs/macos-xpc-authority/. The weak server is kept on purpose: an instrument that claims to assess peer validation has to be shown catching an implementation that is genuinely unsound before a clean verdict on a real service means anything.

Path strings are requests, not resources

Privileged helpers often receive paths because the visible application selected a file, created a backup, downloaded an update, or asked for a configuration change. The service must not treat the string as proof that the caller owns the final object.

Between validation and use, filesystem state can change. Relative paths, symbolic links, aliases, mount points, hard links, case behavior, and directory replacement can alter what a textual path resolves to. A useful review asks:

  • who created each path component;
  • which process owns and can modify the parent directories;
  • whether validation and use address the same opened object;
  • whether the target may cross out of an approved container or product directory;
  • whether the helper follows links or changes ownership and permissions;
  • whether temporary files are created safely;
  • whether archive extraction, update staging, or restore operations preserve a fixed destination;
  • whether a file selected under user consent becomes a generic capability for later operations.

The safest interface carries the narrowest resource representation the operation needs. A helper that exposes “run this command” or “write these bytes to this arbitrary path” has delegated its own authority model to the caller.

Assess the boundary without turning the Mac into a target

A product review should use a dedicated customer-owned test Mac, disposable test accounts, synthetic files, and a reversible operation set. Start with static and observational evidence. Dynamic tests should change one variable at a time.

Test dimensionPositive controlNegative control
Peer identityApproved signed test client connectsUnapproved test client or mismatched requirement is rejected
MethodApproved client invokes one documented operationSame client invokes an unavailable or unauthorized operation
User rightAuthorized test user performs a permitted administrative taskOrdinary test user receives a denial before the effect
Resource ownershipHelper modifies a synthetic product-owned fileCaller-controlled path outside the approved root is rejected
Workflow stateRegistered component performs a current operationRemoved, disabled, or stale component cannot retain old authority
EffectExactly one expected change occurs and is loggedDenied request produces no file, process, setting, or permission change

Stop immediately if a test touches real user data, another account, production credentials, security settings outside the agreed lab, or a system component not owned by the product. Do not disable SIP, bypass TCC prompts, weaken endpoint controls, or install unsigned helpers merely to make a hypothesis easier to demonstrate. If the proof requires changing a platform protection, reframe the test or obtain an explicit lab-specific decision.

A fictional XPC authority review

Assume a product called HarborSync has a sandboxed graphical application and a launch daemon that performs a small set of backup and restore operations. The daemon runs as root and receives requests over XPC. The customer supplies a disposable Mac, two signed test clients, synthetic backup data, and one product-owned destination directory.

  1. The reviewer inventories the GUI, bundled XPC component, launch daemon, updater, and login item. Each executable receives its own signature, entitlement, UID, persistence, and interface record.
  2. The GUI is sandboxed and uses security-scoped access for user-selected data. The launch daemon is not treated as inheriting the GUI’s restrictions; its root authority and XPC listener are mapped separately.
  3. A normal restore from the approved client to the synthetic product directory succeeds. The created file, ownership, mode, audit event, and timestamp establish the positive control.
  4. A second signed test client from the same development team does not satisfy the listener’s designated requirement. Its request is rejected before the restore method runs. Team membership alone is not the product’s caller policy.
  5. The approved client requests a restore outside the product-owned root. The daemon rejects the resource after resolving the final destination. No file or temporary artifact appears.
  6. The customer removes the daemon’s authorization in System Settings. Existing clients lose the operation rather than preserving a hidden long-lived capability.
  7. One issue is found in the cleanup method: it accepts the correct peer but authorizes deletion using a caller-supplied backup identifier that is not bound to the active user’s backup set. In the synthetic lab, User A can request deletion of User B’s test backup.
  8. The test stops after one reversible record proves the boundary. The finding is framed as missing object authorization inside an authenticated XPC method—not as a sandbox escape or a macOS platform failure.

This distinction matters. The application was sandboxed. The service accepted the expected signed client. The missing decision was still local business authorization: which backup object this user was permitted to affect.

Evidence matrix

Observation or resultWhat it supportsWhat it cannot proveRequired next step
Main app has the App Sandbox entitlementThe main executable enters App SandboxHelper sandbox state, TCC state, XPC policy, or method safetyInspect every executable and runtime component
Bundle is notarized and accepted by GatekeeperDistribution and launch checks succeededAbsence of logic, IPC, update, or authorization vulnerabilitiesMap runtime boundaries and privileged effects
Hardened Runtime is enabledDefault runtime integrity restrictions applyWhich exception entitlements weaken a componentInspect executable-specific flags and entitlements
XPC service is packaged in the appA separate process and interface are part of the productPeer requirements, method authorization, or input ownershipRecover listener policy and operation catalogue
Launch daemon runs as rootThe component has system-level execution contextReachability, exploitable operation, or caller authorizationMap every interface and resulting effect
Listener enforces a signing requirementPeers must satisfy one code identity policyThat every accepted peer may invoke every methodTest operation and object authorization
Client PID or process name is checkedA transient process attribute influences policyStable code identity or resistance to process lifecycle changesReplace with platform-enforced code requirement where possible
Path is canonicalized before useOne textual normalization step occurredFinal-object identity, link safety, or validation/use consistencyBind the operation to a resolved approved resource
Unauthorized request returns an errorOne request path reported denialAbsence of side effects, partial writes, or queued workVerify filesystem, process, configuration, and audit state
Synthetic forbidden effect does not occurThe tested negative control preserved the stated invariantAll callers, methods, resources, versions, or lifecycle statesExpand by authority and effect, not blind message mutation

What developers and defenders should receive

The useful deliverable is not an entitlement dump. It is a component-to-effect model.

Executable inventory. Every app, helper, XPC service, login item, agent, daemon, extension, and tool with code requirement, entitlements, UID, launch domain, persistence, and update relationship.

Authority map. Files, processes, credentials, protected data, system settings, devices, and security events each component can affect.

Interface catalogue. XPC listener names, public methods, argument schemas, error behavior, connection lifetime, peer requirements, and alternate interfaces.

Decision matrix. Accepted client identities, method rights, user context, resource ownership, workflow state, expected effects, and denied controls.

Lifecycle results. Installation, registration, upgrade, disablement, logout, user switch, uninstall, and stale-client behavior. Persistent authority should have a visible owner and a defined revocation path.

Remediation contract. The exact receiver-side decision to add or narrow, the component owner, the minimum code requirement, the allowed methods and resources, effect-level logging, and a retest that proves both the intended success and intended denial.

The macOS topic begins with authority, not checklists

macOS Security will follow a consistent route:

  1. App Sandbox and XPC authority: where one process asks another to create a stronger effect.
  2. TCC and consent boundaries: how user-approved access becomes persistent application capability.
  3. Launch services and persistence: how login items, agents, daemons, updates, and ownership survive across lifecycle changes.
  4. Endpoint Security and observability: what defenders can prove about process, file, mount, and authorization events without confusing telemetry with prevention.

The platform already supplies strong primitives: signing requirements, entitlements, sandboxes, Hardened Runtime, user consent, Service Management, and structured IPC. The security review asks whether the product composed those primitives into the intended authority model.

The app window is only the visible component. The boundary is the moment a specific caller, method, resource, and state become a real system effect.

MITRE ATT&CK mapping

TacticTechnique IDTechnique nameVerification signal
ExecutionT1559.003Inter-Process Communication: XPC Serviceslaunchctl print gui/$UID/<service> connection inspect
Privilege EscalationT1548Abuse Elevation Control MechanismAudit caller code requirement against receiving helper
Defense EvasionT1553.001Subvert Trust Controls: Code SigningCheck peer codesign -dvvv and team ID boundary
Companion Lab macOS XPC Authority Boundary Field Kit

Minimal reproducible Swift client, privileged helper server, and signature requirement verification primitives are available in the project repository under labs/macos-xpc-authority/.

Browse Swift Lab Source ↗
Sources & freshness

How current is this note?

Sources checkedAugust 29, 2026

The latest source-review, content-update, or publication date is shown.

ReviewPublic sources reviewed

Primary public records were checked. Environment-specific behavior remains outside the claim unless separately reproduced.