Secret Generation
Secrets can be declared with type and generate to be auto-generated when missing. This is useful for passwords, tokens, and keys that do not need to be shared across developers.
Basic Usage
Section titled “Basic Usage”[profiles.default]DB_PASSWORD = { description = "Database password", type = "password", generate = true }API_TOKEN = { description = "API token", type = "hex", generate = { bytes = 32 } }SESSION_KEY = { description = "Session key", type = "base64", generate = { bytes = 64 } }REQUEST_ID = { description = "Request ID prefix", type = "uuid", generate = true }Generation Types
Section titled “Generation Types”| Type | Default Output | Options |
|---|---|---|
password | 32 alphanumeric chars | length (int), charset ("alphanumeric" or "ascii") |
passphrase (0.21+) | Seven BIP-39 English words joined with - | words (6–32), separator (non-empty string) |
mnemonic (0.21+) | 24-word English BIP-39 mnemonic | algorithm ("bip39"), words (12, 15, 18, 21, or 24), language ("english") |
hex | 64 hex chars (32 bytes) | bytes (int) |
base64 | 44 chars (32 bytes) | bytes (int) |
uuid | UUID v4 (36 chars) | none |
command | stdout of command | command (string, required) |
rsa_private_key | 2048-bit RSA private key (PKCS1 PEM) | bits (int) |
openpgp_private_key (0.21+) | ASCII-armored OpenPGP transferable secret key | user_id (required), algorithm ("ed25519" or "rsa"), bits (RSA only), capabilities (["sign"], ["encrypt"], or both) |
ssh_private_key (0.21+) | Unencrypted OpenSSH Ed25519 private key | algorithm ("ed25519" or "rsa"), bits (RSA only), comment (string) |
wireguard_private_key (0.21+) | Base64-encoded WireGuard private key | none |
jwk_private_key (0.21+) | Ed25519 private signing JWK | algorithm ("ed25519", "p256", or "rsa"), bits (RSA only), kid (string) |
age_identity (0.21+) | Native X25519 age identity | none |
x509_identity (0.21+) | PKCS#12 archive (Base64 at rest) containing a P-256 key and self-signed certificate | san (required), usages, valid_for (1–200 days); issuer = "self_signed", algorithm = "p256" |
Command type
Section titled “Command type”The command type runs a shell command and uses its stdout as the generated value:
MONGO_KEY = { description = "MongoDB keyfile", type = "command", generate = { command = "openssl rand -base64 765" } }command requires generate = { command = "..." } rather than just generate = true.
OpenPGP private keys
Section titled “OpenPGP private keys”openpgp_private_key generates a GnuPG-compatible OpenPGP v4 key entirely in
process; neither gpg nor another executable is required. Its modern default
uses an Ed25519 certification-only primary key and puts routine operations on
separate Ed25519 signing and Curve25519 encryption subkeys.
The User ID is required. With no capabilities, SecretSpec creates both
signing and encryption subkeys:
[profiles.default]GENERAL_KEY = { description = "Service OpenPGP key", type = "openpgp_private_key", generate = { user_id = "Service Bot <service@example.com>" } }
# A signing-only key has no encryption subkey.RELEASE_KEY = { description = "Release signing key", type = "openpgp_private_key", generate = { user_id = "Release Bot <releases@example.com>", capabilities = ["sign"] } }
# RSA is available for consumers that require it; 3072 bits is the default.LEGACY_KEY = { description = "Legacy-compatible OpenPGP key", type = "openpgp_private_key", generate = { user_id = "Legacy Bot <legacy@example.com>", algorithm = "rsa", bits = 4096 } }capabilities must be a non-empty list containing "sign", "encrypt", or
both without duplicates. algorithm defaults to "ed25519". Selecting
"rsa" uses RSA for the primary key and every requested subkey; bits
defaults to 3072 and accepts values from 2048 through 8192. bits is invalid
with "ed25519".
The result is one -----BEGIN PGP PRIVATE KEY BLOCK----- value that can be imported by GnuPG and other OpenPGP tools. It has
no OpenPGP passphrase and no expiration; protect it with an encrypted provider
and rotate it according to the consuming system’s policy. Set as_path = true
when a command needs a temporary key file rather than the armored value in an
environment variable.
SSH private keys
Section titled “SSH private keys”ssh_private_key generates an unencrypted OpenSSH private key entirely in
process. generate = true uses Ed25519, the sensible default for new SSH keys:
[profiles.default]DEPLOY_KEY = { description = "Deployment SSH key", type = "ssh_private_key", generate = true }
# RSA is available for compatibility; 3072 bits is the default.LEGACY_DEPLOY_KEY = { description = "Legacy deployment key", type = "ssh_private_key", generate = { algorithm = "rsa", bits = 4096, comment = "deploy@example.com" } }RSA sizes from 2048 through 8192 bits are accepted. bits is invalid with
Ed25519. comment is optional and cannot contain control characters. Generated
keys are not passphrase-encrypted, so store them in an encrypted provider. Set
as_path = true when a command needs the key in a temporary file rather than in
an environment variable.
Passphrases and infrastructure keys
Section titled “Passphrases and infrastructure keys”passphrase independently selects seven words from the 2,048-word BIP-39
English list by default, providing 77 bits of entropy. The output is a
human-readable passphrase, not a BIP-39 mnemonic: SecretSpec adds no checksum
and does not derive a wallet seed. Set words from 6 through 32 and choose any
non-empty, control-character-free separator:
RECOVERY_CODE = { description = "Operator recovery code", type = "passphrase", generate = { words = 8, separator = "." } }mnemonic (0.21+) generates a checksum-valid BIP-39 recovery mnemonic. It
defaults to 24 English words, encoding 256 bits of OS-provided entropy plus the
BIP-39 checksum. Supported word counts are 12, 15, 18, 21, and 24;
algorithm = "bip39" and language = "english" are currently the only
supported subtype values:
WALLET_RECOVERY = { description = "Wallet recovery mnemonic", type = "mnemonic", generate = { algorithm = "bip39", words = 24, language = "english" } }The generated value is the mnemonic itself. SecretSpec does not derive a BIP-32 seed or wallet keys and does not add BIP-39’s optional mnemonic passphrase. Store it only in a provider appropriate for high-value recovery material.
The other 0.21+ infrastructure formats are generated entirely in process:
WIREGUARD_KEY = { description = "WireGuard peer private key", type = "wireguard_private_key", generate = true }JWT_KEY = { description = "JWT signing key", type = "jwk_private_key", generate = { algorithm = "p256", kid = "release-2026" } }AGE_KEY = { description = "Backup encryption identity", type = "age_identity", generate = true }TLS_IDENTITY = { description = "Development TLS identity", type = "x509_identity", generate = { san = ["dns:localhost", "ip:127.0.0.1"], valid_for = "30d" } }TLS_KEY = { description = "PKCS#8 private key", type = "pkcs8_private_key", from = "TLS_IDENTITY", as_path = true }wireguard_private_key produces the standard Base64-encoded, clamped 32-byte
private key accepted by WireGuard. age_identity produces a native X25519
AGE-SECRET-KEY-1... identity. Neither accepts generation options.
The X25519 age identity maximizes compatibility with the current Rust age
provider. The age reference implementation now recommends hybrid
ML-KEM-768+X25519 identities for data that needs post-quantum confidentiality,
but the Rust age library does not yet parse that identity format. Provision a
hybrid AGE-SECRET-KEY-PQ-1... identity externally when that threat model
applies.
jwk_private_key emits one compact private JWK including its public parameters,
use = "sig", and key_ops = ["sign"]. Ed25519 with RFC 9864’s fully
specified Ed25519 JOSE algorithm identifier is the default;
algorithm = "p256" selects P-256/ES256, while "rsa" selects
RSA/RS256, defaults to 3072 bits, and accepts 2048 through 8192. kid is
optional. Protect all three private-key values with an encrypted provider.
x509_identity (0.21+) generates a self-signed P-256 certificate whose SANs
must be explicit. The canonical value is an empty-password PKCS#12 archive,
stored as Base64 by default; the provider is its security boundary. Generation
uses ECDSA/SHA-256, a 30-day default lifetime, and a 200-day maximum. Declare
further secrets with from = "TLS_IDENTITY" and a typed conversion target to
obtain PKCS#8 PEM or DER private keys, certificate PEM or DER, ordered
certificate chains, or password protected PFX files. See the
configuration reference
for the complete example and validation rules.
How it works
Section titled “How it works”- Generation only triggers when a secret is missing. Existing secrets are never overwritten.
- Generated values are stored via the secret’s configured provider (or the default provider).
- Subsequent runs find the stored value and skip generation (idempotent).
- The
nullprovider (0.19+) instead returns a fresh generated value for only the current resolution. generateanddefaultcannot both be set on the same secret.- Setting
typewithoutgenerateis informational only and does not trigger auto-generation.
Ephemeral generation with null
Section titled “Ephemeral generation with null”Use providers = ["null"] when the value should be generated on demand and
never written to provider storage:
[profiles.default]SESSION_SECRET = { description = "Per-run session secret", type = "base64", generate = { bytes = 32 }, providers = ["null"] }One materializing resolution receives one value. The next run, get,
check, or SDK value-carrying resolution receives a new one. Value-free
reports describe the value as generated without minting it. Use a writable
provider instead when another process or later invocation must retrieve the
same value.
Example
Section titled “Example”[profiles.default]# Auto-generated on first run, reused after thatDB_PASSWORD = { description = "Database password", type = "password", generate = true }
# Custom length and character setADMIN_PASSWORD = { description = "Admin password", type = "password", generate = { length = 64, charset = "ascii" } }
# 64-byte key encoded as base64ENCRYPTION_KEY = { description = "Encryption key", type = "base64", generate = { bytes = 64 } }
# RSA private key (default 2048-bit)JWT_SIGNING_KEY = { description = "JWT signing key", type = "rsa_private_key", generate = true }
# RSA private key with custom key sizeTLS_KEY = { description = "TLS private key", type = "rsa_private_key", generate = { bits = 4096 } }
# OpenPGP signing key (requires SecretSpec 0.21+)RELEASE_KEY = { description = "Release signing key", type = "openpgp_private_key", generate = { user_id = "Release Bot <releases@example.com>", capabilities = ["sign"] } }
# OpenSSH Ed25519 private key (requires SecretSpec 0.21+)DEPLOY_KEY = { description = "Deployment SSH key", type = "ssh_private_key", generate = true }
# Human-readable passphrase (requires SecretSpec 0.21+)RECOVERY_CODE = { description = "Recovery code", type = "passphrase", generate = true }
# Checksum-protected BIP-39 mnemonic (requires SecretSpec 0.21+)WALLET_RECOVERY = { description = "Wallet recovery mnemonic", type = "mnemonic", generate = true }
# Private P-256 JWK (requires SecretSpec 0.21+)JWT_JWK = { description = "JWT signing JWK", type = "jwk_private_key", generate = { algorithm = "p256" } }
# Self-signed P-256 X.509 identity (requires SecretSpec 0.21+)TLS_IDENTITY = { description = "Development TLS identity", type = "x509_identity", generate = { san = ["dns:localhost", "ip:127.0.0.1"] } }
# Informational type only, no generationEXTERNAL_API_KEY = { description = "Provided by vendor", type = "password" }See the configuration reference for the full specification.