secrets
= secrets :toc: left :toclevels: 2 :source-highlighter: rouge
Secrets management for the Aloli Crystal ecosystem: a TOML vault encrypted with age, a master key stored in the macOS Keychain (synced across Macs through iCloud), a Diceware-protected paper recovery code as last-resort.
French version: link:README.fr.adoc[README.fr.adoc].
== Status: v0.2 (single-user macOS, comment-preserving vault)
This shard is the foundation of the Aloli secret-management story described in prod-crystal/CRYSTAL-SECRETS-SPECS.adoc. The current release covers the operator's everyday loop on a Mac:
initgenerates anagekeypair, proposes a Diceware passphrase viaaloli-crystal/diceware, prints the public key + a paper recovery code.vault create / get / set / listfor everyday secret access, built onaloli-crystal/toml'sTOML::Document— comments and formatting survive programmatic edits.master-key export / importfor the paper recovery flow.
Out of scope (planned for later):
- Multi-recipient vaults (team sharing). Single recipient only for now (the operator's own key).
- SSH agent mode (the
ssh-keys.agevault + the in-memory agent serving the SSH protocol). - Other OS backends (Linux Secret Service, Windows Credential Manager, FreeBSD file+passphrase). macOS Keychain only.
- Rotation, delete, edit, diff/log subcommands.
== Architecture
[source]
┌──────────────────────────────────────┐
│ secrets │
│ │
│ CLI │
│ │ │
│ ▼ │
│ MasterKey ─── KeychainMacOS ◄───────┼─── /usr/bin/security
│ │ (shell-out) │
│ ▼ │
│ Vault ──────── age ◄────────────────┼─── /usr/local/bin/age
│ │ (shell-out) │
│ ▼ │
│ TOML::Document ◄─── aloli-crystal/ │
│ (rich AST, toml │
│ comments preserved) │
│ │
│ Diceware ◄─── aloli-crystal/ │
│ diceware │
│ │
│ Recovery ──── openssl enc ◄─────────┼─── /usr/bin/openssl
│ (passphrase) │
│ │
└──────────────────────────────────────┘
== Installation
[source,shell]
brew install age # required runtime dependency shards install crystal build src/cli.cr -o secrets --release sudo install -m 0755 secrets /usr/local/bin/
== Quickstart
[source,shell]
$ secrets init Generated Diceware passphrase (7 words):
perplex grouchy abdomen catacomb mournful prancing slingshot
Accept this passphrase? [O/r/n] o
══════════════════════════════════════════════════════════ PUBLIC KEY (add this to your team recipients.txt) :
age1abc123...
══════════════════════════════════════════════════════════ PAPER RECOVERY CODE — print this and store in a physical safe. NEVER photograph, NEVER scan. ══════════════════════════════════════════════════════════
Passphrase : perplex grouchy abdomen catacomb mournful prancing slingshot
-----BEGIN CRYSTAL-SECRETS RECOVERY----- U2FsdGVkX1+abc... -----END CRYSTAL-SECRETS RECOVERY-----
$ secrets vault create -n prod vault created: ~/.config/secrets/vaults/prod.toml.age
$ secrets set -n prod -k DATABASE_URL Value (hidden): ●●●●●●●●●●●● set DATABASE_URL in prod
$ secrets get prod DATABASE_URL postgres://aloli:secret@db/prod
$ secrets list prod DATABASE_URL
== Recovery (after losing the Mac)
[source,shell]
On the new Mac, after iCloud restore is NOT available:
$ secrets master-key import Paste the recovery paper, then Ctrl-D : -----BEGIN CRYSTAL-SECRETS RECOVERY----- U2FsdGVkX1+abc... -----END CRYSTAL-SECRETS RECOVERY----- ^D Passphrase (hidden): ●●●●●●●●●●● master key restored. Public key: age1abc123...
In practice, iCloud Keychain does this automatically — the master key follows you to the new Mac without any retyping. The paper is the last-resort filet de sécurité, used only if iCloud itself is unavailable.
== Key handling discipline
- The master key is never passed via command-line arguments (would leak through
ps). - Secret values are never passed via argv either;
setreads the value from stdin (hidden when STDIN is a terminal). - The age private key is fed to
age(1)via a temp file with mode0600, deleted as soon asagereturns. - On disk, the vault is
<env>.toml.age(TOML payload encrypted with age). The master key never touches the disk except in the Keychain blob (managed by macOS). - Recovery paper = openssl-encrypted blob; the passphrase is the only protection of the paper. Store both in the same physical safe to avoid splitting the recovery into two failures.
== Development
[source,shell]
shards install crystal spec crystal tool format --check bin/ameba
19 tests cover Keychain shell-out (no key in argv, 8 cases), Vault round-trip (real age, 6 cases), Recovery export/import (5 cases). Diceware and TOML behavior is exercised by their own test suites in the upstream shards.
== License
MIT — see link:LICENSE[LICENSE].
== References
- https://age-encryption.org/v1[age v1 specification]
- https://github.com/aloli-crystal/toml[toml] — TOML v1.0 parser/serializer with comment preservation
- https://github.com/aloli-crystal/diceware[diceware] — Diceware passphrase generator
- https://www.eff.org/dice[EFF Diceware Wordlist]
- https://github.com/mbelivo/diceware-wordlists-fr[mbelivo French wordlist]
prod-crystal/CRYSTAL-SECRETS-SPECS.adoc— parent spec
secrets
- 0
- 0
- 0
- 0
- 3
- about 3 hours ago
- April 28, 2026
MIT License
Wed, 29 Apr 2026 10:16:20 GMT