# SecretSpec commits a declaration of secrets and fetches the values from wherever you keep them

> SecretSpec separates declaring what secrets an application needs from storing their values: you commit a `secretspec.toml`, and the actual values live in a system keyring, a password manager, a cloud secret store or one of more than thirty other providers. The provider list carries the version each backend arrived in, which doubles as a changelog of the project.

**cachix/secretspec** — A declarative interface for every secret provider.

- Repository: https://github.com/cachix/secretspec
- Website: https://secretspec.dev
- Stars: 1,610 · Forks: 103
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/cachix-secretspec

## The declaration is committed, the values are not

The README opens with a problem statement rather than a feature list, and it is specific: secrets end up in `.env` files that get accidentally committed, shared over Slack, or copy pasted between machines.

Three consequences follow from that. Each developer has their own version of the file. Nobody knows which secrets are actually needed. Onboarding means asking around for values.

The fix is a separation. You commit a `secretspec.toml` that declares what secrets your application needs, and the values live in a secure provider such as your system keyring or 1Password or another backend. The declaration is a code review artefact, and it is the thing that tells a new developer, or a CI job, or an auditor exactly what is required.

That inversion is the whole idea. Most secret tooling solves storage and leaves discovery to a spreadsheet. This one treats the list of requirements as the primary artefact and storage as pluggable.

The declarations carry more than names. The feature list mentions descriptions and requirements, so the file can say not just `DATABASE_URL` exists but what it is for and whether it must be set. `secretspec check` then verifies all secrets are set, which is the check you would otherwise write as a startup assertion in services.

There is also a discovery path rather than a requirement that you write the file by hand. `secretspec init` finds secrets from existing `.env` files, so the migration is a command rather than an afternoon of transcription.

## More than thirty providers, and the version floors are a changelog

The provider list is the longest section of the feature list and it is worth reading as two lists: providers you might already pay for, and providers that exist because someone asked for one.

The desktop and password manager entries cover the local cases: keyring as the system keychain and the recommended default, KeePass KDBX, 1Password, Bitwarden Password Manager and Bitwarden Secrets Manager, Keeper Secrets Manager, LastPass, Dashlane, Proton Pass, Pass and Gopass. Passbolt is there too.

The file-oriented entries are interesting because they are deliberately weak: `.env` and plaintext files both exist as providers, and so does a `null` provider. Including those is honest. A tool that refuses to read a `.env` file cannot be introduced gradually, and `null` is what you use in a context where a secret should resolve to nothing, such as a test.

The infrastructure entries are the long tail: Vault, OpenBao, AWS Secrets Manager, AWS Systems Manager Parameter Store, Google Cloud Secret Manager, Cloudflare Secrets Store, Azure Key Vault, Azure App Configuration, Scaleway Secret Manager, Fly.io application secrets, Tailscale Setec, Kubernetes secrets, Infisical, Doppler, systemd credentials, environment variables, age and SOPS.

Each line carries the version it arrived in, and those floors form a readable history: Gopass at 0.15+, Infisical and EJSON around 0.16+, then a burst in 0.17+ covering KDBX, systemd credentials, age, SOPS, Scaleway and OpenBao, 0.18+ adding Bitwarden, AWS Parameter Store, Keeper and Dashlane, 0.19+ adding plaintext files, Passbolt and the null provider, 0.20+ adding Fly.io, Cloudflare, Azure App Configuration, EJSON and Kubernetes, and 0.21+ adding Doppler and Tailscale Setec.

One entry is marked differently: Bitwarden Secrets Manager requires the official `bws` CLI in SecretSpec 0.17 and later, so that provider has an external binary dependency.

## Four commands to go from .env to a running process

The quick start is short enough to read as the intended workflow.

Start with discovery, which reads existing `.env` files and writes the declaration:

```shell-session
$ secretspec init
✓ Created secretspec.toml with 0 secrets
```

Then set up the provider backend, which since 0.17 is an interactive selection:

```shell-session
$ secretspec config global init
? Select your preferred provider backend:
> keyring: Uses system keychain (Recommended)
  kdbx: KeePass KDBX databases (0.17+)
```

Note that this is a global setting, meaning one provider choice for the machine or user, while the declaration itself is per project. That split is deliberate: the project says what it needs, the user says where values live.

After that, two commands. `secretspec check` verifies that all secrets are set, which is the step you run in CI. And `secretspec run -- your-command` runs your process with the secrets resolved into its environment.

The `run` form is the interesting one. Rather than asking your application to read a different configuration format, SecretSpec exports the values as environment variables at process start. That means an existing application usually needs no code change at all, which is what makes the adoption story plausible: change how the values are supplied in development, leave the application alone.

There is also `secretspec config global init` being the only setup step, so nothing is per-project configuration to maintain beyond the `secretspec.toml` itself.

## Profiles, extends and composed secrets

Three features handle the parts of secret management that get improvised elsewhere.

Profiles override secret requirements and defaults per environment, with development and production named as the examples. So a secret can be required in production and optional in development, and defaults can differ per profile without duplicating the file.

Configuration inheritance uses an `extends` feature to extend and override shared configurations. Combined with profiles, this is how a monorepo with twenty services shares one base set of required secrets and adds per-service ones, instead of twenty near-identical files.

Composed secrets, available since 0.16, derive read-only values from declared secrets. The reference syntax is strict and order-independent `${UPPERCASE_NAME}`, and the example given is a DSN, so `DATABASE_URL` composed from a host, a port and a password is declared once and derived. Order independence means the composition does not depend on the order secrets appear in the file.

Two more behaviours decide whether this fits your workflow.

Run prompts, since 0.19, come from setting `prompt = true` on a secret, which requests a hidden value when it is missing. Writable providers save it, while the `null` provider keeps it invocation-only, so a secret can be asked for without being stored.

Audit logging is on by default and records every secret access locally with who, when, why and outcome. The important clause is that secret values are never logged. A local audit trail of access without the values is a reasonable default, and having it on by default rather than opt-in is the right call.

## Secret generation covers keys, not just passwords

Generation is listed as auto-generating passwords, tokens and UUIDs when secrets are missing, and then the list grows into territory most secret tools do not touch.

Since 0.21 it includes OpenPGP and OpenSSH private keys, plus passphrases, BIP-39 mnemonics, WireGuard keys, JWK, age credentials, and self-signed X.509 identities with key and certificate conversions marked as 0.22 and later.

That scope is the interesting part. A development key for SSH is a pain in every other setup, because the usual workaround is a committed test key in a fixture or a key generated by hand once and pasted into a Slack channel. Generating it on demand, in the same mechanism as a database password, removes that class of problem.

Worth noting against the release history: the current version is 0.21.1, published on 2026-09-27 after 0.21.0 on 2026-09-22 and 0.20.0 on 2026-08-31. Features labelled 0.22+ are therefore on `main` and not yet in a tag, which is a fair indication that the project ships fast and that the README describes a moving target.

## Credential helpers and a workspace of language bindings

Three integrations extend the idea past your own application's environment variables.

Since 0.20, a Docker credential helper authenticates registry pulls and pushes through any SecretSpec provider, and a Git credential helper does the same for Git HTTPS and `git send-email` SMTP operations. Both mean the same secret resolution path covers your container registry and your source control, which is where a developer's credentials usually sprawl.

Since 0.21 there is a Claude Code credential integration, described as configuring, storing and removing API or gateway credentials through Claude Code's native `apiKeyHelper`. That is a specific hook rather than a general convention, and it is a useful signal about where the tool's author sees demand.

The repository itself is a Cargo workspace with nine members: the `secretspec` binary, `secretspec-derive` as a proc macro crate, `libsecretspec`, `secretspec-ipc`, a conformance test runner, and bindings for Node, PHP and Python.

The top level shows more: `secretspec-dotnet`, `secretspec-go`, `secretspec-hs`, `secretspec-jvm`, `secretspec-rb` and `secretspec-swift`, alongside `composer.json` and `Package.swift` which confirm the PHP and Swift packages ship through their usual ecosystems. The Rust SDK generates strongly typed structs from `secretspec.toml` for compile-time safety, which is the feature that turns a typo in a secret name from a runtime error into a compile error.

Other files at the root hint at the engineering behind it: a `fuzz/` directory, a `conformance/` directory, `schema/`, a `rust-toolchain.toml`, and topic documents such as `dynamic-secrets.md`, `secret-revisions.md`, `nixos.md` and `OPPORTUNITIES.md`.

The licence is Apache-2.0, and the last push was on 2026-10-16.

## Conclusion

Adopt SecretSpec if your team scatters `.env` files and cannot tell which secrets a service actually needs, since the declaration and the audit log solve exactly that. Skip it if you already have a working secrets platform with its own SDKs and you would only be adding a layer. Verify first that the provider you intend to use exists in the list, and check the version floor next to it, because a provider marked 0.20+ is not available in the release you would install.

## FAQ

### How do I start using SecretSpec on an existing project?

Run `secretspec init`, which discovers secrets from existing `.env` files and creates `secretspec.toml`. Then set a provider with `secretspec config global init`, verify with `secretspec check`, and run your application through `secretspec run -- your-command`.

### Does my application need code changes to use SecretSpec?

No, if your application already reads environment variables. `secretspec run` resolves the declared secrets and passes them to the process as environment variables. For compile-time safety in Rust there is a separate SDK that generates typed structs from the declaration.

### Are secret values written to the audit log?

No. Audit logging is on by default and records every access locally with who, when, why and outcome, and the README states that secret values are never logged.

## Sources

- [cachix/secretspec on GitHub](https://github.com/cachix/secretspec)
- [License: Apache-2.0](https://github.com/cachix/secretspec/blob/main/LICENSE)
- [Project website](https://secretspec.dev)
- [README](https://github.com/cachix/secretspec/blob/main/README.md)
- [Releases](https://github.com/cachix/secretspec/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cachix-secretspec
