fix: add com.apple.secd mach-lookup for Keychain access on macOS 13+#215
Open
javabrett wants to merge 2 commits into
Open
fix: add com.apple.secd mach-lookup for Keychain access on macOS 13+#215javabrett wants to merge 2 commits into
javabrett wants to merge 2 commits into
Conversation
Background ---------- The sandbox profile already allows `(allow mach-lookup (global-name "com.apple.SecurityServer"))` with the stated intent of permitting Keychain operations. This works correctly on macOS 12 (Monterey) and earlier. On macOS 13 (Ventura) and later, Apple refactored the Keychain subsystem as part of the data-protection improvements introduced in that release: the Security Entry Daemon (`secd`, Mach service name `com.apple.secd`) became the primary handler for credential CRUD operations, with `SecurityServer` (`securityd`) retained as a compatibility shim for older code paths. The result is that on macOS 13+, Keychain write and delete operations made by sandboxed processes - including those using the `apple-tool:` partition for password-less access - route through `com.apple.secd`, not `com.apple.SecurityServer`. A sandbox profile that only allows `SecurityServer` blocks these operations silently (no EPERM visible to the caller, just a failed return code from the Security framework), which manifests as errors like: "Failed to delete keychain entry" observed in applications that refresh short-lived OAuth tokens stored in the Keychain, such as Claude Code. The token refresh network call succeeds but the refreshed credential cannot be persisted back to the Keychain, forcing repeated re-authentication. Why secd is safe to allow ------------------------- `com.apple.secd` is the same class of grant as `com.apple.SecurityServer`, which the profile already includes. Allowing Mach IPC to `secd` does not grant the sandboxed process any capability that was not already intended: - The Keychain still enforces its own per-item ACL for each operation. Processes without an explicit ACL entry rely on `partition_id: apple-tool:` which `secd` validates - this is unchanged. - Applications cannot access Keychain items belonging to other applications without an explicit ACL entry for each item, regardless of whether `secd` IPC is allowed. - No credential is readable, writable, or deletable through `secd` that was not already accessible through `SecurityServer`. The grant shifts which daemon routes the call, not what the call is permitted to do. On macOS 13+ the existing `SecurityServer` allow is effectively a no-op for Keychain writes. Omitting `secd` makes the built-in security grant misleading: it appears to allow Keychain access but does not function on the OS versions where most users are now running. References ---------- - Apple Platform Security guide, "Keychain data protection" section - macOS 13 Ventura release notes on Security framework changes - WWDC 2022 session on data protection and Keychain - Observed failure: Claude Code OAuth token refresh writing to "Claude Code-credentials" keychain entry fails on macOS 13+ inside the srt sandbox; succeeds after adding this rule Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
javabrett
added a commit
to javabrett/sandbox-runtime
that referenced
this pull request
Apr 13, 2026
Identifies the local build containing two fixes pending upstream: - fix/cloudsdk-proxy-type-invalid (PR anthropic-experimental#179) - fix/keychain-secd-mach-lookup (PR anthropic-experimental#215)
Companion fix to the com.apple.secd mach-lookup change. The `security` CLI (and similar tools) read security.mac.sandbox.sentinel as a pre-flight sandbox check. When the read fails with EPERM they abort before attempting any Keychain operation - so the secd mach-lookup fix alone is not sufficient for CLI-based Keychain access. Diagnosis: with the secd fix applied, running security add-generic-password inside srt still failed. Capturing all sandbox denials via: log stream --predicate 'eventMessage CONTAINS "Sandbox:"' --style compact showed only one denial during the failing call: deny(1) sysctl-read security.mac.sandbox.sentinel No secd deny, no file-write deny. The sentinel check was the sole remaining blocker. The sentinel is enforced by seatbelt (not a deeper kernel mechanism): default sandbox profiles simply don't include an allow rule for it, making it readable only from unsandboxed processes by convention. An explicit (allow sysctl-read ...) in srt's profile is sufficient to unblock it. srt's other enforcement rules (mach-lookup, file-write, network) remain intact. Adds a test asserting the sentinel sysctl is in the generated profile, with a comment documenting the diagnostic process. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The sandbox profile allows `(allow mach-lookup (global-name "com.apple.SecurityServer"))` with the intent of permitting Keychain operations. This works on macOS 12 (Monterey) and earlier. On macOS 13 (Ventura) and later it silently fails for write and delete operations, because Apple moved those operations to a different daemon.
Observed symptom: applications that store short-lived OAuth credentials in the Keychain -- such as Claude Code storing its access token under `"Claude Code-credentials"` -- can read the token but cannot write the refreshed token back after expiry. The Security framework returns a failure code without raising EPERM, surfacing as:
```
Failed to delete keychain entry
```
The token refresh network call succeeds. The credential cannot be persisted. The user is forced to re-authenticate repeatedly.
Root cause (two-part fix)
Part 1: com.apple.secd mach-lookup
In macOS 13 (Ventura), Apple refactored the Keychain subsystem. The Security Entry Daemon (`secd`, registered as `com.apple.secd`) became the primary handler for Keychain credential CRUD operations. `SecurityServer` (`securityd`) was retained for compatibility but no longer handles writes and deletes on modern macOS.
A sandboxed process making a Keychain write or delete call on macOS 13+ sends the Mach IPC message to `com.apple.secd`. If that service name is not in the sandbox allow-list, the IPC is silently dropped.
Part 2: security.mac.sandbox.sentinel sysctl
After applying the secd fix, the macOS `security` CLI (`/usr/bin/security`) still failed inside srt with `UNIX[Operation not permitted]`. Capturing all sandbox denials via:
```bash
log stream --predicate 'eventMessage CONTAINS "Sandbox:"' --style compact
```
showed only one denial during a failing `security add-generic-password` call:
```
deny(1) sysctl-read security.mac.sandbox.sentinel
```
No secd deny, no file-write deny. The `security` CLI reads `security.mac.sandbox.sentinel` as a pre-flight sandbox check. When the read fails with EPERM it aborts before attempting any Keychain operation -- so the secd fix alone was not sufficient.
The sentinel is enforced by seatbelt (not a deeper kernel mechanism): default sandbox profiles simply don't include an allow rule for it. An explicit `(allow sysctl-read (sysctl-name "security.mac.sandbox.sentinel"))` is sufficient to unblock it. srt's other enforcement rules (mach-lookup, file-write, network) remain intact.
Why these grants are safe
secd: same class of permission as `SecurityServer`, which the profile already includes. The Keychain enforces its own per-item ACL for every operation -- the grant changes which daemon routes the call, not what the call may do.
sentinel sysctl: the sentinel is a convention, not a security boundary. It signals to callers that they are in a sandbox, prompting them to self-restrict. srt's actual security enforcement is the seatbelt profile itself; allowing the sentinel read simply prevents tools from prematurely refusing to operate.
Changes
Relation to #179
This PR is a companion to #179 (fix `CLOUDSDK_PROXY_TYPE=https` -> `http`), also submitted from observed failures running tools inside the srt sandbox.