Short version: we run a sandboxed helper as a hidden service account, started at boot by an SMAppService daemon. It works, even before login. But when the user turns our app off in Background App Activity, only the daemon stops. The helper keeps running. Is this setup supported, and what's the right way to manage the helper?
What we want
A Developer ID signed, notarized app (not Mac App Store) with a helper that parses untrusted input. The helper should:
- run as a dedicated, hidden, non-login local account;
- use App Sandbox, with its own container;
- be available before anyone logs in (after FileVault unlock).
What we built
An unsandboxed root LaunchDaemon, registered with SMAppService.daemon, runs this at boot:
launchctl bootstrap user/<serviceUID> <fixed-agent-plist>
The agent plist uses LimitLoadToSessionType=Background. The helper is a nested app in the same bundle, with com.apple.security.app-sandbox=true. We don't create a GUI session, change UID after the sandbox starts, or use private APIs.
What we measured
macOS 27.0 (26A428), arm64, dummy data only:
- Register and approve: the daemon starts. The helper starts as UID 60000, its container works, and reads outside it are denied.
- Turn the app off in Background App Activity: the daemon gets SIGTERM and stops. The helper keeps running (same PID).
- Turn it back on: the daemon starts again. Its
bootstrapreturns exit 5, because the old helper is still loaded. - Call
unregister(): the daemon stops. The helper keeps running. - Cold boot (tested with a plain
/Library/LaunchDaemonsjob, not yetSMAppService): the helper started and worked before login finished.
For comparison, running the same sandboxed helper as a system daemon with UserName set to this account fails before main: Incoming message euid:60000 does not match secinitd uid:0.
Questions
- Is this setup supported for shipping, including the sandbox starting before anyone logs in?
- Does the approval for daemon-bundled helpers cover a helper bootstrapped into another account's domain?
- Our plan: when the daemon gets SIGTERM, it runs
bootouton the helper and its domain, and it treatsbootstrapexit 5 as "already loaded". Is that the intended pattern, or is there a supported way for the helper to follow the app's Background App Activity setting? - If this setup isn't supported, what public mechanism gives a sandboxed helper its own non-root identity before login?
I can share the plists, entitlements and logs from a minimal reproducer.