i am trying to create a daemon with xpc for my app by referring to https://github.com/alienator88/HelperToolApp but i keep getting XPC remote proxy error: Couldn’t communicate with a helper application. All the identifiers all correct but the helper code is not reached.
Processes & Concurrency
RSS for tagDiscover how the operating system manages multiple applications and processes simultaneously, ensuring smooth multitasking performance.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Issue:
Background downloads using the flutter_downloader package work perfectly in debug mode and release mode when run directly from Xcode (plugged in).
However, when I create an archive build and install the app separately (via TestFlight or direct IPA install), the background download stops working as soon as the app is minimized.
✅ What I’ve already done
Info.plist
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
<string>fetch</string>
<string>processing</string>
<string>audio</string>
<string>push-to-talk</string>
</array>
AppDelegate.swift
import UIKit
import Flutter
import Firebase
import flutter_downloader
import BackgroundTasks
@main
@objc class AppDelegate: FlutterAppDelegate {
static let backgroundChannel = "com.example.app/background_service"
private var backgroundCompletionHandler: (() -> Void)?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
FirebaseApp.configure()
GeneratedPluginRegistrant.register(with: self)
FlutterDownloaderPlugin.setPluginRegistrantCallback(registerPlugins)
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self
}
if #available(iOS 13.0, *) {
registerBackgroundTask()
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
@available(iOS 13.0, *)
private func registerBackgroundTask() {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "com.example.app.process_download_queue",
using: nil
) { [weak self] task in
guard let self = self else { return }
self.handleDownloadQueueTask(task: task as! BGProcessingTask)
}
}
@available(iOS 13.0, *)
private func handleDownloadQueueTask(task: BGProcessingTask) {
scheduleNextDownloadTask()
let headlessEngine = FlutterEngine(name: "BackgroundTaskEngine", project: nil, allowHeadlessExecution: true)
headlessEngine.run()
let channel = FlutterMethodChannel(
name: AppDelegate.backgroundChannel,
binaryMessenger: headlessEngine.binaryMessenger
)
task.expirationHandler = {
channel.invokeMethod("backgroundTaskExpired", arguments: nil)
}
channel.invokeMethod("processNextInBackground", arguments: nil) { result in
task.setTaskCompleted(success: (result as? Bool) ?? false)
}
}
override func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void
) {
self.backgroundCompletionHandler = completionHandler
super.application(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler)
}
override func applicationDidEnterBackground(_ application: UIApplication) {
if #available(iOS 13.0, *) {
scheduleNextDownloadTask()
}
}
@available(iOS 10.0, *)
override func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
if #available(iOS 14.0, *) {
completionHandler([.list, .banner, .badge, .sound])
} else {
completionHandler([.alert, .badge, .sound])
}
}
@available(iOS 10.0, *)
override func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
completionHandler()
}
}
// MARK: - Helper
@available(iOS 13.0, *)
func scheduleNextDownloadTask() {
let request = BGProcessingTaskRequest(identifier: "com.example.app.process_download_queue")
request.requiresNetworkConnectivity = true
request.requiresExternalPower = false
request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
do {
try BGTaskScheduler.shared.submit(request)
print("BGTask: Download queue processing task scheduled successfully.")
} catch {
print("BGTask: Could not schedule download queue task: \(error)")
}
}
private func registerPlugins(registry: FlutterPluginRegistry) {
if !registry.hasPlugin("FlutterDownloaderPlugin") {
FlutterDownloaderPlugin.register(with: registry.registrar(forPlugin: "FlutterDownloaderPlugin")!)
}
}
🧩 Observations
Background download works correctly when:
The app is plugged in and run via Xcode (release/debug)
It stops working when:
The app is installed from an archived build (IPA/TestFlight) and minimized
All entitlements and background modes are properly added.
Provisioning profile includes required background modes.
❓Question
Is there any known limitation or signing difference between Xcode run and archived release builds that could cause URLSession background tasks not to trigger?
Has anyone faced a similar issue when using flutter_downloader on iOS 13+ with BGTaskScheduler or URLSession background configuration?
Any help or working setup example for production/TestFlight would be appreciated.
If I create a BGContinuedProcessingTaskRequest, register it, and then "do work" within it appropriately reporting progress, and before my task has finished doing all the work it had to do, its expirationHandler triggers...
does the task later try again?
Or does it lose the execution opportunity until the app is next re-launched to the foreground?
In my testing, I never saw my task execute again once expired (which suggests the latter?).
I was able to easily force this expiry by starting my task, backgrounding my app, then launching the iOS Camera App. My example is just using test code inspired from https://developer.apple.com/documentation/backgroundtasks/performing-long-running-tasks-on-ios-and-ipados
let request = BGContinuedProcessingTaskRequest(identifier: taskIdentifier, title: "Video Upload", subtitle: "Starting Upload")
request.strategy = .queue
BGTaskScheduler.shared.register(forTaskWithIdentifier: taskIdentifier, using: nil) { task in
guard let task = task as? BGContinuedProcessingTask else { return }
print("i am a good task")
var wasExpired = false
task.expirationHandler = {
wasExpired = true
}
let progress = task.progress
progress.totalUnitCount = 100
while !progress.isFinished && !wasExpired {
progress.completedUnitCount += 1
let formattedProgress = String(format: "%.2f", progress.fractionCompleted * 100)
task.updateTitle(task.title, subtitle: "Completed \(formattedProgress)%")
sleep(1)
}
if progress.isFinished {
print ("i was a good task")
task.setTaskCompleted(success: true)
} else {
print("i was not a good task")
task.setTaskCompleted(success: false)
}
}
try? BGTaskScheduler.shared.submit(request)
Apologies if this is clearly stated somewhere and I'm missing it.
For example, let’s propose an XPC service that can connect to websites. Suppose that I want to connect to Apple.com, microsoft.com, and ibm.com. Can 3 service objects be made between the service and client? Or does the service have to return an ID for each web connection, with the client needing to specify which connection ID along with a command?
Just trying to understand the documentation.
Obviously, we can send a request to the service to return all the data at once. Can the data arrive in pieces, involving either multiple async callbacks or a Combine Publisher?
In the header for workloop.h there is this note:
A dispatch workloop is a "subclass" of dispatch_queue_t which can be passed to all APIs accepting a dispatch queue, except for functions from the dispatch_sync() family. dispatch_async_and_wait() must be used for workloop objects. Functions from the dispatch_sync() family on queues targeting a workloop are still permitted but discouraged for performance reasons.
I have a couple questions related to this. First, I'd like to better understand what the alluded-to 'performance reasons' are that cause this pattern to be discouraged in the 'queues targeting a workloop' scenario. From further interrogation of the headers, I've found these explicit callouts regarding differences in the dispatch_sync and dispatch_async_and_wait API:
dispatch_sync:
Work items submitted to a queue with dispatch_sync() do not observe certain queue attributes of that queue when invoked (such as autorelease frequency and QOS class).
dispatch_async_and_wait:
Work items submitted to a queue with dispatch_async_and_wait() observe all queue attributes of that queue when invoked (inluding [sic] autorelease frequency or QOS class).
Additionally, dispatch_async_and_wait has a section of the headers devoted to 'Differences with dispatch_sync()', though I can't say I entirely follow the distinctions it attempts to draw.
Based on that, my best guess is that the 'performance reasons' are something about either QoS not being properly respected/observed or some thread context switching differences that can degrade performance, but I would appreciate insight from someone with more domain knowledge.
My second question is a bit more general – taking a step back, why exactly do these two API exist? It's not clear to me from the existing documentation I've found why I would/should prefer dispatch_sync over dispatch_async_and_wait (other than the aforementioned callout noting the former is unsupported on workloops). What is the motivation for preserving both these API vs deprecating dispatch_sync in favor of dispatch_async_and_wait (or functionally subsuming one with the other)?
Credit to Luna for originally posing/inspiring these questions.
iOS BGProcessingTask + Background Upload Not Executing Reliably on TestFlight (Works in Debug)
Description:
We are facing an issue with BGTaskScheduler and BGProcessingTask when trying to perform a background audio-upload flow on iOS. The behavior is inconsistent between Debug builds and TestFlight (Release) builds.
Summary of the Problem
Our application records long audio files (up to 1 hour) and triggers a background upload using:
BGTaskScheduler
BGProcessingTaskRequest
Background URLSession (background with identifier)
URLSession background upload task + AppDelegate.handleEventsForBackgroundURLSession
In Debug mode (Xcode → Run on device), everything works as expected:
BGProcessingTask executes
handleEventsForBackgroundURLSession fires
Background URLSession continues uploads reliably
Long audio files successfully upload even when the app is in background or terminated
However, in TestFlight / Release mode, the system does not reliably launch the BGProcessingTask or Background URLSession events.
Technical Details
We explicitly register BGTaskScheduler:
BGTaskScheduler.shared.register(
forTaskWithIdentifier: "example.background.process",
using: nil
) { task in
self.handleBackgroundProcessing(task: task as! BGProcessingTask)
}
We schedule it using:
let request = BGProcessingTaskRequest(identifier: "example.background.process")
request.requiresNetworkConnectivity = true
request.requiresExternalPower = false
try BGTaskScheduler.shared.submit(request)
We also use Background URLSession:
let config = URLSessionConfiguration.background(withIdentifier: sessionId)
config.sessionSendsLaunchEvents = true
config.isDiscretionary = false
AppDelegate.handleEventsForBackgroundURLSession is implemented correctly and works in Debug.
Issue Observed (TestFlight Only)
In TestFlight builds:
BGProcessingTask rarely triggers, or the system marks it as NO LONGER RUNNING.
Background upload tasks sometimes never start or complete.
No logs appear from our BGProcessingTask handler.
system logs show messages like:
NO LONGER RUNNING bgProcessing-example.background.process
Tasks running in group [com.apple.dasd.defaultNetwork] are 1!
This occurs most frequently for large audio uploads (30–60 minutes), while small files behave normally.
What We Have Verified
Proper Info.plist values:
Permitted background modes: processing, audio, fetch
BGTaskSchedulerPermittedIdentifiers contains our identifier
BGProcessingTask is being submitted successfully (no errors)
App has microphone permission + background audio works
Device plugged/unplugged doesn’t change outcome
Key Question for Apple
We need clarification on:
Why BGProcessingTask behave differently between Debug and TestFlight builds?
Are there additional restrictions or heuristics (related to file size, CPU usage, runtime, network load, or power constraints) that cause BGProcessingTask to be throttled or skipped in Release/TestFlight?
How can we guarantee a background upload continues reliably for large files (100MB–500MB) on TestFlight and App Store builds?
Is there an Apple-recommended pattern to combine BGProcessingTask + Background URLSession for long-running uploads?
Expected Result
Background uploads should continue reliably for long audio files (>30 minutes) when the app goes to background or is terminated, in the same way they currently function in Debug builds.
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
iOS
Background Tasks
Foundation
CFNetwork
As far as I understand, the main thread has a run loop.
When an iOS app launches, the process must keep the run loop running to stay alive.
Does that mean the main thread is the very first thread created when the process starts?
Regarding the Background Assets capability on iOS:
In the install scenario, resources defined as the "install" type are incorporated into the App Store download progress. Do resources of the "update" type in the update scenario also get incorporated into the App Store download progress in the same way?
If an exception occurs during the download of install-type resources and the download cannot proceed further, will the system no longer actively block users from launching the app and instead enable the launch button?
Currently, if a user has enabled automatic updates on their device, after the app is updated and released on the App Store, will the Background Assets download start immediately once the automatic update completes? Or does Background Assets have its own built-in scheduling logic that prevents it from running concurrently with the automatic update?
Hi All,
In continuation of this thread https://developer.apple.com/forums/thread/804439
I want to perform data upload after getting it from the BLE device. As state restoration wake should not deal with data upload i though of using a processing task to perform the data upload.
So the flow will be something like:
Connect to device -> listen to notification -> go to background -> wake from notification -> handle data download from ble device -> register processing task for data upload -> hopefully get the data uploaded
From reading about processing task i understand that the task execution is completely handled by the OS and depends on user behaviour and app usage. I even saw that if the user is not using the app for a while, the OS might not even perfoirm the task. So my quesiton is: does state restoration wakeup and perfroming data dowloads in the backgound considered app usage that will increase the likeluhood the task will get execution time?
Can we rely on this for a scenario that the user opens the app for the first time, register, onboard for ble, connect to devie and then put it in the background for days or weeks and only relying on state restoration and processing tasks to do their thing?
Sorry for the long read and appreciate your support!
Shimon
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Background Tasks
Core Bluetooth
Regarding App Update Synchronization During Workout Mode:
My watchOS app has workout mode enabled. When I update the app from the App Store on my iPhone while a workout session is active on my Apple Watch, the update does not sync to the watch. Why does this happen, and when can I expect the watch app to be updated?
Regarding Automatic App Launch After a Prolonged Shutdown:
I would like my watchOS app to launch automatically on my Apple Watch after it has been powered off for an extended period and then turned back on. Is this functionality possible to implement? If not, please provide a definitive answer regarding this capability.
I’m reaching out regarding an issue we’ve been experiencing with BGProcessingTask since upgrading to Xcode 26.1.1.
Issue Summary
Our daily background processing task—scheduled shortly after end‑of‑day—has stopped triggering reliably at night. This behavior started occurring only after updating to Xcode 26.1.1. Prior to this update, the task consistently ran around midnight, executed for ~10–15 seconds, and successfully rescheduled itself for the next day.
Expected Behavior
BGProcessingTask should run at/near the scheduled earliestBeginDate, which we set to roughly 2 hours after end-of-day.
The task should execute, complete, and then reschedule itself.
Actual Behavior
On devices running builds compiled with Xcode 26.1.1, the task does not trigger at all during the night.
The same code worked reliably before the Xcode update.
No system logs indicate rejection, expiration, or background task denial.
Technical Details
This is the identifier we use:
private enum DayEndProcessorConst {
static let taskIdentifier = "com.company.sdkmanagement.daysummary.manager"
}
The task is registered as follows: When app launched
BGTaskScheduler.shared.register(
forTaskWithIdentifier: DayEndProcessorConst.taskIdentifier,
using: nil
) { [weak self] task in
self?.handleDayEndTask(task)
}
And scheduled like this:
let date = Calendar.current.endOfDay(for: Date()).addingTimeInterval(60 * 60 * 2)
let request = BGProcessingTaskRequest(identifier: DayEndProcessorConst.taskIdentifier)
request.requiresNetworkConnectivity = true
request.requiresExternalPower = false
request.earliestBeginDate = date
try BGTaskScheduler.shared.submit(request)
As per our logs, tasks scheduled successfully
The handler wraps the work in an operation queue, begins a UI background task, and marks completion appropriately:
task.setTaskCompleted(success: true)
Could you please advise whether:
There are known issues with BGProcessingTask scheduling or midnight execution in Xcode 26.1.1 or iOS versions associated with it?
Any new entitlement, configuration, or scheduler behavior has changed in recent releases?
Additional logging or diagnostics can help pinpoint why the scheduler never fires the task?
I am developing a remote support tool for macOS. While we have successfully implemented a Privileged Helper Tool and LaunchDaemon architecture that works within an active Aqua session, we have observed a total failure to capture the screen buffer or receive input at the macOS Login Window.
Our observation of competitor software (AnyDesk, TeamViewer) shows they maintain graphical continuity through logout/restart. We are seeking the official architectural path to replicate this system-level access.
Current Technical Implementation
Architecture: A root-level LaunchDaemon manages the persistent network connection. A PrivilegedHelperTool (installed in /Library/PrivilegedHelperTools/) is used for elevated tasks.
Environment: Tested on macOS 14.x (Sonoma) and macOS 15.x (Sequoia) on Apple Silicon.
Capture Methods: We have implemented ScreenCaptureKit (SCK) as the primary engine and CGDisplayCreateImage as a fallback.
Binary Status: All components are signed with a Developer ID and have been successfully Notarized.
Observed Behavior & Blockers
The "Aqua" Success: Within a logged-in user session, our CGI correctly identifies Display IDs and initializes the capture stream. Remote control is fully functional.
The "Pre-Login" Failure: When the Mac is at the Login Window (no user logged in), the following occurs:
The Daemon remains active, but the screen capture buffer returns NULL or an empty frame.
ScreenCaptureKit fails to initialize, citing a lack of graphical context.
No TCC (Transparency, Consent, and Control) prompt can appear because no user session exists.
The "Bootstrap" Observation: We have identified that the loginwindow process exists in a restricted Mach bootstrap namespace that our Daemon (running in the System domain) cannot natively bridge.
Comparative Analysis (Competitor Benchmarking)
We have analyzed established remote desktop solutions like AnyDesk and Jump Desktop to understand their success at the login screen. Our findings suggest:
Dual-Context Execution: They appear to use a Global LaunchAgent with LimitLoadToSessionType = ["LoginWindow"]. This allows a child process to run as root inside the login window’s graphical domain.
Specialized Entitlements: These apps have migrated to the com.apple.developer.persistent-content-capture entitlement. This restricted capability allows them to bypass the weekly/monthly TCC re-authorization prompts and function in unattended scenarios where a user cannot click "Allow."
Questions
Entitlement Requirement: Is the persistent-content-capture entitlement the only supported way for a third-party app to capture the LoginWindow buffer without manual user intervention?
LaunchAgent Strategy: To gain a graphical context at the login screen, is it recommended to load a specialized agent into the loginwindow domain via launchctl bootstrap loginwindow ...?
ScreenCaptureKit vs. Legacy: Does ScreenCaptureKit officially support the LoginWindow session, or does it require an active Aqua session to initialize?
MDM Bypass: For Enterprise environments, can a Privacy Preferences Policy Control (PPPC) payload grant "Screen Recording" to a non-entitled Daemon specifically for the login window context?
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
macOS
Security
ScreenCaptureKit
Service Management
This is a successor to:
https://developer.apple.com/forums/thread/814231
I went into a slightly different direction. I generated more AI slop that use NSLock. Then I had the NSLock usage changed to Mutex usage. Now it crashes with:
Task 13: EXC_BREAKPOINT (code=1, subcode=0x18d29326c)
On one of the mutex closures. With an extended description:
warning: TypeSystemSwiftTypeRef::operator(): had to engage SwiftASTContext fallback for type $s7Combine10PublishersO21LineBreakingPublisherE11SplitAtZeroV12Subscription33_D18F5AAE73662968F407B0A79FBD1F8DLLCy_x_qd__GD
I put the class, a Subscription nested in its corresponding Publisher operator, in the given file
Subscription.txt
Hi,
I’m looking for clarification on what concurrency and consistency guarantees Apple provides when multiple targets (main app + Widget extensions) access shared storage.
Specifically:
1. UserDefaults (App Group / suiteName:)
• If multiple processes (app + multiple widget instances) read and write the same shared UserDefaults, what guarantees are provided?
• Is access serialized internally to prevent corruption?
• Are read–modify–write operations safe across processes, or can lost updates occur?
2. Core Data (shared SQLite store in App Group container)
• Is it officially supported for multiple processes to open and write to the same Core Data SQLite store?
• Are there recommended configurations (e.g. WAL mode) for safe multi-process access?
• Is Apple’s recommendation to have a single writer process?
3. FileManager (shared container files)
• If two processes write to the same file in an App Group container, what guarantees are provided by the system?
• Is atomic replaceItemAt the recommended pattern for safe cross-process updates?
Additionally:
• Do multiple widget instances count as separate processes with respect to these guarantees?
• Is there official guidance on best practices for shared persistence between app and widget extensions?
I want to ensure I’m following the correct architecture and not relying on undefined behavior.
Thanks.
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Foundation
WidgetKit
Core Data
Concurrency
General:
Forums subtopic: App & System Services > Processes & Concurrency
Forums tag: Background Tasks
Background Tasks framework documentation
UIApplication background tasks documentation
ProcessInfo expiring activity documentation
Using background tasks documentation for watchOS
Performing long-running tasks on iOS and iPadOS documentation
WWDC 2020 Session 10063 Background execution demystified — This is critical resource. Watch it! [1]
WWDC 2022 Session 10142 Efficiency awaits: Background tasks in SwiftUI
WWDC 2025 Session 227 Finish tasks in the background — This contains an excellent summary of the expected use cases for each of the background task types.
iOS Background Execution Limits forums post
UIApplication Background Task Notes forums post
Testing and Debugging Code Running in the Background forums post
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] Sadly the video is currently not available from Apple. I’ve left the link in place just in case it comes back.
This week I’m handling a DTS incident from a developer who wants to escalate privileges in their app. This is a tricky problem. Over the years I’ve explained aspects of this both here on DevForums and in numerous DTS incidents. Rather than do that again, I figured I’d collect my thoughts into one place and share them here.
If you have questions or comments, please start a new thread with an appropriate tag (Service Management or XPC are the most likely candidates here) in the App & System Services > Core OS topic area.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
BSD Privilege Escalation on macOS
macOS has multiple privilege models. Some of these were inherited from its ancestor platforms. For example, Mach messages has a capability-based privilege model. Others were introduced by Apple to address specific user scenarios. For example, macOS 10.14 and later have mandatory access control (MAC), as discussed in On File System Permissions.
One of the most important privilege models is the one inherited from BSD. This is the classic users and groups model. Many subsystems within macOS, especially those with a BSD heritage, use this model. For example, a packet tracing tool must open a BPF device, /dev/bpf*, and that requires root privileges. Specifically, the process that calls open must have an effective user ID of 0, that is, the root user. That process is said to be running as root, and escalating BSD privileges is the act of getting code to run as root.
IMPORTANT Escalating privileges does not bypass all privilege restrictions. For example, MAC applies to all processes, including those running as root. Indeed, running as root can make things harder because TCC will not display UI when a launchd daemon trips over a MAC restriction.
Escalating privileges on macOS is not straightforward. There are many different ways to do this, each with its own pros and cons. The best approach depends on your specific circumstances.
Note If you find operations where a root privilege restriction doesn’t make sense, feel free to file a bug requesting that it be lifted. This is not without precedent. For example, in macOS 10.2 (yes, back in 2002!) we made it possible to implement ICMP (ping) without root privileges. And in macOS 10.14 we removed the restriction on binding to low-number ports (r. 17427890). Nice!
Decide on One-Shot vs Ongoing Privileges
To start, decide whether you want one-shot or ongoing privileges. For one-shot privileges, the user authorises the operation, you perform it, and that’s that. For example, if you’re creating an un-installer for your product, one-shot privileges make sense because, once it’s done, your code is no longer present on the user’s system.
In contrast, for ongoing privileges the user authorises the installation of a launchd daemon. This code always runs as root and thus can perform privileged operations at any time.
Folks often ask for one-shot privileges but really need ongoing privileges. A classic example of this is a custom installer. In many cases installation isn’t a one-shot operation. Rather, the installer includes a software update mechanism that needs ongoing privileges. If that’s the case, there’s no point dealing with one-shot privileges at all. Just get ongoing privileges and treat your initial operation as a special case within that.
Keep in mind that you can convert one-shot privileges to ongoing privileges by installing a launchd daemon.
Just Because You Can, Doesn’t Mean You Should
Ongoing privileges represent an obvious security risk. Your daemon can perform an operation, but how does it know whether it should perform that operation?
There are two common ways to authorise operations:
Authorise the user
Authorise the client
To authorise the user, use Authorization Services. For a specific example of this, look at the EvenBetterAuthorizationSample sample code.
Note This sample hasn’t been updated in a while (sorry!) and it’s ironic that one of the things it demonstrates, opening a low-number port, no longer requires root privileges. However, the core concepts demonstrated by the sample are still valid.
The packet trace example from above is a situation where authorising the user with Authorization Services makes perfect sense. By default you might want your privileged helper tool to allow any user to run a packet trace. However, your code might be running on a Mac in a managed environment, where the site admin wants to restrict this to just admin users, or just a specific group of users. A custom authorisation right gives the site admin the flexibility to configure authorisation exactly as they want.
Authorising the client is a relatively new idea. It assumes that some process is using XPC to request that the daemon perform a privileged operation. In that case, the daemon can use XPC facilities to ensure that only certain processes can make such a request.
Doing this securely is a challenge. For specific API advice, see this post.
WARNING This authorisation is based on the code signature of the process’s main executable. If the process loads plug-ins [1], the daemon can’t tell the difference between a request coming from the main executable and a request coming from a plug-in.
[1] I’m talking in-process plug-ins here. Plug-ins that run in their own process, such as those managed by ExtensionKit, aren’t a concern.
Choose an Approach
There are (at least) seven different ways to run with root privileges on macOS:
A setuid-root executable
The sudo command-line tool
The authopen command-line tool
AppleScript’s do shell script command, passing true to the administrator privileges parameter
The osascript command-line tool to run an AppleScript
The AuthorizationExecuteWithPrivileges routine, deprecated since macOS 10.7
The SMJobSubmit routine targeting the kSMDomainSystemLaunchd domain, deprecated since macOS 10.10
The SMJobBless routine, deprecated since macOS 13
An installer package (.pkg)
The SMAppService class, a much-needed enhancement to the Service Management framework introduced in macOS 13
Note There’s one additional approach: The privileged file operation feature in NSWorkspace. I’ve not listed it here because it doesn’t let you run arbitrary code with root privileges. It does, however, have one critical benefit: It’s supported in sandboxed apps. See this post for a bunch of hints and tips.
To choose between them:
Do not use a setuid-root executable. Ever. It’s that simple! Doing that is creating a security vulnerability looking for an attacker to exploit it.
If you’re working interactively on the command line, use sudo, authopen, and osascript as you see fit.
IMPORTANT These are not appropriate to use as API. Specifically, while it may be possible to invoke sudo programmatically under some circumstances, by the time you’re done you’ll have code that’s way more complicated than the alternatives.
If you’re building an ad hoc solution to distribute to a limited audience, and you need one-shot privileges, use either AuthorizationExecuteWithPrivileges or AppleScript.
While AuthorizationExecuteWithPrivileges still works, it’s been deprecated for many years. Do not use it in a widely distributed product.
The AppleScript approach works great from AppleScript, but you can also use it from a shell script, using osascript, and from native code, using NSAppleScript. See the code snippet later in this post.
If you need one-shot privileges in a widely distributed product, consider using SMJobSubmit. While this is officially deprecated, it’s used by the very popular Sparkle update framework, and thus it’s unlikely to break without warning.
If you only need escalated privileges to install your product, consider using an installer package. That’s by far the easiest solution to this problem.
Keep in mind that an installer package can install a launchd daemon and thereby gain ongoing privileges.
If you need ongoing privileges but don’t want to ship an installer package, use SMAppService. If you need to deploy to older systems, use SMJobBless.
For instructions on using SMAppService, see Updating helper executables from earlier versions of macOS.
For a comprehensive example of how to use SMJobBless, see the EvenBetterAuthorizationSample sample code. For the simplest possible example, see the SMJobBless sample code. That has a Python script to help you debug your setup. Unfortunately this hasn’t been updated in a while; see this thread for more.
Hints and Tips
I’m sure I’ll think of more of these as time goes by but, for the moment, let’s start with the big one…
Do not run GUI code as root. In some cases you can make this work but it’s not supported. Moreover, it’s not safe. The GUI frameworks are huge, and thus have a huge attack surface. If you run GUI code as root, you are opening yourself up to security vulnerabilities.
Appendix: Running an AppleScript from Native Code
Below is an example of running a shell script with elevated privileges using NSAppleScript.
WARNING This is not meant to be the final word in privilege escalation. Before using this, work through the steps above to see if it’s the right option for you.
Hint It probably isn’t!
let url: URL = … file URL for the script to execute …
let script = NSAppleScript(source: """
on open (filePath)
if class of filePath is not text then
error "Expected a single file path argument."
end if
set shellScript to "exec " & quoted form of filePath
do shell script shellScript with administrator privileges
end open
""")!
// Create the Apple event.
let event = NSAppleEventDescriptor(
eventClass: AEEventClass(kCoreEventClass),
eventID: AEEventID(kAEOpenDocuments),
targetDescriptor: nil,
returnID: AEReturnID(kAutoGenerateReturnID),
transactionID: AETransactionID(kAnyTransactionID)
)
// Set up the direct object parameter to be a single string holding the
// path to our script.
let parameters = NSAppleEventDescriptor(string: url.path)
event.setDescriptor(parameters, forKeyword: AEKeyword(keyDirectObject))
// The `as NSAppleEventDescriptor?` is required due to a bug in the
// nullability annotation on this method’s result (r. 38702068).
var error: NSDictionary? = nil
guard let result = script.executeAppleEvent(event, error: &error) as NSAppleEventDescriptor? else {
let code = (error?[NSAppleScript.errorNumber] as? Int) ?? 1
let message = (error?[NSAppleScript.errorMessage] as? String) ?? "-"
throw NSError(domain: "ShellScript", code: code, userInfo: nil)
}
let scriptResult = result.stringValue ?? ""
Revision History
2025-03-24 Added info about authopen and osascript.
2024-11-15 Added info about SMJobSubmit. Made other minor editorial changes.
2024-07-29 Added a reference to the NSWorkspace privileged file operation feature. Made other minor editorial changes.
2022-06-22 First posted.
Service Management framework supports installing and uninstalling services, including Service Management login items, launchd agents, and launchd daemons.
General:
Forums subtopic: App & System Services > Processes & Concurrency
Forums tag: Service Management
Service Management framework documentation
Daemons and Services Programming Guide archived documentation
Technote 2083 Daemons and Agents — It hasn’t been updated in… well… decades, but it’s still remarkably relevant.
EvenBetterAuthorizationSample sample code — This has been obviated by SMAppService.
SMJobBless sample code — This has been obviated by SMAppService.
Sandboxing with NSXPCConnection sample code
WWDC 2022 Session 10096 What’s new in privacy introduces the new SMAppService facility, starting at 07˸07
BSD Privilege Escalation on macOS forums post
Getting Started with SMAppService forums post
Background items showing up with the wrong name forums post
Related forums tags include:
XPC, Apple’s preferred inter-process communication (IPC) mechanism
Inter-process communication, for other IPC mechanisms
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
XPC is the preferred inter-process communication (IPC) mechanism on Apple platforms. XPC has three APIs:
The high-level NSXPCConnection API, for Objective-C and Swift
The low-level Swift API, introduced with macOS 14
The low-level C API, which, while callable from all languages, works best with C-based languages
General:
Forums subtopic: App & System Services > Processes & Concurrency
Forums tag: XPC
Creating XPC services documentation
NSXPCConnection class documentation
Low-level API documentation
XPC has extensive man pages — For the low-level API, start with the xpc man page; this is the original source for the XPC C API documentation and still contains titbits that you can’t find elsewhere. Also read the xpcservice.plist man page, which documents the property list format used by XPC services.
Daemons and Services Programming Guide archived documentation
WWDC 2012 Session 241 Cocoa Interprocess Communication with XPC — This is no longer available from the Apple Developer website )-:
Technote 2083 Daemons and Agents — It hasn’t been updated in… well… decades, but it’s still remarkably relevant.
TN3113 Testing and Debugging XPC Code With an Anonymous Listener
XPC and App-to-App Communication forums post
Validating Signature Of XPC Process forums post
This forums post summarises the options for bidirectional communication
This forums post explains the meaning of privileged flag
Related tags include:
Inter-process communication, for other IPC mechanisms
Service Management, for installing and uninstalling Service Management login items, launchd agents, and launchd daemons
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Swift Concurrency Resources:
Forums tags: Concurrency
The Swift Programming Language > Concurrency documentation
Migrating to Swift 6 documentation
WWDC 2022 Session 110351 Eliminate data races using Swift Concurrency — This ‘sailing on the sea of concurrency’ talk is a great introduction to the fundamentals.
WWDC 2021 Session 10134 Explore structured concurrency in Swift — The table that starts rolling out at around 25:45 is really helpful.
Swift Async Algorithms package
Swift Concurrency Proposal Index DevForum post
Why is flow control important? forums post
Dispatch Resources:
Forums tags: Dispatch
Dispatch documentation — Note that the Swift API and C API, while generally aligned, are different in many details. Make sure you select the right language at the top of the page.
Dispatch man pages — While the standard Dispatch documentation is good, you can still find some great tidbits in the man pages. See Reading UNIX Manual Pages. Start by reading dispatch in section 3.
WWDC 2015 Session 718 Building Responsive and Efficient Apps with GCD [1]
WWDC 2017 Session 706 Modernizing Grand Central Dispatch Usage [1]
Avoid Dispatch Global Concurrent Queues forums post
Waiting for an Async Result in a Synchronous Function forums post
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] These videos may or may not be available from Apple. If not, the URL should help you locate other sources of this info.