Skip to content

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:

  1. Read docs to understand which secrets are needed
  2. Manually generate passwords and tokens
  3. 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.

curl -sSL https://install.secretspec.dev | sh

See the configuration reference for full documentation.

Share your thoughts on our Discord community or open an issue on GitHub.

Domen