# Agent isolation design 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 1. Ship `KeyringSeamBroker.app` as an app-like bundle, signed with Developer ID Application and Hardened Runtime. 2. Register the explicit bundle identifier `org.fieldnote.keyringseam.broker` and embed a matching Developer ID provisioning profile that authorizes its restricted Keychain entitlement. 3. Target the Data Protection Keychain with `kSecUseDataProtectionKeychain: true`. Store each item in the broker's private access group with `kSecAttrAccessibleWhenUnlockedThisDeviceOnly` and a `SecAccessControl` user-presence constraint. 4. Authenticate through `LAContext` before any secret-bearing response. Cancellation, an unavailable biometric/passcode policy, a locked Keychain, missing entitlements, or an invalid signature fails closed. 5. 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. 6. 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. 7. Keep inherited process credentials read-only and highest priority. Resolve managed values from the broker, then fall back to project and user `.env` layers 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 compiled `SecItemCopyMatching` reader, and a copied broker fail without interactive authorization. - An isolated DSH `0.1.0-rc.6` consumer 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/security` attacks 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](https://developer.apple.com/documentation/technotes/tn3125-inside-code-signing-provisioning-profiles) - [Sharing access to keychain items among a collection of apps](https://developer.apple.com/documentation/security/sharing-access-to-keychain-items-among-a-collection-of-apps) - [Creating distribution-signed code for macOS](https://developer.apple.com/documentation/xcode/creating-distribution-signed-code-for-the-mac) ## 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.