Secrets + Keychain Audit Matrix
June 29, 2026 ยท View on GitHub
Last updated: 2026-02-25
This document captures the current, tested contract for Lemon secret resolution across keychain, encrypted store, and environment fallback paths.
Flow Matrix
| Layer | Write Path | Read/Resolve Path | Fallbacks | Primary Files |
|---|---|---|---|---|
| macOS Keychain master key | LemonCore.Secrets.MasterKey.init/1 -> Keychain.put_master_key/2 | LemonCore.Secrets.MasterKey.resolve/1 -> Keychain.get_master_key/1 | On :missing / :keychain_unavailable / command failures, tries env master key, then local file | apps/lemon_core/lib/lemon_core/secrets/keychain.ex, apps/lemon_core/lib/lemon_core/secrets/master_key.ex |
| Master key env fallback | Manual set of LEMON_SECRETS_MASTER_KEY (external) | MasterKey.resolve/1 via resolve_from_env/1 | On missing env, tries ~/.lemon/secrets_master_key; malformed env still fails as :invalid_master_key | apps/lemon_core/lib/lemon_core/secrets/master_key.ex |
| Local master key file fallback | ~/.lemon/secrets_master_key | MasterKey.resolve/1 via local file fallback | Returns :missing_master_key or :invalid_master_key when no valid source exists | apps/lemon_core/lib/lemon_core/secrets/master_key.ex |
| Encrypted secret store | LemonCore.Secrets.set/3 (AES-256-GCM at rest) | LemonCore.Secrets.get/2, resolve/2, exists?/2 | resolve/2 can fallback to env by same secret name (env_fallback: true) | apps/lemon_core/lib/lemon_core/secrets.ex |
| Coding Agent provider secret refs | Configured api_key_secret names in provider config | CodingAgent.Session.resolve_secret_api_key/1 -> LemonCore.Secrets.resolve/2 | Store first, env fallback enabled | apps/coding_agent/lib/coding_agent/session.ex |
Keychain Error Semantics (Current Contract)
LemonCore.Secrets.Keychain maps command outcomes to stable errors:
- Exit code
44->{:error, :missing} - Non-zero exit codes ->
{:error, {:command_failed, code, stderr_or_output}} - Timeout (
Task.yieldexpiry) ->{:error, :timeout} - Non-macOS / missing
securityexecutable ->{:error, :unavailable} - Empty retrieved value -> treated as missing (
{:error, :missing})
Master Key Resolution Precedence
LemonCore.Secrets.MasterKey.resolve/1 order:
- Keychain (
:keychainsource) LEMON_SECRETS_MASTER_KEY(:envsource)~/.lemon/secrets_master_key(:filesource)- Error (
:missing_master_key,:invalid_master_key, or{:keychain_failed, reason})
Additional nuance:
- If keychain returns malformed key material, env fallback is attempted first; only then returns
:invalid_master_key. status/1suppresses expected keychain absence (:missing,:keychain_unavailable) fromkeychain_errorwhile still surfacing hard failures.- The local source launcher
bin/lemonalso normalizesLEMON_SECRETS_MASTER_KEYfrom~/.lemon/secrets_master_keyon non-macOS systems so stale desktop/session env does not override the working local key by accident.
Operator Notes
mix lemon.secrets.initis the preferred bootstrap path on macOS (stores generated key in keychain).- For local non-macOS development, keep
~/.lemon/secrets_master_keyas the canonical master key file.bin/lemonwill export that value intoLEMON_SECRETS_MASTER_KEYbefore boot when the file exists. secrets.listandsecrets.statusreturn metadata only (never plaintext secret values).- If keychain prompts are denied (
User interaction is not allowed), Lemon can still operate via env fallback when configured.
Validation References
apps/lemon_core/test/lemon_core/secrets/keychain_test.exsapps/lemon_core/test/lemon_core/secrets/master_key_test.exsapps/lemon_core/test/lemon_core/secrets_test.exs