Skip to content
This repository was archived by the owner on Aug 22, 2025. It is now read-only.

malpern/privileged_helper_help

Repository files navigation

macOS SMAppService Privileged Helper Implementation Guide

A complete guide for implementing privileged helper daemons using Apple's modern SMAppService API on macOS 15+. This repository provides a working example and step-by-step instructions to avoid common configuration pitfalls.

Reference implementation for keyboard remapping and system-level operations

Overview

This guide helps you implement a privileged helper daemon that can:

  1. Register with SMAppService - Use Apple's modern service management API
  2. Execute privileged operations - Run tasks requiring root permissions
  3. Communicate via XPC - Secure inter-process communication
  4. Work on macOS 15+ - Compatible with Sequoia and later

🎯 Common Issues Resolved

Error 108 "Unable to read plist"

This error typically occurs when mixing legacy SMJobBless configuration with modern SMAppService:

Root Causes:

  1. Mixed APIs: Using SMAuthorizedClients/SMPrivilegedExecutables (SMJobBless) with SMAppService
  2. Missing .plist extension: daemon(plistName:) requires the full filename including extension

Solutions: See Configuration Guide below.

πŸ“š Essential Documentation

Apple's Official Resources

LLM-Accessible Documentation

This repository includes servicemanagement-updating-helper-executables-from-earlier-versions-of-macos.md - a markdown version of Apple's documentation generated using llm.codes by @steipete.

Why this matters: Apple's documentation requires JavaScript, making it difficult for LLMs to parse. The markdown version enables AI assistants to provide accurate guidance based on official Apple documentation.

Generate your own: Use @steipete's llm.codes tool to convert any Apple documentation into LLM-readable markdown format.

βœ… Correct Configuration

Main Application

Info.plist - No SMAppService-specific keys needed:

<?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>
    <!-- Standard app keys only - no SMPrivilegedExecutables -->
</dict>
</plist>

Entitlements (helperpoc.entitlements):

<key>com.apple.security.app-sandbox</key>
<false/>

Swift Implementation:

// CRITICAL: Include .plist extension
private let helperPlistName = "com.keypath.helperpoc.helper.plist"

let service = SMAppService.daemon(plistName: helperPlistName)
try service.register()

Helper Daemon

Info.plist - Standard bundle keys only:

<?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>CFBundleIdentifier</key>
    <string>com.keypath.helperpoc.helper</string>
    <key>CFBundleName</key>
    <string>helperpoc-helper</string>
    <!-- No SMAuthorizedClients with SMAppService -->
</dict>
</plist>

Entitlements (helperpoc-helper.entitlements):

<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.developer.service-management.managed-by-main-app</key>
<true/>

Daemon Configuration (com.keypath.helperpoc.helper.plist):

<dict>
    <key>Label</key>
    <string>com.keypath.helperpoc.helper</string>
    <key>BundleProgram</key>
    <string>Contents/MacOS/helperpoc-helper</string>
    <key>AssociatedBundleIdentifiers</key>
    <array>
        <string>com.keypath.helperpoc</string> <!-- Main app identifier -->
    </array>
    <key>MachServices</key>
    <dict>
        <key>com.keypath.helperpoc.xpc</key>
        <true/>
    </dict>
</dict>

πŸ—οΈ Required Bundle Structure

helperpoc.app/
β”œβ”€β”€ Contents/
β”‚   β”œβ”€β”€ MacOS/
β”‚   β”‚   β”œβ”€β”€ helperpoc           # Main app binary
β”‚   β”‚   └── helperpoc-helper    # Helper daemon binary
β”‚   └── Library/
β”‚       └── LaunchDaemons/
β”‚           └── com.keypath.helperpoc.helper.plist

πŸ§ͺ Step-by-Step Implementation

1. Prerequisites

  • macOS 15.x Sequoia or later
  • Xcode 16.x
  • Apple Developer account with Developer ID certificates

2. Build and Test

# Clone repository
git clone https://github.com/malpern/privileged_helper_help.git
cd privileged_helper_help/helperpoc

# Build project
xcodebuild -project helperpoc.xcodeproj -scheme helperpoc -configuration Debug clean build

# Launch app
open build/Debug/helperpoc.app

3. Registration Process

  1. Click "Register Helper" - May initially show "Operation not permitted"
  2. Approve in System Settings - macOS will prompt for permission
  3. Verify "Helper Status: Enabled" - Registration successful
  4. Test functionality - Click "Test Helper" to verify privileged operations

4. Troubleshooting Checklist

Error 108 "Unable to read plist":

  • βœ… Remove SMAuthorizedClients from helper's Info.plist
  • βœ… Remove SMPrivilegedExecutables from main app's Info.plist
  • βœ… Add .plist extension to daemon(plistName:) parameter
  • βœ… Verify AssociatedBundleIdentifiers references main app

"Operation not permitted":

  • βœ… Normal first-time behavior - approve in System Settings
  • βœ… Check System Settings > General > Login Items & Extensions

Build failures:

  • βœ… Ensure helper binary is copied to correct bundle location
  • βœ… Verify daemon plist is embedded in Contents/Library/LaunchDaemons/

πŸ“– Key Differences: SMJobBless vs SMAppService

Feature SMJobBless (Legacy) SMAppService (Modern)
macOS Version 10.6-13.0 13.0+
Configuration SMAuthorizedClients/SMPrivilegedExecutables Bundle structure only
Helper Location Contents/Library/LaunchServices Contents/Library/LaunchDaemons
Plist Reference Without extension With .plist extension
Authorization Manual code signing validation Automatic with proper bundle
API Status Deprecated Current best practice

πŸ”§ Advanced Configuration

XPC Communication

The helper implements a simple XPC protocol for secure communication:

@objc protocol HelperProtocol {
    func createTestFile(reply: @escaping (Bool, String?) -> Void)
}

Logging and Debugging

  • App logs: Check console for SMAppService registration messages
  • Helper logs: Monitor system logs for daemon execution
  • Bundle verification: Use codesign -vvv to verify signatures

Production Deployment

  1. Code signing: Use Developer ID Application certificates
  2. Notarization: Required for distribution outside Mac App Store
  3. User approval: Always required for first-time registration

πŸ™ Acknowledgments

πŸ”— Additional Resources


This guide demonstrates working SMAppService implementation on macOS 15. For questions or issues, refer to Apple's official documentation or the developer forums.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published