Agent isolation design
August 15, 2026 ยท View on GitHub
Status: 0.2.0-rc.1 public release candidate; the provider now uses the fail-closed Broker by default. Local acceptance, Apple distribution gates, public publication, and archive hash re-verification pass. The 3-machine/24-hour external acceptance round is intentionally deferred.
Security objective
KeyringSeam must prevent a model-launched process running as the same macOS user from retrieving a managed credential without an explicit, contemporaneous user authorization. The protected attacker may invoke arbitrary shell commands, launch a copied broker binary, call Security.framework directly, and know the service and credential reference names.
The DSH host and in-process plugins remain trusted because ctx.credentials.resolve() intentionally returns a credential to them. macOS administrators, a compromised operating system, debugger attachment to the trusted host, and a user who approves an unexpected authorization prompt remain outside the boundary.
Required architecture
- Ship
KeyringSeamBroker.appas an app-like bundle, signed with Developer ID Application and Hardened Runtime. - Register the explicit bundle identifier
org.fieldnote.keyringseam.brokerand embed a matching Developer ID provisioning profile that authorizes its restricted Keychain entitlement. - Target the Data Protection Keychain with
kSecUseDataProtectionKeychain: true. Store each item in the broker's private access group withkSecAttrAccessibleWhenUnlockedThisDeviceOnlyand aSecAccessControluser-presence constraint. - Authenticate through
LAContextbefore any secret-bearing response. Cancellation, an unavailable biometric/passcode policy, a locked Keychain, missing entitlements, or an invalid signature fails closed. - Run one broker process for the provider lifetime over anonymous stdin/stdout pipes. Do not expose a socket, URL scheme, command-line
get, or one-shot unauthenticated read interface. Drain and terminate the broker when the provider is disposed. - Pin the expected Team ID, bundle identifier, designated requirement, entitlements, provisioning profile, Broker hash, and notarization evidence in the release manifest. Never fall back to the legacy file-based Keychain or plaintext storage.
- Keep inherited process credentials read-only and highest priority. Resolve managed values from the broker, then fall back to project and user
.envlayers from DSH's immutable launch-environment snapshot.
The macOS code signature must claim com.apple.application-identifier (not the iOS-only spelling) and one exact keychain-access-groups value. A provisioning profile may authorize that exact group or a matching Team-ID wildcard, but wildcard values are never placed in the broker's own signature.
External prerequisite
An ad-hoc CLI and a Developer ID-signed CLI with unprovisioned entitlements both returned errSecMissingEntitlement (-34018) in local prototypes. Apple documents that Data Protection Keychain access groups are restricted entitlements: a non-bundled executable must use an app-like structure and embed a matching distribution provisioning profile.
Creating the App ID and Developer ID provisioning profile changes the Apple Developer account and therefore requires explicit human approval. No release candidate may claim Agent isolation before the provisioned bundle runs the acceptance suite.
Local implementation checkpoint
The development tree contains a persistent, length-prefixed broker protocol, an environment-free Node lifecycle manager, a Data Protection Keychain implementation with a private access group and userPresence, and an app-bundle build script that refuses to run without both an explicit Developer ID identity and a matching provisioning profile. MacOSKeychainStore uses this Broker by default and the Cordis service disposes it with the provider.
The transport suite currently verifies one-process reuse, serialized requests, pre-disposal drain, post-disposal rejection, timeout, mismatched response IDs, malformed frame lengths, output caps, missing executable handling, empty child environment, and absence of secret-bearing executable arguments. A locally compiled unprovisioned broker completed the real binary framing exchange and returned errSecMissingEntitlement (-34018) as required; that negative result is a gate, not evidence that isolation is delivered.
npm run build:broker requires KEYRINGSEAM_SIGN_IDENTITY and KEYRINGSEAM_PROVISIONING_PROFILE to identify approved material. The build validates the profile's expiry, macOS platform, Team ID, application identifier, private Keychain group, and debugger entitlement before compiling or signing, and refuses to overwrite an existing broker bundle.
On 2026-08-15, the explicit App ID and Developer ID profile were created and the profile gate passed. A first provisioned broker proved that an independent same-user Security.framework reader receives errSecMissingEntitlement and /usr/bin/security cannot find the Data Protection item. The same run also found that metadata lookup returns errSecInteractionNotAllowed, deletion is not automatically user-authorized, and an authorization UI request can remain pending. The broker therefore now treats that metadata status as configured and performs explicit device-owner authentication before every get, set, and unset.
The revised provisioned Broker completed authenticated set/get/unset, cancellation, independent-reader, copied-binary, native/Intel, quarantined launch, and provider disposal checks. Apple accepted and stapled notarization submission 8941cae5-75a5-4f1c-bdfb-998d1ce578c3; Gatekeeper reports source=Notarized Developer ID. The signing wrapper retains a hard timeout and never substitutes --timestamp=none.
Acceptance suite
All secret values are disposable fixtures and must be deleted after each test.
- A normal DSH request succeeds only after the user authorizes the broker.
- Denying or cancelling authorization returns a typed error and never falls back.
- A direct invocation of the broker cannot read without fresh authorization.
/usr/bin/security, an independently compiledSecItemCopyMatchingreader, and a copied broker fail without interactive authorization.- An isolated DSH
0.1.0-rc.6consumer installs the plugin, replaces the credentials row, boots Web with HTTP 200, and its bash-tool attack fails closed without returning a value. The wrapper exposed no native exit code, so stronger Keychain error evidence comes from the independent Security.framework and/usr/bin/securityattacks above. - The broker never receives the host environment and never places a secret in argv, logs, config, crash output, or a filesystem path.
- Concurrent set/get/unset operations serialize correctly; cancellation, timeout, crash, locked-Keychain, corrupt-response, and disposal paths do not emit false update events or terminate the Host.
- Apple Silicon and Intel slices pass on macOS 13 or newer; Developer ID, Hardened Runtime, secure timestamp, provisioning profile, notarization, quarantine launch, manifest, and release archive hashes must match the final frozen candidate.
Apple references
- TN3125: Inside Code Signing โ Provisioning Profiles
- Sharing access to keychain items among a collection of apps
- Creating distribution-signed code for macOS
Migration
Migration from $DSH_HOME/.credentials.yaml is explicit and interactive: validate owner-only permissions, import one reference at a time, read each value back through the authorized broker, and produce a value-free receipt. Deleting or shredding the original document is a separate user-confirmed action and is never implicit.