Skip to content

apple: prefer implicit default keychain; explicit path as fallback#70

Merged
jgowdy-godaddy merged 1 commit intomainfrom
fix/macos-keychain-default-first
Apr 17, 2026
Merged

apple: prefer implicit default keychain; explicit path as fallback#70
jgowdy-godaddy merged 1 commit intomainfrom
fix/macos-keychain-default-first

Conversation

@jgowdy-godaddy
Copy link
Copy Markdown
Contributor

Summary

Follow-up to #68 and #69. Root-causes the npmenc macOS CI hang and restores pre-#68 CI behaviour while keeping the `$HOME`-override dialog fix.

  • Threat-model hardening pass: envelope, meta HMAC, bridge, process, adapter #68 made the Swift bridge unconditionally open the login keychain by absolute path and pin every `SecItem*` op to it via `kSecUseKeychain` / `kSecMatchSearchList`. That fixed a "Keychain Not Found" modal on local dev tests that override `$HOME`.
  • But pinning ops to the legacy login keychain made headless CI runners hang: `SecItemAdd` then blocks on a same-binary ACL confirmation prompt that the runner can never answer. apple: silent empty-password unlock of login keychain on open #69's empty-password unlock was not sufficient — the hang isn't lock state, it's the ACL dialog.
  • New behaviour: check `SecKeychainCopyDefault` first. When a default is reachable (normal interactive sessions and GitHub Actions runners), let Security.framework route implicitly — that reaches the Data Protection keychain on unsigned builds and never fires the legacy ACL dialog. Only when no default is reachable do we fall back to explicit-path open, preserving Threat-model hardening pass: envelope, meta HMAC, bridge, process, adapter #68's dialog-avoidance fix for `$HOME`-override tests and launchd sandboxes.

Test plan

  • `cargo clippy -p enclaveapp-apple --features signing,encryption --all-targets -- -D warnings`
  • `cargo test -p enclaveapp-apple --features signing,encryption --lib` — 44 pass (including real-keychain `keychain_wrap::tests`)
  • `cargo test --test integration -p npmenc -- --test-threads=1` — 30 pass locally in 2.78s
  • npmenc Skip co-author trailers in CLAUDE.md #18 macOS CI green after merge (was hanging 30+ min, expected < 2 min)

PR #68 made the Swift bridge unconditionally open the login
keychain by absolute path and pin every SecItem* op to it via
kSecUseKeychain / kSecMatchSearchList. That fixed the
"Keychain Not Found" modal dialog on local dev runs that
override $HOME in tests, but it made GitHub Actions `macos-latest`
jobs hang: SecItemAdd against an explicitly-pinned legacy
keychain blocks on a same-binary ACL confirmation prompt the
headless runner cannot answer.

Fix: check SecKeychainCopyDefault first. When a default keychain
is reachable (normal interactive sessions and CI runners), fall
through to Security.framework's implicit routing, which reaches
the Data Protection keychain on unsigned builds and does not
trigger the legacy ACL prompt — restoring the pre-#68 CI
behaviour. Only when no default is reachable ($HOME-overridden
tests, launchd sandboxes) do we open the login keychain by
explicit path and constrain ops to it — preserving #68's
dialog-avoidance fix for those contexts.
@jgowdy-godaddy jgowdy-godaddy merged commit 9bff626 into main Apr 17, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants