SecretSpec 0.7: Declarative Secret Generation
If you haven't tried SecretSpec yet, see Announcing SecretSpec for an introduction.
SecretSpec 0.7 introduces declarative secret generation — declare that secrets should be auto-generated when missing, directly in your secretspec.toml.
The Problem
When onboarding to a project, developers typically need to:
- Read docs to understand which secrets are needed
- Manually generate passwords and tokens
- Store them in the right provider
Some secrets — like local database passwords or session keys — don't need to be shared at all. They just need to exist.
The Solution: type + generate
Add type and generate to any secret declaration, and SecretSpec handles the rest:
[project]
name = "my-app"
revision = "1.0"
[profiles.default]
DB_PASSWORD = { description = "Database password", type = "password", generate = true }
API_TOKEN = { description = "Internal API token", type = "hex", generate = { bytes = 32 } }
SESSION_KEY = { description = "Session signing key", type = "base64", generate = { bytes = 64 } }
REQUEST_ID = { description = "Request ID prefix", type = "uuid", generate = true }
Run secretspec check or secretspec run, and any missing secret with generate configured is automatically created and stored in your provider:
$ secretspec check
Checking secrets in my-app (profile: default)...
✓ DB_PASSWORD - generated and saved to keyring (profile: default)
✓ API_TOKEN - generated and saved to keyring (profile: default)
✓ SESSION_KEY - generated and saved to keyring (profile: default)
✓ REQUEST_ID - generated and saved to keyring (profile: default)
Summary: 4 found, 0 missing
On subsequent runs, the stored values are reused — generation is idempotent.
Five Generation Types
| Type | Default Output | Options |
|---|---|---|
password |
32 alphanumeric characters | length, charset ("alphanumeric" or "ascii") |
hex |
64 hex characters (32 bytes) | bytes |
base64 |
44 characters (32 bytes) | bytes |
uuid |
UUID v4 | none |
command |
stdout of a shell command | command (required) |
Custom Options
Use a table instead of true for fine-grained control:
# 64-character password with printable ASCII
ADMIN_PASSWORD = { description = "Admin password", type = "password", generate = { length = 64, charset = "ascii" } }
# 64 random bytes, hex-encoded (128 chars)
ENCRYPTION_KEY = { description = "Encryption key", type = "hex", generate = { bytes = 64 } }
Shell Commands
The command type runs arbitrary shell commands, covering any generation need:
# WireGuard private key
WG_PRIVATE_KEY = { description = "WireGuard key", type = "command", generate = { command = "wg genkey" } }
# MongoDB keyfile
MONGO_KEYFILE = { description = "MongoDB keyfile", type = "command", generate = { command = "openssl rand -base64 765" } }
# SSH public key (from existing key)
SSH_PUBKEY = { description = "SSH public key", type = "command", generate = { command = "ssh-keygen -y -f ~/.ssh/id_ed25519" } }
Design Decisions
Generate if missing, never overwrite. Existing secrets are always preserved. This makes generation safe to declare in shared config files — it only fills in gaps.
No separate generate command. Generation happens automatically during check and run. A dedicated CLI command for rotation is planned for a future release.
type without generate is valid. You can annotate secrets with a type for documentation purposes without enabling generation. This is useful for secrets that must be manually provisioned but benefit from type metadata.
Conflicts are caught early. generate + default on the same secret is an error (which value should win?). type = "command" with generate = true (no command string) is also an error.
Upgrading
Update to SecretSpec 0.7 and add type/generate to any secrets you want auto-generated. Existing configurations continue to work without changes — both fields are optional.
See the configuration reference for full documentation.
Share your thoughts on our Discord community or open an issue on GitHub.
Domen