Skip to content

fix(secrets): support macOS Keychain across worktrees and SSH#152

Open
lmwnshn wants to merge 1 commit into
exoharness:mainfrom
lmwnshn:codex/keychain-ssh
Open

fix(secrets): support macOS Keychain across worktrees and SSH#152
lmwnshn wants to merge 1 commit into
exoharness:mainfrom
lmwnshn:codex/keychain-ssh

Conversation

@lmwnshn

@lmwnshn lmwnshn commented Jul 19, 2026

Copy link
Copy Markdown

Fixes keychain issues across the environments I work in (ssh and codex desktop's worktrees).

Problems:

  1. setup.sh: in a worktree, .git is a file and not a folder, so detecting a git checkout should use -e instead of -d.
  2. Having fixed that, setup.sh would encounter keychain/codesign issues in trying to store the API key. The two main issues were (1) worktrees colliding over the keychain account root.to_string_lossy().to_string() -> .exo/exoharness, and (2) when run over ssh, the GUI keychain prompt doesn't pop up.
==> Store secrets and register model
Storing secret openai from $OPENAI_API_KEY...
Error: Platform secure storage failure: User interaction is not allowed.

Caused by:
    User interaction is not allowed.

Fixed by:

  1. Check .git with -e instead of -d.
  2. Namespace the keychain account by assigning each secret store a UUID, now keychain identity survives relocation. This PR also migrates existing path-based keys. If you're ok dropping backwards compatibility, this part can be cleaned up further.
  3. Unlock the keychain when necessary (e.g., over ssh). Ad-hoc sign the exo and scheduler binaries and add their CDHashes to partition ACL, basically it lets them read the keychain master key to then decrypt the stored API keys.

AI use: PR contents mostly generated with 5.6 Sol, though I've been actively getting it to rewrite some of its stupider decisions. I'm not particularly fond of macOS permissions, feel free to provide feedback/make edits.

@akrentsel akrentsel left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems reasonable overall to me, thanks for catching this class of problem

serde_json::from_slice(&plaintext).map_err(Into::into)
}

pub(crate) fn verify_key_access(&self) -> Result<()> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this probe silently provisions a key if it doesn't exist – is that intentional? seems like a side-effect that could have unexpected consequences?

perhaps there should be a key_provider.try_get_key that is read only?

Comment thread setup.sh
is_exo_checkout() {
local dir="$1"
[[ -d "$dir/.git" && -f "$dir/exo.sh" ]]
# In linked Git worktrees, .git is a file rather than a directory.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this fix – I wan't working with worktrees in my workflow so didn't run into this.

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