Tools like shipkey rely on 1Password or Bitwarden CLI as secret backends. Coffer provides a self-hosted local alternative with a compatible CLI interface, removing the dependency on third-party services.
coffer-core (library) coffer-cli (binary)
┌─────────────────────┐ ┌──────────────────┐
│ Coffer API │◄────│ clap parsing │
│ ├── crypto.rs │ │ rpassword prompt │
│ ├── store.rs │ └──────────────────┘
│ └── error.rs │
└─────────────────────┘
▲
│ (future)
macOS GUI App
via uniffi / C FFI
coffer-core is a pure library with no terminal I/O, no stdin/stdout assumptions. This separation exists so that:
- The CLI is just one thin consumer of the library
- A future macOS SwiftUI app can link
coffer-coredirectly via uniffi orcbindgenC FFI - macOS Keychain / Touch ID integration can be layered on top without modifying core logic
All data lives in a single SQLite file for easy backup and migration:
~/.coffer/
└── store.db
secrets table — one row per secret:
| Column | Type | Notes |
|---|---|---|
| vault, provider, project, env, field | TEXT | Composite unique key, stored in plaintext for queryability |
| encrypted_value | BLOB | AES-256-GCM ciphertext |
| nonce | BLOB | 12-byte random nonce (unique per write) |
| created_at, updated_at | TEXT | RFC 3339 timestamps |
meta table — key-value store for:
| Key | Value |
|---|---|
password_salt |
32-byte random salt for Argon2id |
password_verify |
Encrypted known-plaintext token |
password_verify_nonce |
Nonce for the verify token |
version |
Schema version (1) |
User Password ──[Argon2id(default params) + 32-byte salt]──> 256-bit Master Key
- Argon2id is the winner of the Password Hashing Competition, resistant to both side-channel and GPU attacks
- Salt is generated once at
inittime via OS CSPRNG and stored in themetatable - The master password is never stored; only the derived key exists transiently in memory
Secret Value ──[AES-256-GCM + 12-byte random nonce]──> (ciphertext, nonce)
- AES-256-GCM provides authenticated encryption (confidentiality + integrity + authenticity)
- A fresh random nonce is generated for every write, including updates to existing secrets
- Tampering with ciphertext is detected at decryption time (GCM authentication tag verification)
On init, the string "coffer-password-verify" is encrypted with the master key. On subsequent open calls, this token is decrypted — if decryption succeeds and the plaintext matches, the password is correct. This avoids storing any password hash that could be attacked offline independently of the encrypted data.
Metadata fields (vault, provider, project, env, field) are stored in plaintext. This is a deliberate trade-off:
- Enables SQL
WHEREfiltering without decrypting every row - Secret names are typically not sensitive (e.g.,
GITHUB_TOKEN); the values are - If metadata confidentiality is needed in the future, the schema can be extended without breaking existing stores
The CLI interface is designed to be compatible with shipkey's SecretBackend interface:
| CLI Command | SecretBackend Method |
|---|---|
coffer status |
checkStatus() / isAvailable() |
coffer get --vault ... --field ... |
read(ref) |
coffer set --vault ... --field ... --value ... |
write(entry) |
coffer list [--project ...] [--env ...] |
list(project?, env?) |
coffer delete --vault ... --field ... |
(extension) |
Output conventions:
getprints the raw secret value to stdout (no trailing newline), suitable for$(coffer get ...)listandstatusoutput JSON to stdout- Human-readable messages (
Secret saved,Secret deleted) go to stderr
set uses an update-first approach: attempt UPDATE by the 5-field composite key, and if zero rows were affected, fall back to INSERT. This avoids SQLite's ON CONFLICT upsert syntax while keeping the logic simple and the UNIQUE constraint enforced.
- shipkey backend integration — a
coffer.tsbackend in shipkey that shells out to thecofferbinary - macOS GUI App — SwiftUI app linking
coffer-corevia uniffi, with Keychain-stored master password and Touch ID unlock - Password change — re-encrypt all secrets with a new master key derived from a new password
- Export/import — plaintext JSON export (encrypted at rest, decrypted for transfer) for migration between machines