Credential Storage
Wayland Core stores credentials through a CredentialsStore trait backed by one of three implementations. The backend is selected in config.toml; the default is plaintext with 0o600 permissions enforced on write. For most interactive installs the keyring backend is the better choice. The encrypted-file backend is for headless or server deployments where the OS keychain is unavailable.
All three backends use flat string keys with dotted prefixes for namespacing, for example providers.anthropic.api_key or bedrock.secret_access_key.
Backend selection
Section titled “Backend selection”Set the backend in ~/.wayland-core/config.toml (or your project-local .wayland-core.toml):
[storage.credentials]backend = "keyring" # or "plaintext" or "encrypted_file"service_name = "wayland-core" # optional; scopes keyring entries (default: "wayland-core")For the encrypted-file backend, two file paths must also be specified:
[storage.credentials]backend = { encrypted_file = { cipher_path = "~/.wayland/credentials.enc", key_params_path = "~/.wayland/credentials.kdf.json" } }Set WAYLAND_VAULT=plaintext before startup to skip any auto-migration prompt and keep the plaintext backend regardless of what the config file says.
Plaintext backend (default)
Section titled “Plaintext backend (default)”PlaintextCredentialsStore stores a [secrets] table in a TOML file. On every write it enforces 0o600 permissions on Unix and attempts a deny-all ACL on Windows via atomic_write - a sibling tempfile is written and then renamed so a crash or interruption cannot leave a half-written file. Reads warn via tracing if the file is world-readable but still load, to avoid stranding users on a freshly created file that the kernel briefly held at the umask default.
Default path: wherever wayland-core --config-path reports on your OS (usually ~/.config/wayland-core/config.toml on Linux, ~/Library/Application Support/wayland-core/config.toml on macOS).
This backend is appropriate for development machines where the OS keychain is inconvenient. It offers no encryption at rest; anyone with read access to the file can read the secrets.
Keyring backend
Section titled “Keyring backend”KeyringCredentialsStore uses the keyring crate to store secrets in the platform’s native credential store:
- macOS: Keychain
- Windows: Credential Manager
- Linux: Secret Service (e.g. GNOME Keyring, KWallet)
Each key is stored as a (service, user) pair where service is the configured service_name (default: "wayland-core") and user is the key string. Lookup is O(1).
To switch to the keyring backend:
[storage.credentials]backend = "keyring"Then set your API key once:
wayland-core setup # interactive onboarding stores keys in the selected backendOr set it directly via the environment - the engine’s credential lookup order is environment variable first, then the credential store, then the config file. Keys read from the environment are never written to the store.
Encrypted-file backend
Section titled “Encrypted-file backend”EncryptedFileCredentialsStore is for server or CI deployments where no OS keychain is available. It uses:
- KDF: Argon2id (v0x13), parameters m=65536 KiB (64 MiB), t=3, p=1, output 32 bytes
- AEAD: XChaCha20-Poly1305, 24-byte nonce, 16-byte Poly1305 MAC tag
- Blob layout:
nonce(24) || ciphertext || tag(16)in the cipher file
The KDF parameters are confirmed in crates/wcore-config/src/credentials.rs:
const DEFAULT_M_COST_KIB: u32 = 64 * 1024; // 64 MiBconst DEFAULT_T_COST: u32 = 3;const DEFAULT_P_COST: u32 = 1;pub const NONCE_LEN: usize = 24;pub const TAG_LEN: usize = 16;pub const KEY_LEN: usize = 32;Two-file layout
Section titled “Two-file layout”The backend writes two files:
| File | Contents | Secret? |
|---|---|---|
cipher_path (e.g. ~/.wayland/credentials.enc) | Raw bytes: nonce(24) || ciphertext || tag(16) | Yes - protect with 0o600 |
key_params_path (e.g. ~/.wayland/credentials.kdf.json) | JSON: { salt_b64, m_cost, t_cost, p_cost, version } | No - contains only public KDF tuning params |
The salt is 16 random bytes generated from rand::thread_rng() (seeded from the OS) on vault creation and stored in key_params_path. Storing the KDF parameters separately means you can re-derive the key from your passphrase at any time without knowing the original tuning values. The AEAD nonce is re-randomized on every put() write.
Both files are written with atomic_write (tempfile + rename) and 0o600 permissions are enforced immediately after.
Passphrase resolution
Section titled “Passphrase resolution”The engine resolves the vault passphrase in this order:
WAYLAND_VAULT_PASSPHRASE_FD: a Unix file descriptor number (e.g.3). The engine validates the fd is open and readable before consuming it. This path does not appear in/proc/<pid>/environ.WAYLAND_VAULT_PASSPHRASE: env var (legacy). Emits atracing::warn!about the/proc/<pid>/environvisibility risk on Linux.- Interactive prompt:
rpassword::prompt_password("vault passphrase: ")on a TTY.
The passphrase is held in a zeroize::Zeroizing<String> in memory. Argon2id derivation runs once per process; subsequent get()/put() calls reuse the cached derived key under a parking_lot::Mutex. Cross-process write concurrency is not modeled - if you run multiple writers against the same vault files, serialize at the application layer.
Unlock behavior
Section titled “Unlock behavior”On first access the engine:
- Reads the passphrase via the resolution order above.
- Loads
key_params_pathif it exists; otherwise usesKdfParams::default()(fresh random salt). - If
cipher_pathalready exists, decrypts it immediately to verify the passphrase is correct. A wrong passphrase fails here rather than silently rotating the vault key on the next write.
Headless deployment example
Section titled “Headless deployment example”Pass the passphrase via a file descriptor to avoid environment variable exposure:
# Open a pipe, write the passphrase to fd 3, run wayland-coreexec 3< <(echo -n "my-vault-passphrase")WAYLAND_VAULT_PASSPHRASE_FD=3 wayland-core "run my task"exec 3<&-For CI/CD, inject the passphrase via a secret manager into the fd at process start rather than an environment variable.
Credential key namespace
Section titled “Credential key namespace”Keys follow a dotted convention. Some examples used by the engine:
| Key | What it holds |
|---|---|
providers.anthropic.api_key | Anthropic API key |
providers.openai.api_key | OpenAI API key |
bedrock.access_key_id | AWS access key ID for Bedrock |
bedrock.secret_access_key | AWS secret access key for Bedrock |
providers.<name>.api_key | Any custom provider’s key |
The wayland-core setup interactive onboarding stores keys under these namespaced paths. Channel adapter keys (Slack, Discord, etc.) use channels.<name>.<field> prefixes managed by their respective crates.
Choosing a backend
Section titled “Choosing a backend”| Scenario | Recommended backend |
|---|---|
| Developer workstation, interactive use | keyring |
| Developer workstation, no keychain daemon | plaintext (ensure 0o600; consider full-disk encryption) |
| Server or CI, no GUI/keychain | encrypted_file with passphrase via fd |
| Testing or short-lived container | plaintext or environment variables only |
Related pages
Section titled “Related pages”- Security Model Overview - sandbox, egress gate, threat model
- Configuration - the
[storage]section and config file location - Providers and Auth - how API keys are passed to provider clients