This document details Agent!'s security model and entitlements.
Agent! requires the following entitlements in Agent.entitlements:
| Entitlement | Purpose |
|---|---|
automation.apple-events |
AppleScript and ScriptingBridge automation |
cs.allow-unsigned-executable-memory |
Required for dlopen'd AgentScript dylibs |
cs.disable-library-validation |
Load user-compiled script dylibs at runtime |
assets.music.read-write |
Music library access via MusicBridge |
device.audio-input |
Microphone access for audio scripts |
device.bluetooth |
Bluetooth device interaction |
device.camera |
Camera capture (CapturePhoto script) |
device.usb |
USB device access |
files.downloads.read-write |
Read/write Downloads folder |
files.user-selected.read-write |
Read/write user-selected files |
network.client |
Outbound connections (API calls, web search) |
network.server |
Inbound connections (MCP HTTP/SSE transport) |
personal-information.addressbook |
Contacts access via ContactsBridge |
personal-information.calendars |
Calendar access via CalendarBridge |
personal-information.location |
Location services |
personal-information.photos-library |
Photos access via PhotosBridge |
keychain-access-groups |
Secure API key storage |
Protected macOS APIs require user approval. Agent handles TCC correctly:
| Component | TCC Grants |
|---|---|
run_agent_script, applescript_tool, TCC shell commands |
ALL (Accessibility, Screen Recording, Automation) |
execute_user_command (LaunchAgent) |
None |
execute_command (root) |
Separate context |
Rule: Use run_agent_script or applescript_tool for Accessibility/Automation tasks, not shell commands.
applescript_toolblocks destructive operations (delete,close,move,quit) by default- The AI must explicitly set
allow_writes: trueto permit them - This prevents accidental data loss from misinterpreted commands
Shell execution goes through two XPC services (Inter-Process Communication):
Agent.app (SwiftUI)
|
|-- UserService (XPC) → Agent.app.toddbruss.user (LaunchAgent, runs as current user — not privileged)
|-- HelperService (XPC) → Agent.app.toddbruss.helper (LaunchDaemon, runs as root — privileged)
The XPC boundary ensures:
- The Launch Agent handles everyday shell tasks as the current user (no elevated privileges)
- Root operations are isolated to the Launch Daemon
- Each XPC call is a discrete, auditable transaction
- File permissions are restored to the user after root operations
Agent! registers its Launch Agent and Launch Daemon via Apple's SMAppService framework (macOS 13+). This provides OS-enforced security that makes manual XPC audit token validation redundant:
- Code Signature Chain: macOS requires the Launch Agent and Launch Daemon to be embedded inside the signed app bundle and share the same Team ID (469UCUB275). The OS validates this at registration time — unsigned or differently-signed helpers are rejected by launchd before they can run.
- User Approval: SMAppService routes through System Settings → Login Items & Extensions, giving the user explicit control over which helpers are active.
- Lifecycle Management: launchd owns the helper lifecycle. The helpers can only be registered/unregistered through the SMAppService API, not by dropping arbitrary plists.
- No Manual Audit Token Checks Needed: Because the OS enforces that only code signed by the same developer can register and communicate through the Mach service, the
shouldAcceptNewConnectioncallback does not need to re-verify what the OS has already guaranteed. An unauthorized process cannot connect to the XPC service in the first place.
Agent! wraps SMAppService via SafeSMAppService and SafeSMAppServiceDaemon, which add plist validation safety checks before calling the framework methods.
Agent! prevents false-action claims — where an AI reports performing an action it never executed — with three independent layers:
The system prompt instructs the LLM to say "action not performed" if it did not call a tool. It may never claim to have searched, opened, clicked, ran, or found something without a matching tool_result.
If the LLM returns text claiming "I searched", "I opened", "I clicked", etc. but made zero tool calls in that turn, the app injects a correction tool_result telling the LLM to use the actual tool or admit it cannot perform the action. This is logged in the activity view as ⚠️ action not performed.
Apple Intelligence tool calls (accessibility, applescript, shell) are logged to the activity view with the 🍎 prefix showing exactly what was called and what it returned. If Apple AI's tools produce no substantive output (empty, just an exit code, or error), the request is automatically forwarded to the cloud LLM. Apple AI can only claim task completion when its tools return real evidence of work.
All tool execution flows through the app's dispatchTool() layer — the LLM never self-reports tool results. The flow is:
- LLM returns a
tool_useJSON block - Agent!'s dispatch layer executes the tool via XPC, shell, or in-process
- The real output goes back as
tool_result - The LLM summarizes the real result
The LLM cannot fabricate tool outputs because it never controls what tool_result contains — the app does.