You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Retire fix-macos.md; fold findings into THREAT_MODEL (#67)
fix-macos.md's implementation plan is complete:
- Steps 1-6 (Path 2: AES-GCM-wrapped .handle + keychain-held wrapping key)
are shipped in PR #65.
- Steps 7-8 (Path 1: entitled SecKeyCreateRandomKey with
kSecAttrTokenIDSecureEnclave) are blocked on a provisioning profile, not
deferred work. The required keychain-access-groups entitlement is
AMFI-restricted and unavailable to Homebrew / cargo-install distribution,
so these steps will not land under the current distribution model.
The still-useful content from the plan doc has been migrated into
THREAT_MODEL's macOS platform-specific notes:
- Full prompt-behavior matrix (ad-hoc vs self-signed vs trusted-cert,
first run / rebuild / different path)
- Deny / Always Allow / upgrade-transition behavior
- -34018 finding explaining why the legacy keychain is used
- Explicit note that the entitled SE path is blocked on provisioning
fix-macos.md is deleted.
Co-authored-by: Jay Gowdy <jay@gowdy.me>
Copy file name to clipboardExpand all lines: THREAT_MODEL.md
+19-1Lines changed: 19 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -162,7 +162,25 @@ The cache header (version, magic, timestamps, risk level) in `crates/enclaveapp-
162
162
163
163
## Platform-specific notes
164
164
165
-
**macOS Keychain prompts:** On unsigned builds, the Keychain prompts once per binary hash change (e.g., after `brew upgrade`). Signed builds avoid this. This is the Keychain enforcing its ACL, not a vulnerability.
165
+
**macOS Keychain prompts.** The Keychain scopes access to `kSecClassGenericPassword` items by the calling binary's code-signing identity. The prompt behavior is load-bearing for the wrapping-key threat model (items above): it is exactly what blocks a different binary from reading the wrapping key.
166
+
167
+
Observed behavior by signing scenario:
168
+
169
+
| Scenario | First run | Rebuild at same path | Different path |
| Trusted signing identity (Apple Development / Developer ID) | no prompt | no prompt (identity unchanged) |**prompt**|
174
+
175
+
Additional behavior:
176
+
- "Deny" is not permanent: operations fail with `errSecUserCanceled` (`-128`), but the next invocation prompts again. The user is never locked out.
177
+
- "Always Allow" persists until the binary is replaced. After `brew upgrade`, one new prompt appears on first use of the upgraded binary.
178
+
- Ad-hoc → trusted-cert transition: one prompt on the first signed run; after "Always Allow," subsequent runs with the same identity are silent.
179
+
- Trusting a self-signed cert requires a `security add-trusted-cert` system password dialog and cannot be automated silently in a Homebrew formula.
180
+
181
+
**Data Protection Keychain not used.**`kSecUseDataProtectionKeychain: true` fails with `errSecMissingEntitlement` (`-34018`) on unsigned / ad-hoc-signed builds. The implementation uses the legacy (file-based) login keychain with `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`.
182
+
183
+
**Entitled Secure Enclave path not enabled.** Storing SE keys directly as Keychain items via `SecKeyCreateRandomKey` + `kSecAttrTokenIDSecureEnclave` + `kSecAttrIsPermanent: true` would remove the `.handle` file entirely. It requires `keychain-access-groups`, which is an AMFI-restricted entitlement that needs a provisioning profile — even a valid Apple Development cert without a matching profile causes AMFI to kill the binary with error `-413`. This path is only viable for App Store / Enterprise / Xcode-provisioned distribution and is not available for Homebrew or `cargo install` builds. The current Path-2 implementation (AES-GCM-wrapped `.handle` + Keychain-held wrapping key) is the hardening target for unsigned distribution.
166
184
167
185
**WSL bridge:** Communicates over stdin/stdout of a child process. The client (`crates/enclaveapp-bridge/src/client.rs`) discovers the bridge only from a fixed-path list under `/mnt/c/Program Files/<app>/` and `/mnt/c/ProgramData/<app>/`. Replacing the binary at those paths requires Windows admin rights. The previous `which`-based PATH fallback was removed — a user-writable `$PATH` entry on the WSL side could otherwise substitute a malicious bridge binary. Request/response size is capped at 64 KB, the child is reaped via `BridgeSession::Drop`, and reads are bounded. There is still no Authenticode / `WinVerifyTrust` check on the resolved bridge binary; adding one is a tracked hardening gap for environments where the Windows host itself is semi-trusted.
0 commit comments