Series · 4 partsmacOS Security BoundariesPart 3 · You are here
  1. 1The App Was Sandboxed. The XPC Boundary Still Needed Authorization.
  2. 2The Permission Was Granted. The Data Use Still Needed a Policy.
  3. 3The Helper Was Registered. Its Lifetime Outlived the App.You are here
  4. 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:

  1. Packaged: a helper executable and property list are present inside a signed application bundle.
  2. Registered: the application asked Service Management to make the helper launchable.
  3. Approved: the person or administrator allowed the relevant background or system-level item.
  4. Running: launchd created 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:

packagedSIGNED BUNDLE registeredSERVICE RECORD approvedUSER · ADMIN launchedDOMAIN · UID · SESSION runtime operationPEER · METHOD · EFFECT updatedNEW IDENTITY unregisteredNO RELAUNCH removedSTATE CLEANED failure or denialDISABLED · VISIBLE LIFECYCLE IS AUTHORITYinstall and uninstall are security transitions
The helper is not simply present or absent. Every transition changes who can launch it, which identity runs, and what remains after failure or removal.

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.

ComponentTypical lifetimeContextImportant security question
Login itemlogin and user sessionlogged-in userDoes the visible feature need to start every login?
Launch agentuser session, on demand or persistentper-user launch domainWhich same-session clients can reach its interface?
Launch daemonboot or on demandsystem domain, commonly rootWhich authenticated client can turn requests into system effects?
System extensionsystem-managed lifecyclerestricted extension contextWhich entitlement, approval, event source, and containing app control it?
Bundled XPC servicenormally client-coupledseparate processDoes 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:

  1. Which code requirement must the connecting process satisfy?
  2. Is that identity allowed to invoke this method in the current product and user state?
  3. Which arguments are chosen by the caller?
  4. Does the service resolve final files and objects safely in its own context?
  5. Can a lower-authority client request a higher-authority effect?
  6. 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.

registered helperTEST DOMAIN approved peerALLOW METHOD bounded effectSYNTHETIC MARKER mismatched peerDENY BEFORE CALL app exitsEXPECTED LIFETIME update or denialSAFE FAILURE unregisterNO RELAUNCH · CLEAN RETEST THE TRANSITIONSnot only the first successful launch
A lifecycle test proves the intended run path, the rejected peer, behavior after app exit or update, and complete unregistration.

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

ClaimPositive evidenceNegative controlWhat remains unproven
Helper is packaged as claimedBundle-relative path, per-executable signature, property-list hashAltered copy fails signature verificationRuntime approval on another Mac
Registration creates the intended serviceExact SMAppService status and named service responseUnregistered state cannot launchLegacy installer paths not supplied for review
Runtime identity matches designNamed launch domain, UID, entitlements, protocol versionWrong session or wrong peer deniedAll enterprise management combinations
High-authority methods authenticate callersApproved code requirement reaches one synthetic effectMismatched signing identifier denied before method bodyUnknown future signed products from same developer
Update preserves the boundaryOld-to-new and interrupted-update cases have explicit outcomesUnsupported client/helper pair fails closedVendor update infrastructure outside scope
Uninstall removes effective persistenceService unregistered, cannot relaunch, product state cleanedRestart or login does not recreate markerData 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

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.

Sources & freshness

How current is this note?

Sources checkedAugust 30, 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.