# Secret Generation

> Automatically generating passwords, tokens, and keys for missing secrets

**New in version 0.7**

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

```toml
[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

| Type                            | Default Output                                                                      | Options                                                                                                                            |
| ------------------------------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `password`                      | 32 alphanumeric chars                                                               | `length` (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 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; exact bytes in 0.21+                                             | `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.22+) | Base64-encoded WireGuard private key                                                | none                                                                                                                               |
| `jwk_private_key` (0.22+)       | Ed25519 private signing JWK                                                         | `algorithm` (`"ed25519"`, `"p256"`, or `"rsa"`), `bits` (RSA only), `kid` (string)                                                 |
| `age_identity` (0.22+)          | Native X25519 age identity                                                          | none                                                                                                                               |
| `x509_identity` (0.22+)         | 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

**Changed in version 0.21**

Command output is preserved byte-for-byte, including non-UTF-8 data, NULs, whitespace, and final newlines. Output that is empty or contains only whitespace is rejected. Use `printf` or trim inside your command if you need a value without a final newline. Binary output needs a byte-capable provider or manifest `encoding`.

The `command` type runs a shell command and uses its stdout as the generated value:

```toml
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

**New in version 0.21**

`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:

```toml
[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

**New in version 0.21**

`ssh_private_key` generates an unencrypted OpenSSH private key entirely in process. `generate = true` uses Ed25519, the sensible default for new SSH keys:

```toml
[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

**New in version 0.22**

`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`:

```toml
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:

```toml
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:

```toml
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](/reference/configuration/#derived-typed-secrets-022) for the complete example and validation rules.

## 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 `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.

## Ephemeral generation with null

**New in version 0.19**

Use `providers = ["null"]` when the value should be generated on demand and never written to provider storage:

```toml
[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

```toml
[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](/reference/configuration/#secret-generation) for the full specification.