Series · 4 partsmacOS Security BoundariesPart 3 · You are here
- 1The App Was Sandboxed. The XPC Boundary Still Needed Authorization.
- 2The Permission Was Granted. The Data Use Still Needed a Policy.
- 3The Helper Was Registered. Its Lifetime Outlived the App.You are here
- 4The Event Was Visible. The Detection Still Needed Context.
The macOS helper lifecycle in 60 seconds
A visible macOS application may close while its login item, launch agent, or launch daemon continues to run. That behavior can be correct: a sync client needs background work, a security product needs a system extension, and an updater may need a narrowly privileged service. The security boundary is not the existence of a helper. It is the relationship between who packaged it, who registered it, who approved it, where it runs, what it accepts, how it updates, and how it stops.
Modern Service Management APIs let an application control helper executables kept inside its signed
bundle. Apple’s SMAppService
documentation distinguishes login items, launch agents, and launch daemons and exposes registration,
unregistration, and status. Registration means the service can begin launching subject to the rules
and approval for that service type. It does not mean that every IPC client is authorized or that the
runtime effect matches the user’s expectation.
The useful pentest question is:
Which authority survives when the app window, user session, version, account, or product itself is no longer active?
Answering it requires lifecycle evidence, not a screenshot of Login Items and not a broad dump of the personal Mac’s background services.
Packaging, registration, approval, and execution are separate states
Four statements that sound similar have different security meanings:
- Packaged: a helper executable and property list are present inside a signed application bundle.
- Registered: the application asked Service Management to make the helper launchable.
- Approved: the person or administrator allowed the relevant background or system-level item.
- Running:
launchdcreated a process in a specific domain, user context, and session.
Apple documents that in macOS 13 and later, SMAppService is the supported control for bundled login
items, launch agents, and launch daemons. The details differ. A login item can start immediately and
at subsequent logins. A registered launch agent may bootstrap in the user’s session. A launch daemon
does not bootstrap until administrator approval and can return on subsequent boots. The registration
documentation
is explicit that registration is subject to user approval.
These transitions should become a state machine in the assessment:
Start from the supplied bundle, not the whole laptop
A privacy-safe review inventories one authorized product. It does not enumerate every personal login item, agent, daemon, process, or application installed on the assessor’s Mac.
For the named app artifact, record:
- each helper executable and its bundle-relative location;
- the enclosing bundle’s signature and each helper’s own designated requirement;
- the service property-list name, label, program path, arguments, and launch conditions;
- login item, user agent, system daemon, or system extension role;
- registration and approval state for the product under test;
- launch domain, UID, session availability, sandbox, and entitlements;
- XPC or other message interfaces and their peer requirements;
- update, rollback, uninstall, and orphan cleanup paths.
Read-only inspection can start with the customer-supplied artifact:
find CustomerApp.app/Contents/Library -maxdepth 3 \
\( -name 'LaunchAgents' -o -name 'LaunchDaemons' -o -name 'LoginItems' \) -print
codesign -dvvv --entitlements :- CustomerApp.app
plutil -p CustomerApp.app/Contents/Library/LaunchAgents/com.example.agent.plist
Repeat signature inspection for the actual helper executable. Do not infer helper identity from the outer app’s marketing name or Team ID alone. Two correctly signed components can have different identifiers, entitlements, sandbox state, and update behavior.
Apple’s migration guidance recommends keeping supported helper resources and their property lists in the signed app bundle. That reduces mutable installation material, but it does not remove the need to validate the service configuration or the runtime interface.
Model launch domains and authority explicitly
A login item, launch agent, and launch daemon are not interchangeable persistence labels.
| Component | Typical lifetime | Context | Important security question |
|---|---|---|---|
| Login item | login and user session | logged-in user | Does the visible feature need to start every login? |
| Launch agent | user session, on demand or persistent | per-user launch domain | Which same-session clients can reach its interface? |
| Launch daemon | boot or on demand | system domain, commonly root | Which authenticated client can turn requests into system effects? |
| System extension | system-managed lifecycle | restricted extension context | Which entitlement, approval, event source, and containing app control it? |
| Bundled XPC service | normally client-coupled | separate process | Does process separation also enforce peer and method authorization? |
Record the effective property-list semantics instead of assigning a risk score to RunAtLoad alone.
Demand-driven services may still be persistent in practice if a public Mach service repeatedly wakes
them. A process that exits cleanly may be relaunched after failure. Conversely, a daemon that exists
on disk but is disabled and unreachable does not prove active impact.
The authority graph should connect launch trigger → runtime principal → callable interface → system effect. A root label without a reachable method is not the finding; a user agent with broad privacy access and an unauthenticated interface may be more consequential.
Registration does not authorize the XPC caller
Service Management decides whether and how the helper can launch. XPC decides how processes exchange messages. The receiver still owns peer authentication, operation authorization, input validation, and effect verification.
For every callable method, ask:
- Which code requirement must the connecting process satisfy?
- Is that identity allowed to invoke this method in the current product and user state?
- Which arguments are chosen by the caller?
- Does the service resolve final files and objects safely in its own context?
- Can a lower-authority client request a higher-authority effect?
- What audit record distinguishes request, decision, outcome, and actual effect?
The synthetic XPC lab used in part one demonstrates this boundary without touching a system daemon. A temporary user-domain service accepted the same harmless input from two differently signed clients. The server admitted the expected signing identifier and rejected the mismatch before method execution, then the service was removed. That is a testing primitive, not evidence that a third-party product is vulnerable.
Update and rollback are identity transitions
An update can replace the containing app, helper executable, service property list, protocol version, and code requirement. Test it as a security migration.
Important cases include:
- old app with old helper;
- new app with new helper;
- new app encountering a still-running old helper;
- interrupted update between bundle replacement and service transition;
- rollback to an older protocol or signature requirement;
- app moved to another path after registration;
- helper crash during migration;
- revoked or disabled background permission during upgrade.
Fail closed without creating an unrecoverable system state. A new client should not silently weaken its peer requirement to talk to any leftover process. An old helper should not accept new operations it cannot validate. The updater should authenticate both source and destination artifacts and preserve a clear rollback boundary.
Version the XPC protocol and make unsupported combinations explicit. Tie high-impact operations to the current installed product state rather than accepting any binary ever signed by the same developer. Developer identity is often too broad for method authorization; the designated requirement should name the intended product and any permitted migration window.
Uninstall is a security feature
Deleting the visible .app is not proof that every helper stopped or that its state disappeared. The
product needs a supported removal path that unregisters services, stops background work, invalidates
credentials, and handles data according to the user’s retention choice.
Verify removal at four layers:
- registration: Service Management reports that the product’s helper is no longer registered;
- runtime: the named service cannot relaunch and the IPC name is unreachable;
- artifact: product-owned helper files and mutable configuration are removed as documented;
- authority: tokens, bookmarks, privileged approvals, queues, and remote registrations no longer produce effects.
Do not use a broad launchctl or filesystem dump as proof. Query the exact product label or test-domain
identifier. This is both more reliable and more respectful of the device owner’s privacy.
Evidence matrix
| Claim | Positive evidence | Negative control | What remains unproven |
|---|---|---|---|
| Helper is packaged as claimed | Bundle-relative path, per-executable signature, property-list hash | Altered copy fails signature verification | Runtime approval on another Mac |
| Registration creates the intended service | Exact SMAppService status and named service response | Unregistered state cannot launch | Legacy installer paths not supplied for review |
| Runtime identity matches design | Named launch domain, UID, entitlements, protocol version | Wrong session or wrong peer denied | All enterprise management combinations |
| High-authority methods authenticate callers | Approved code requirement reaches one synthetic effect | Mismatched signing identifier denied before method body | Unknown future signed products from same developer |
| Update preserves the boundary | Old-to-new and interrupted-update cases have explicit outcomes | Unsupported client/helper pair fails closed | Vendor update infrastructure outside scope |
| Uninstall removes effective persistence | Service unregistered, cannot relaunch, product state cleaned | Restart or login does not recreate marker | Data intentionally retained under documented policy |
Evidence should distinguish configuration, observation, and effect. A property list shows desired configuration. A process listing shows an observation at one instant. A bounded test proves whether a specific client can cause a specific result. None of those alone proves the entire lifecycle.
Remediation priorities
For most findings, repair the narrowest failed transition:
- use the current Service Management model instead of mutable installer scripts where supported;
- keep helper artifacts inside the signed bundle and verify each executable identity;
- request registration when the background feature is explained, not as unrelated setup noise;
- authenticate XPC peers with a narrow code requirement and authorize every method separately;
- minimize daemon operations and pass handles or structured intent instead of arbitrary commands;
- version protocols and reject unsafe old/new combinations;
- make disabled, denied, and crash states visible without coercive re-registration loops;
- log lifecycle decisions with product-scoped identifiers and no personal content;
- provide complete unregister and removal behavior, then regression-test it across login and reboot.
Persistence is not automatically malicious, and user approval is not automatically sufficient. A high-quality macOS product makes the helper’s purpose, authority, current state, and removal as clear as the visible application itself.
Sources
- Apple Developer Documentation, Service Management
- Apple Developer Documentation,
SMAppService - Apple Developer Documentation,
register() - Apple Developer Documentation, Updating helper executables from earlier versions of macOS
- Apple Developer Documentation, Updating your app package installer to use the new Service Management API
- Apple Developer Documentation, XPC
This is a source-reviewed assessment methodology. Its local example uses a temporary user-domain service and synthetic input; it does not enumerate a personal Mac’s background items or claim a third-party persistence vulnerability.
How current is this note?
The latest source-review, content-update, or publication date is shown.
Primary public records were checked. Environment-specific behavior remains outside the claim unless separately reproduced.
