In macOS 26 I noticed there is a section Menu Bar in System Settings which allows to toggle visibility of status items created with NSStatusItem. I'm assuming this is new, since I never noticed it before.
Currently my app has a menu item that allows toggling its status item, but now I wonder whether it should always create the status item and let the user control its visibility from System Settings. Theoretically, keeping this option inside the app could lead to confusion if the user has previously disabled the status item in System Settings, then perhaps forgot about it, and then tries to enable it inside the app, but apparently nothing happens because System Settings overrides the app setting. Should I remove the option inside the app?
This also makes me think of login items, which can be managed both in System Settings and inside the app via SMAppService. Some users ask why my app doesn't have a launch at login option, and I tell them that System Settings already offers that functionality. Since there is SMAppService I could offer an option inside the app that is kept in sync with System Settings, but I prefer to avoid duplicating functionality, particularly if it's something that is changed once by the user and then rarely (if ever) changed afterwards. But I wonder: why can login items be controlled by an app, and the status item cannot (at least I'm not aware of an API that allows to change the option in System Settings)? If the status item can be overridden in System Settings, why do login items behave differently?
Service Management
RSS for tagThe Service Management framework provides facilities to load and unload launched services and read and modify launched dictionaries from within an application.
Posts under Service Management tag
58 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I've recently upgraded to the RC candidates of macOS 26 and Xcode 26. The app I'm building has a helper tool using SMAppService. When I run the app and helper tool in macOS 15 or macOS 26, all works as expected. When it runs on macOS 13 or 14, which previously worked. The helper now crashes on launch with the following reason:
Termination Reason: CODESIGNING 4 Launch Constraint Violation
I found this developer session which seems to address this, but the plist I've added doesn't seem to satisfy the constraint.
https://developer.apple.com/videos/play/wwdc2023/10266/
Here are the contents of my new plist:
Are there any gotchas here that I might be missing?
Thanks!
To establish a privileged helper daemon from a command line app to handle actions requiring root privileges I still use the old way of SMJobBless. But this is deprecated since OSX 10.13 and I want to finally update it to the new way using SMAppService.
As I'm concerned with securing it against malicious exploits, do you have a recommended up-to-date implementation in Objective-C establishing a privileged helper and verifying it is only used by my signed app?
I've seen the suggestion in the documentation to use SMAppService, but couldn't find a good implementation covering security aspects. My old implementation in brief is as follows:
bool runJobBless () {
// check if already installed
NSFileManager* filemgr = [NSFileManager defaultManager];
if ([filemgr fileExistsAtPath:@"/Library/PrivilegedHelperTools/com.company.Helper"] &&
[filemgr fileExistsAtPath:@"/Library/LaunchDaemons/com.company.Helper.plist"])
{
// check helper version to match the client
// ...
return true;
}
// create authorization reference
AuthorizationRef authRef;
OSStatus status = AuthorizationCreate (NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef);
if (status != errAuthorizationSuccess) return false;
// obtain rights to install privileged helper
AuthorizationItem authItem = { kSMRightBlessPrivilegedHelper, 0, NULL, 0 };
AuthorizationRights authRights = { 1, &authItem };
AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
status = AuthorizationCopyRights (authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL);
if (status != errAuthorizationSuccess) return false;
// SMJobBless does it all: verify helper against app and vice-versa, place and load embedded launchd.plist in /Library/LaunchDaemons, place executable in /Library/PrivilegedHelperTools
CFErrorRef cfError;
if (!SMJobBless (kSMDomainSystemLaunchd, (CFStringRef)@"com.company.Helper", authRef, &cfError)) {
// check helper version to match the client
// ...
return true;
} else {
CFBridgingRelease (cfError);
return false;
}
}
void connectToHelper () {
// connect to helper via XPC
NSXPCConnection* c = [[NSXPCConnection alloc] initWithMachServiceName:@"com.company.Helper.mach" options:NSXPCConnectionPrivileged];
c.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol (SilentInstallHelperProtocol)];
[c resume];
// call function on helper and wait for completion
dispatch_semaphore_t semaphore = dispatch_semaphore_create (0);
[[c remoteObjectProxy] callFunction:^() {
dispatch_semaphore_signal (semaphore);
}];
dispatch_semaphore_wait (semaphore, dispatch_time (DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC));
dispatch_release (semaphore);
[c invalidate];
[c release];
}
I abandoned Mac development back around 10.4 when I departed Apple and am playing catch-up, trying to figure out how to register a privileged helper tool that can execute commands as root in the new world order. I am developing on 13.1 and since some of these APIs debuted in 13, I'm wondering if that's ultimately the root of my problem.
Starting off with the example code provided here:
https://developer.apple.com/documentation/servicemanagement/updating-your-app-package-installer-to-use-the-new-service-management-api
Following all build/run instructions in the README to the letter, I've not been successful in getting any part of it to work as documented. When I invoke the register command the test app briefly appears in System Settings for me to enable, but once I slide the switch over, it disappears. Subsequent attempts to invoke the register command are met only with the error message:
`Unable to register Error Domain=SMAppServiceErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedFailureReason=Operation not permitted}
The app does not re-appear in System Settings on these subsequent invocations. When I invoke the status command the result mysteriously equates to SMAppService.Status.notFound.
The plist is in the right place with the right name and it is using the BundleProgram key exactly as supplied in the sample code project. The executable is also in the right place at Contents/Resources/SampleLaunchAgent relative to the app root.
The error messaging here is extremely disappointing and I'm not seeing any way for me to dig any further without access to the underlying Objective-C (which the Swift header docs reference almost exclusively, making it fairly clear that this was a... Swift... Port... [Pun intended]).
To gain exclusive access to keyboard HID devices like Amazon Fire Bluetooth remote controls, my app has been installing a privileged helper tool with SMJobBless in the past. The app - which also has Accessibility permissions - then invoked and communicated with that helper tool through XPC.
Now I'm looking into replacing that with a daemon installed through the newer SMAppService APIs, but running into a permission problem:
If I try to exclusively open a keyboard HID device from the SMAppService-registered XPC service/daemon (which runs as root as seen in Activity Monitor), IOHIDDeviceOpen returns kIOReturnNotPermitted.
I've spent many hours now trying to get it to work, but so far didn't find a solution.
Could it be that XPC services registered as a daemon through SMAppService do not inherit the TCC permissions from the invoking process (here: Accessibility permissions) - and the exclusive IOHIDDeviceOpen therefore fails?
I am trying to get a followup on the following thread.
It seems like it is not possible to prevent non-admin users from unloading launch agents through the terminal or deleting the user level ones.
We want to keep our Mac UI app running all the time, when a user is logged into to a mac machine (app resides in /Applications). To achieve this, we can use launchctl from within post-isntall script to load a plist file which resides in /Library/LaunchAgent.
How to prevent a user (without admin password) to unload the agent using launchctl from terminal?
I'm working on an enterprise product that's mainly a daemon (with Endpoint Security) without any GUI component. I'm looking into the update process for daemons/agents that was introduced with Ventura (Link), but I have to say that the entire process is just deeply unfun. Really can't stress this enough how unfun.
Anyway...
The product bundle now contains a dedicated Swift executable that calls SMAppService.register for both the daemon and agent.
It registers the app in the system preferences login items menu, but I also get an error.
Error registering daemon: Error Domain=SMAppServiceErrorDomain Code=1 "Operation not permitted" UserInfo={NSLocalizedFailureReason=Operation not permitted}
What could be the reason?
I wouldn't need to activate the items, I just need them to be added to the list, so that I can control them via launchctl.
Which leads me to my next question, how can I control bundled daemons/agents via launchctl? I tried to use launchctl enable and bootstrap, just like I do with daemons under /Library/LaunchDaemons, but all I get is
sudo launchctl enable system/com.identifier.daemon
sudo launchctl bootstrap /Path/to/daemon/launchdplist/inside/bundle/Library/LaunchDaemons/com.blub.plist
Bootstrap failed: 5: Input/output error (not super helpful error message)
I'm really frustrated by the complexity of this process and all of its pitfalls.
Hi,
We have a macOS application that contains a helper daemon that was registered with launchd using the SMAppService API and for the most part its been working okay until we tried to release an update that added an XPC service to the daemon. When users try to upgrade the software, the new service now fails to launch due to a launch constraint violation.
The Console log shows the following error after the upgrade:
AMFI: Launch Constraint Violation (enforcing), error info: c[5]p[1]m[1]e[0], (Constraint not matched) launching proc[vc: 6 pid: 1422]: /Applications/Mozilla VPN.app/Contents/Library/LaunchServices/org.mozilla.macos.FirefoxVPN.daemon, launch type 0, failure proc [vc: 6 pid: 1422]: /Applications/Mozilla VPN.app/Contents/Library/LaunchServices/org.mozilla.macos.FirefoxVPN.daemon
The service plist before the upgrade looked like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AssociatedBundleIdentifiers</key>
<string>org.mozilla.macos.FirefoxVPN</string>
<key>Label</key>
<string>org.mozilla.macos.FirefoxVPN.service</string>
<key>BundleProgram</key>
<string>Contents/MacOS/Mozilla VPN</string>
<key>ProgramArguments</key>
<array>
<string>Mozilla VPN</string>
<string>macosdaemon</string>
</array>
<key>UserName</key>
<string>root</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>1024</integer>
</dict>
<key>StandardErrorPath</key>
<string>/var/log/mozillavpn/stderr.log</string>
</dict>
</plist>
The updated plist changes the BundleProgram, removes ProgramArguments and adds MachServices, which results in the following plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AssociatedBundleIdentifiers</key>
<string>org.mozilla.macos.FirefoxVPN</string>
<key>Label</key>
<string>org.mozilla.macos.FirefoxVPN.service</string>
<key>BundleProgram</key>
<string>Contents/Library/LaunchServices/org.mozilla.macos.FirefoxVPN.daemon</string>
<key>MachServices</key>
<dict>
<key>org.mozilla.macos.FirefoxVPN.service</key>
<true/>
</dict>
<key>UserName</key>
<string>root</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>1024</integer>
</dict>
<key>StandardErrorPath</key>
<string>/var/log/mozillavpn/stderr.log</string>
</dict>
</plist>
On a fresh machine/VM, this works just fine, we only encounter the Launch Constraint Violation when upgrading from one version to the next.
We were hoping that the service could have been upgraded by calling unregisterWithCompletionHandler first, but this seems have no effect on the bug.
So, I guess my questions are:
Is there a way to diagnose what the launch constraints are for a service, and which why the constraints are being violated?
How does one go about changing the plist for a daemon installed via SMAppService?
Thanks,
Naomi
Hello, I am currently researching to develop an application where I want to apply the MacOS updates without the password prompt shown to the users.
I did some research on this and understand that an MDM solution can apply these patches without user intervention.
Are there any other ways we can achieve this? Any leads are much appreciated.
Hi, I'm working on an application on MacOS. It contains a port-forward feature on TCP protocol.
This application has no UI, but a local HTTP server where user can access to configure this application.
I found that my application always exit for unknown purpose after running in backgruond for minutes. I think this is about MacOS's background process controlling.
Source codes and PKG installers are here: https://github.com/burningtnt/Terracotta/actions/runs/16494390417
I've discovered that a system network extension can communicate with a LaunchDaemon (loaded using SMAppService) over XPC, provided that the XPC service name begins with the team ID.
If I move the launchd daemon plist to Contents/Library/LaunchAgents and swap the SMAppService.daemon calls to SMAppService.agent calls, and remove the .privileged option to NSXPCConnection, the system extension receives "Couldn't communicate with a helper application" as an error when trying to reach the LaunchAgent advertised service. Is this limitation by design?
I imagine it is, but wanted to check before I spent any more time on it.
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Service Management
XPC
System Extensions
Network Extension
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
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"
My app uses SMAppService to register a privileged helper, the helper registers without errors, and can be seen in System Settings. I can get a connection to the service and a remote object proxy, but the helper process cannot be found in Activity Monitor and the calls to the proxy functions seem to always fail without showing any specific errors. What could be causing this situation?
SMAppService Error 108 "Unable to read plist" on macOS 15 Sequoia - Comprehensive Test Case
Summary
We have a fully notarized SMAppService implementation that consistently fails with Error 108 "Unable to
read plist" on macOS 15 Sequoia, despite meeting all documented requirements. After systematic testing
including AI-assisted analysis, we've eliminated all common causes and created a comprehensive test
case.
Error: SMAppServiceErrorDomain Code=108 "Unable to read plist: com.keypath.helperpoc.helper"
📋 Complete Repository: https://github.com/malpern/privileged_helper_help
What We've Systematically Verified ✅
Perfect bundle structure: Helper at Contents/MacOS/, plist at Contents/Library/LaunchDaemons/
Correct SMAuthorizedClients: Embedded in helper binary via CREATE_INFOPLIST_SECTION_IN_BINARY=YES
Aligned identifiers: Main app, helper, and plist all use consistent naming
Production signing: Developer ID certificates with full Apple notarization and stapling
BundleProgram paths: Tested both Contents/MacOS/helperpoc-helper and simplified helperpoc-helper
Entitlements: Tested with and without com.apple.developer.service-management.managed-by-main-app
What Makes This Different
Systematic methodology: Not a "help me debug" post - we've done comprehensive testing
Expert validation: AI analysis helped eliminate logical hypotheses
Reproduction case: Minimal project that demonstrates the issue consistently
Complete documentation: All testing steps, configurations, and results documented
Use Case Context
We're building a keyboard remapper that integrates with https://github.com/jtroo/kanata and needs
privileged daemon registration for system-wide keyboard event interception.
Key Questions
Does anyone have a working SMAppService implementation on macOS 15 Sequoia?
Are there undocumented macOS 15 requirements we're missing?
Is Error 108 a known issue with specific workarounds?
Our hypothesis: This appears to be a macOS 15 system-level issue rather than configuration error, since
our implementation meets all documented Apple requirements but fails consistently.
Has anyone encountered similar SMAppService issues on macOS 15, or can confirm a working
implementation?
Topic:
App & System Services
SubTopic:
Processes & Concurrency
Tags:
Service Management
Notarization
My app is for personal use currently, so distribution won't be a problem. It registers a privileged helper using SMAppService, and I was wondering whether there is a way to customize the authorization dialog that the system presents to the user.
I am developing the application in Mac. My requirement is to start the application automatically when user login.
I have tried adding the plist file in launch agents, But it doesn't achieve my requirement.
Please find the code added in the launch agents
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.sftk.secure</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/Testing.app/Contents/MacOS/Testing</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<false/>
</dict>
</plist>
I have tried by adding manually in the setting, but it was opened sometimes and closed suddenly. On open manually it works.
Please provide a solution to start the application automatically on system starts
our app has a helper to perform privileged operations.
previously that helper was installed via SMJobBless() into the /Library/LaunchDaemons/ and /Library/PrivilegedHelperTools/
we also had a script that would install the helper from the command-line, which was essential for enterprise users that could not manually install the helper on all their employee's Macs. the script would copy the files to their install location and would use launchctl bootstrap system as the CLI alternative to SMJobBless(). the full script is here: https://pastebin.com/FkzuAWwV
due to various issues with the old SMJobBless() approach we have ported to helper to the new SMAppService API where the helpers do not need to be installed but remain within the app bundle ( [[SMAppService daemonServiceWithPlistName:HELPER_PLIST_NAME] registerAndReturnError:&err] )
however, we are having trouble writing a (remote-capable) CLI script to bootstrap the new helper for those users that need to install the helper on many Macs at once. running the trivial
sudo launchctl bootstrap system /Applications/MacUpdater.app/Contents/Library/LaunchDaemons/com.corecode.MacUpdaterPrivilegedInstallHelperTool2.plist
would just result in a non-informative:
Bootstrap failed: 5: Input/output error
various other tries with launchctl bootstrap/kickstart/enable yielded nothing promising.
so, whats the command-line way to install a SMAppService based helper daemon? obviously 'installing' means both 'registering' (which we do with registerAndReturnError in the GUI app) and 'approving' (which a GUI user needs to manually do by clicking on the notification or by going into System Settings).
thanks in advance!
p.s. we wanted to submit this as a DTS TSI, but those are no longer available without spending another day on a reduced sample projects. words fail me.
p.p.s. bonus points for a CLI way to give FDA permissions to the app!
Topic:
App & System Services
SubTopic:
Core OS
Tags:
Entitlements
Service Management
Command Line Tools
Hello,
I want to create a Launch Agent that triggers an executable upon changes in the /Applications folder.
The launch agent is normally a loaded but not running, and by adding /Applications to the WatchPath parameters in the plist, launchd is supposed to trigger the process, that will run and exit once done.
Sadly this seems not to be working uniformly. The script only works on one machine, in the the others the execcutable is never run. There seem not to be any meaningful differences in the launchd or system logs.
The same identical plist works perfectly when changing something in the user's ~/Applications folder. The script does its job and logs are visible.
Is there an undocumented limitation specifically for the /Applications folder that prevents luanchd to observe it in the WatchPaths? Maybe SIP not allowing access? But why does it work on my machine?
Here is an example of the ~/Library/LaunchAgents/com.company.AppName.LaunchAgent.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AssociatedBundleIdentifiers</key>
<string>com.company.AppName</string>
<key>KeepAlive</key>
<false/>
<key>Label</key>
<string>com.company.AppName.LaunchAgent</string>
<key>ProgramArguments</key>
<array>
<string>/Users/username/Library/Application Support/com.company.AppName/Launch Agent.app/Contents/MacOS/LaunchAgent</string>
</array>
<key>RunAtLoad</key>
<false/>
<key>WatchPaths</key>
<array>
<string>/Users/username/Applications</string>
<string>/Applications</string>
<string>/Network/Applications</string>
</array>
</dict>
</plist>
With the executable being a standard app bundle in /Users/username/Library/Application Support/com.company.AppName/Launch Agent.app
Thank you
I have created an OpenDirectory module based on the template and docs here: https://developer.apple.com/library/archive/releasenotes/NetworkingInternetWeb/RN_OpenDirectory/chapters/chapter-1.xhtml.html
After I copy my module in place and I set my module's configuration (see Configuration APIs section), my module does not get loaded. Currently the way I am able to start/reload it is sending a TERM signal to "opendirectoryd". (Launchctl refuses to stop it.) Then launchd restarts it, and my module gets started fine. Problem is that on some macOS this leads to system inresponsiveness for long time (even minutes).
I have tried HUP signal, odutil reset cache etc, they do not help, my module does not get recognized.
Is there a recommended way how to notify opendirectoryd about a new module?
Repro: My example module can be found here: https://www.dropbox.com/scl/fi/qb8pa100yy56n5hangad0/MyODModule-250527-131702.tar.gz?rlkey=m96vb1rrxc6hml878jn64ybc8&st=h22tl4cy&dl=0
To reproduce the behaviour, uncomment line 12 in register_odmodule.sh: "/usr/bin/killall opendirectoryd", and compile and install the module with
"make && sudo make install". And observe that it does not get loaded. Then "killall opendirectoryd", and observe that it got loaded.
(To test for loaded or not, you can read on the node it creates with dscl: "dscl /MyExample -list /", or just see that it is not started as a process with "ps").
Thanks for any help in advance!