A declarative interface for every secret provider.
See the code
Stop committing secrets to git and putting them to .env files.
Secrets end up in .env files that get accidentally committed, shared over Slack, or copy pasted between machines. Each developer has their own version, nobody knows which secrets are actually needed, and onboarding means asking around for values.
SecretSpec fixes this by separating secret declaration from secret storage. You commit a secretspec.toml that declares what secrets your application needs, while the actual values live in a secure provider like your system keyring, 1Password, or any other backend. No secrets in git, no .env files to leak.
Documentation | Quick Start | Announcement Blog Post
secretspec.toml with descriptions and requirementsbws CLI required in SecretSpec 0.17+)secretspec.toml for compile-time safetyprompt = true to request a hidden missing value; writable providers save it, while null keeps it invocation-only${UPPERCASE_NAME} referencesextends featuregit send-email SMTP operations through any SecretSpec providersecretspec init to discover secrets from existing .env filesapiKeyHelper# 1. Initialize secretspec.toml (discovers secrets from .env)
$ secretspec init
✓ Created secretspec.toml with 0 secrets
Next steps:
1. secretspec config global init # Set up user defaults (0.17+)
2. secretspec check # Verify all secrets are set
3. secretspec run -- your-command # Run with secrets
# 2. Set up provider backend
$ secretspec config global init # 0.17+
? Select your preferred provider backend:
> keyring: Uses system keychain (Recommended)
kdbx: KeePass KDBX databases (0.17+)
onepassword: 1Password password manager
keeper: Keeper Secrets Manager (0.18+) via official Rust SDK
doppler: Doppler secret management (0.21+)
dotenv: Traditional .env files
file: Plaintext files, one per secret (0.19+)
env: Read-only environment variables
null: Use defaults, generation, or run prompts without storage (0.19+)
systemd-credential: Read-only systemd service credentials (0.17+)
fly: Fly.io application secrets via flyctl, write-only (0.20+)
cloudflare: Cloudflare Secrets Store, write-only (0.20+)
pass: Unix password manager with GPG encryption
gopass: Gopass CLI password manager with GPG encryption (0.15+)
protonpass: Proton Pass via official pass-cli
passbolt: Passbolt self-hosted password manager (0.19+) via go-passbolt-cli
lastpass: LastPass password manager
dashlane: Dashlane password manager, read-only (0.18+)
gcsm: Google Cloud Secret Manager
awssm: AWS Secrets Manager
awsps: AWS Systems Manager Parameter Store (0.18+)
scaleway: Scaleway Secret Manager (0.17+)
setec: Tailscale Setec secrets service (0.21+)
vault: HashiCorp Vault secret management
openbao: OpenBao secret management (0.17+)
bw: Bitwarden Password Manager (0.18+)
bws: Bitwarden Secrets Manager
akv: Azure Key Vault
aac: Azure App Configuration (0.20+)
infisical: Infisical secret management (0.16+)
ejson: EJSON encrypted files, read-only (0.20+)
age: age-encrypted file (0.17+)
sops: SOPS encrypted files (0.17+)
kubernetes: Kubernetes (0.20+)
? Select your default profile:
> development
default
none
✓ Configuration saved to /home/user/.config/secretspec/config.toml
# 3. Check and configure secrets
$ secretspec check
# 4. Run your application with secrets
$ secretspec run -- npm start
# Or with a specific profile and provider
$ secretspec run --profile production --provider dotenv -- npm start
See the Quick Start Guide for detailed instructions.
curl -sSL https://install.secretspec.dev | sh
SecretSpec 0.20+ provides static musl CLI binaries for x64 and arm64 Linux, so the installer works directly on Alpine without a glibc compatibility layer.
See the installation guide for more options including Nix and Devenv.
Editor autocomplete is available through JSON Schemas (0.21+). Add
#:schema https://secretspec.dev/schema/secretspec.schema.json at the top of
secretspec.toml, or #:schema https://secretspec.dev/schema/config.schema.json
at the top of your user config.toml, in an editor using Taplo. Generate a schema
matching your installed CLI with secretspec schema --config project or
secretspec schema --config global (0.21+); use --output FILE to save it. See the
editor setup guide.
Each project has a secretspec.toml file that declares the required secrets:
[project]
name = "my-app" # Inferred from current directory name when using `secretspec init`
revision = "1.0"
# Optional: extend other configuration files
extends = ["../shared/common", "../shared/auth"]
# SecretSpec 0.21+: use one provider chain unless a profile or secret overrides it.
[defaults]
providers = ["developer"]
[profiles.default]
DATABASE_URL = { description = "PostgreSQL connection string" }
REDIS_URL = { description = "Redis connection string" }
# Profile-specific configurations
[profiles.development]
DATABASE_URL = { required = false, default = "sqlite://./dev.db" }
REDIS_URL = { required = false, default = "redis://localhost:6379" }
[profiles.production]
DATABASE_URL = { required = true }
REDIS_URL = { required = true }
# SecretSpec 0.19+: a profile can use an independent secret set.
[profiles.deployment.defaults]
inherit = false
[profiles.deployment]
DEPLOY_TOKEN = { description = "Deployment credential", required = true }
SecretSpec 0.19+ can map one logical secret to different native coordinates per provider alias, including independent import source and destination refs:
[providers]
remote = { uri = "onepassword://Production", ref = { item = "{project}-{profile}", field = "{key}" } }
local = "keyring://"
[profiles.production]
API_KEY = { description = "API key", providers = ["local"], refs = { remote = { item = "legacy-api", field = "token" } } }
See the configuration reference for all available options.
Profiles allow you to define different secret requirements for each environment (development, production, etc.):
$ secretspec run --profile development -- npm start
$ secretspec run --profile production -- npm start
# Set a user-global default profile (0.17+)
$ secretspec config global init
# SecretSpec 0.17+: set provider and profile defaults without prompting
$ secretspec config global init --provider env --profile default
Learn more about profiles and profile selection.
SecretSpec supports multiple storage backends for secrets:
flyctl; Fly.io does not expose plaintext valuesdcli CLIopenbao:// through the Vault providerbw CLIbws CLI required in SecretSpec 0.17+)$ secretspec run --provider keyring -- npm start
$ secretspec run --provider dotenv -- npm start
# Configure a user-global default provider (0.17+)
$ secretspec config global init
See provider concepts and provider reference for details.
Generate strongly-typed Rust structs from your secretspec.toml:
secretspec_derive::declare_secrets!("secretspec.toml");
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load secrets with type safety
let secrets = SecretSpec::load(Provider::Keyring)?;
// Access secrets as struct fields
println!("Database: {}", secrets.database_url);
// Optional secrets are Option<String>
if let Some(redis) = &secrets.redis_url {
println!("Redis: {}", redis);
}
Ok(())
}
See the Rust SDK documentation for advanced usage including profile-specific types.
Beyond Rust, SecretSpec ships SDKs for other languages. Each is a thin client over the same native core, so every provider, chain, profile, and generator works identically with no per-language resolution logic:
The shared embedded ABI is named libsecretspec in SecretSpec 0.20+; it was
named secretspec-ffi through 0.19. Its exported secretspec_* symbols and the
SECRETSPEC_FFI_LIB override are unchanged.
This embedded library is separate from the
libsecretspec-resolver C client and secretspec-ipc Rust implementation used for
out-of-process client and provider communication in 0.21+.
libsecretspec C ABI)ext-ffi fallback)from secretspec import SecretSpec
resolved = SecretSpec.builder().with_provider("keyring://").with_reason("boot").load()
print(resolved.secrets["DATABASE_URL"].get)
For typed access, secretspec schema emits a JSON Schema you feed to
quicktype to generate typed classes for any language,
built from the SDK's fields() map.
Common commands:
# Initialize and configure
secretspec init # Create secretspec.toml
secretspec config global init # Set up user defaults (0.17+)
# Manage secrets
secretspec check # Verify all secrets are set
secretspec add KEY --description "Purpose" # Declare a secret (0.18+)
secretspec set KEY # Set a secret interactively
secretspec set KEY --from-file secret.bin # Set exact bytes (0.21+)
secretspec get KEY # Retrieve a secret
secretspec delete KEY # Delete a stored provider value (0.18+)
secretspec import PROVIDER # Import secrets from another provider
secretspec import PROVIDER --delete-source # Move verified values (0.18+)
secretspec cache clear [KEY] # Clear cached provider values (0.17+)
# Run with secrets
secretspec run -- command # Run command with secrets as env vars
# Inspect access
secretspec audit # Show the local audit log of secret access
secretspec serve # Private SDK resolver session (0.21+)
# Enable contextual Fish completions for this session (0.20+)
secretspec completions fish | source
See the full CLI reference for all commands and options.
Recognized temporary provider read failures retry with exponential backoff,
using three total attempts by default. Configure [defaults.retry] with
max_attempts = 1 in ~/.config/secretspec/config.toml to disable retries, or
choose an attempt count from 1 to 10. See the retry policy documentation
for batching, safe writes, and native SDK behavior.
We welcome contributions! Areas where you can help:
See our GitHub repository to get started.
This project is licensed under the Apache License 2.0.

(top 24 of 110)
931 followers · starred Sep 2026
2,592 followers · starred Aug 2025
297 followers · starred Sep 2025
929 followers · starred Aug 2026
A declarative interface for every secret provider.
See the code
Stop committing secrets to git and putting them to .env files.
Secrets end up in .env files that get accidentally committed, shared over Slack, or copy pasted between machines. Each developer has their own version, nobody knows which secrets are actually needed, and onboarding means asking around for values.
SecretSpec fixes this by separating secret declaration from secret storage. You commit a secretspec.toml that declares what secrets your application needs, while the actual values live in a secure provider like your system keyring, 1Password, or any other backend. No secrets in git, no .env files to leak.
Documentation | Quick Start | Announcement Blog Post
secretspec.toml with descriptions and requirementsbws CLI required in SecretSpec 0.17+)secretspec.toml for compile-time safetyprompt = true to request a hidden missing value; writable providers save it, while null keeps it invocation-only${UPPERCASE_NAME} referencesextends featuregit send-email SMTP operations through any SecretSpec providersecretspec init to discover secrets from existing .env filesapiKeyHelper# 1. Initialize secretspec.toml (discovers secrets from .env)
$ secretspec init
✓ Created secretspec.toml with 0 secrets
Next steps:
1. secretspec config global init # Set up user defaults (0.17+)
2. secretspec check # Verify all secrets are set
3. secretspec run -- your-command # Run with secrets
# 2. Set up provider backend
$ secretspec config global init # 0.17+
? Select your preferred provider backend:
> keyring: Uses system keychain (Recommended)
kdbx: KeePass KDBX databases (0.17+)
onepassword: 1Password password manager
keeper: Keeper Secrets Manager (0.18+) via official Rust SDK
doppler: Doppler secret management (0.21+)
dotenv: Traditional .env files
file: Plaintext files, one per secret (0.19+)
env: Read-only environment variables
null: Use defaults, generation, or run prompts without storage (0.19+)
systemd-credential: Read-only systemd service credentials (0.17+)
fly: Fly.io application secrets via flyctl, write-only (0.20+)
cloudflare: Cloudflare Secrets Store, write-only (0.20+)
pass: Unix password manager with GPG encryption
gopass: Gopass CLI password manager with GPG encryption (0.15+)
protonpass: Proton Pass via official pass-cli
passbolt: Passbolt self-hosted password manager (0.19+) via go-passbolt-cli
lastpass: LastPass password manager
dashlane: Dashlane password manager, read-only (0.18+)
gcsm: Google Cloud Secret Manager
awssm: AWS Secrets Manager
awsps: AWS Systems Manager Parameter Store (0.18+)
scaleway: Scaleway Secret Manager (0.17+)
setec: Tailscale Setec secrets service (0.21+)
vault: HashiCorp Vault secret management
openbao: OpenBao secret management (0.17+)
bw: Bitwarden Password Manager (0.18+)
bws: Bitwarden Secrets Manager
akv: Azure Key Vault
aac: Azure App Configuration (0.20+)
infisical: Infisical secret management (0.16+)
ejson: EJSON encrypted files, read-only (0.20+)
age: age-encrypted file (0.17+)
sops: SOPS encrypted files (0.17+)
kubernetes: Kubernetes (0.20+)
? Select your default profile:
> development
default
none
✓ Configuration saved to /home/user/.config/secretspec/config.toml
# 3. Check and configure secrets
$ secretspec check
# 4. Run your application with secrets
$ secretspec run -- npm start
# Or with a specific profile and provider
$ secretspec run --profile production --provider dotenv -- npm start
See the Quick Start Guide for detailed instructions.
curl -sSL https://install.secretspec.dev | sh
SecretSpec 0.20+ provides static musl CLI binaries for x64 and arm64 Linux, so the installer works directly on Alpine without a glibc compatibility layer.
See the installation guide for more options including Nix and Devenv.
Editor autocomplete is available through JSON Schemas (0.21+). Add
#:schema https://secretspec.dev/schema/secretspec.schema.json at the top of
secretspec.toml, or #:schema https://secretspec.dev/schema/config.schema.json
at the top of your user config.toml, in an editor using Taplo. Generate a schema
matching your installed CLI with secretspec schema --config project or
secretspec schema --config global (0.21+); use --output FILE to save it. See the
editor setup guide.
Each project has a secretspec.toml file that declares the required secrets:
[project]
name = "my-app" # Inferred from current directory name when using `secretspec init`
revision = "1.0"
# Optional: extend other configuration files
extends = ["../shared/common", "../shared/auth"]
# SecretSpec 0.21+: use one provider chain unless a profile or secret overrides it.
[defaults]
providers = ["developer"]
[profiles.default]
DATABASE_URL = { description = "PostgreSQL connection string" }
REDIS_URL = { description = "Redis connection string" }
# Profile-specific configurations
[profiles.development]
DATABASE_URL = { required = false, default = "sqlite://./dev.db" }
REDIS_URL = { required = false, default = "redis://localhost:6379" }
[profiles.production]
DATABASE_URL = { required = true }
REDIS_URL = { required = true }
# SecretSpec 0.19+: a profile can use an independent secret set.
[profiles.deployment.defaults]
inherit = false
[profiles.deployment]
DEPLOY_TOKEN = { description = "Deployment credential", required = true }
SecretSpec 0.19+ can map one logical secret to different native coordinates per provider alias, including independent import source and destination refs:
[providers]
remote = { uri = "onepassword://Production", ref = { item = "{project}-{profile}", field = "{key}" } }
local = "keyring://"
[profiles.production]
API_KEY = { description = "API key", providers = ["local"], refs = { remote = { item = "legacy-api", field = "token" } } }
See the configuration reference for all available options.
Profiles allow you to define different secret requirements for each environment (development, production, etc.):
$ secretspec run --profile development -- npm start
$ secretspec run --profile production -- npm start
# Set a user-global default profile (0.17+)
$ secretspec config global init
# SecretSpec 0.17+: set provider and profile defaults without prompting
$ secretspec config global init --provider env --profile default
Learn more about profiles and profile selection.
SecretSpec supports multiple storage backends for secrets:
flyctl; Fly.io does not expose plaintext valuesdcli CLIopenbao:// through the Vault providerbw CLIbws CLI required in SecretSpec 0.17+)$ secretspec run --provider keyring -- npm start
$ secretspec run --provider dotenv -- npm start
# Configure a user-global default provider (0.17+)
$ secretspec config global init
See provider concepts and provider reference for details.
Generate strongly-typed Rust structs from your secretspec.toml:
secretspec_derive::declare_secrets!("secretspec.toml");
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load secrets with type safety
let secrets = SecretSpec::load(Provider::Keyring)?;
// Access secrets as struct fields
println!("Database: {}", secrets.database_url);
// Optional secrets are Option<String>
if let Some(redis) = &secrets.redis_url {
println!("Redis: {}", redis);
}
Ok(())
}
See the Rust SDK documentation for advanced usage including profile-specific types.
Beyond Rust, SecretSpec ships SDKs for other languages. Each is a thin client over the same native core, so every provider, chain, profile, and generator works identically with no per-language resolution logic:
The shared embedded ABI is named libsecretspec in SecretSpec 0.20+; it was
named secretspec-ffi through 0.19. Its exported secretspec_* symbols and the
SECRETSPEC_FFI_LIB override are unchanged.
This embedded library is separate from the
libsecretspec-resolver C client and secretspec-ipc Rust implementation used for
out-of-process client and provider communication in 0.21+.
libsecretspec C ABI)ext-ffi fallback)from secretspec import SecretSpec
resolved = SecretSpec.builder().with_provider("keyring://").with_reason("boot").load()
print(resolved.secrets["DATABASE_URL"].get)
For typed access, secretspec schema emits a JSON Schema you feed to
quicktype to generate typed classes for any language,
built from the SDK's fields() map.
Common commands:
# Initialize and configure
secretspec init # Create secretspec.toml
secretspec config global init # Set up user defaults (0.17+)
# Manage secrets
secretspec check # Verify all secrets are set
secretspec add KEY --description "Purpose" # Declare a secret (0.18+)
secretspec set KEY # Set a secret interactively
secretspec set KEY --from-file secret.bin # Set exact bytes (0.21+)
secretspec get KEY # Retrieve a secret
secretspec delete KEY # Delete a stored provider value (0.18+)
secretspec import PROVIDER # Import secrets from another provider
secretspec import PROVIDER --delete-source # Move verified values (0.18+)
secretspec cache clear [KEY] # Clear cached provider values (0.17+)
# Run with secrets
secretspec run -- command # Run command with secrets as env vars
# Inspect access
secretspec audit # Show the local audit log of secret access
secretspec serve # Private SDK resolver session (0.21+)
# Enable contextual Fish completions for this session (0.20+)
secretspec completions fish | source
See the full CLI reference for all commands and options.
Recognized temporary provider read failures retry with exponential backoff,
using three total attempts by default. Configure [defaults.retry] with
max_attempts = 1 in ~/.config/secretspec/config.toml to disable retries, or
choose an attempt count from 1 to 10. See the retry policy documentation
for batching, safe writes, and native SDK behavior.
We welcome contributions! Areas where you can help:
See our GitHub repository to get started.
This project is licensed under the Apache License 2.0.

(top 24 of 110)
931 followers · starred Sep 2026
2,592 followers · starred Aug 2025
297 followers · starred Sep 2025
929 followers · starred Aug 2026