Two methods
| Method | Best for |
|---|---|
| User-provided secrets (Keychain) | Most apps — the user enters secrets at first open; stored in macOS Keychain |
| Encrypted secrets in package | Distributing apps with pre-configured secrets bundled inside |
Method A: User-provided secrets (recommended default)
The manifest declares which secrets the app needs:- On first open, the host app detects missing required secrets
- The user is prompted to enter each secret value
- Secrets are stored in the macOS Keychain, scoped to the project instance
- At service start, secrets are injected as environment variables
- Secrets never exist inside the package
- Secrets persist across app restarts
- Secrets are scoped per project instance — not shared between instances of the same app
- Deleting a project instance removes its Keychain entries
- Required secrets that are missing prevent the app from starting
Method B: Encrypted secrets in package
For distribution models where developers ship pre-configured secrets. The package contains an encrypted blob (secrets.encrypted) with the secret values. On open, the user is prompted for a decryption password. The decrypted secrets are held in memory only — never written back to disk.
Brute-force protection:
- Exponential backoff after failed decryption attempts
- 5-minute temporary lock after 5 consecutive failures
- Attempt counter resets on success
Runtime injection
Regardless of storage method, secrets reach services as environment variables. The secret name becomes the environment variable name:OPENAI_API_KEY=<value> is set in all service environments at start time.
Safety guarantees
- Secrets never appear in logs (the log collector redacts known secret names)
- Secrets never appear in snapshots (excluded from state copy)
- Secrets are not stored in the immutable package in plaintext
- Keychain-backed secrets survive app restarts
- Secrets are isolated per project instance and never leak between apps
