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.22+)Seven BIP-39 English words joined with -words (6–32), separator (non-empty string)
mnemonic (0.22+)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 command; exact bytes in 0.21+command (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.22+)Base64-encoded WireGuard private keynone
jwk_private_key (0.22+)Ed25519 private signing JWKalgorithm ("ed25519", "p256", or "rsa"), bits (RSA only), kid (string)
age_identity (0.22+)Native X25519 age identitynone
x509_identity (0.22+)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.22+) 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.22+ 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.22+) 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 does not trigger auto-generation. In 0.22+, registered X.509 identity and conversion types still validate and interpret the value; other types remain informational.

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.22+)
RECOVERY_CODE = { description = "Recovery code", type = "passphrase", generate = true }
# Checksum-protected BIP-39 mnemonic (requires SecretSpec 0.22+)
WALLET_RECOVERY = { description = "Wallet recovery mnemonic", type = "mnemonic", generate = true }
# Private P-256 JWK (requires SecretSpec 0.22+)
JWT_JWK = { description = "JWT signing JWK", type = "jwk_private_key", generate = { algorithm = "p256" } }
# Self-signed P-256 X.509 identity (requires SecretSpec 0.22+)
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.