Skip to content

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.

[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 }
TypeDefault OutputOptions
password32 alphanumeric charslength (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 mnemonicalgorithm ("bip39"), words (12, 15, 18, 21, or 24), language ("english")
hex64 hex chars (32 bytes)bytes (int)
base6444 chars (32 bytes)bytes (int)
uuidUUID v4 (36 chars)none
commandstdout of commandcommand (string, required)
rsa_private_key2048-bit RSA private key (PKCS1 PEM)bits (int)
openpgp_private_key (0.21+)ASCII-armored OpenPGP transferable secret keyuser_id (required), algorithm ("ed25519" or "rsa"), bits (RSA only), capabilities (["sign"], ["encrypt"], or both)
ssh_private_key (0.21+)Unencrypted OpenSSH Ed25519 private keyalgorithm ("ed25519" or "rsa"), bits (RSA only), comment (string)
wireguard_private_key (0.21+)Base64-encoded WireGuard private keynone
jwk_private_key (0.21+)Ed25519 private signing JWKalgorithm ("ed25519", "p256", or "rsa"), bits (RSA only), kid (string)
age_identity (0.21+)Native X25519 age identitynone
x509_identity (0.21+)PKCS#12 archive (Base64 at rest) containing a P-256 key and self-signed certificatesan (required), usages, valid_for (1–200 days); issuer = "self_signed", algorithm = "p256"

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_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_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.

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.

  • 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 null provider (0.19+) instead returns a fresh generated value for only the current resolution.
  • generate and default cannot both be set on the same secret.
  • Setting type without generate is informational only and does not trigger auto-generation.

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.

[profiles.default]
# Auto-generated on first run, reused after that
DB_PASSWORD = { description = "Database password", type = "password", generate = true }
# Custom length and character set
ADMIN_PASSWORD = { description = "Admin password", type = "password", generate = { length = 64, charset = "ascii" } }
# 64-byte key encoded as base64
ENCRYPTION_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 size
TLS_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 generation
EXTERNAL_API_KEY = { description = "Provided by vendor", type = "password" }

See the configuration reference for the full specification.