Release Notes
Fixed
- Cache reads, refreshes, and clears now share one ownership and freshness
policy, consistently handling expiration boundaries, clock rollback,
corrupted SecretSpec entries, and values owned by another project or profile. - Vault and OpenBao providers reuse one
reqwest::Clientper provider
instance (sameOnceLockpattern as Infisical) instead of building a fresh
client on every get/set/login. Concurrentget_manyof many secrets no
longer opens one TCP(+TLS) handshake per secret against reverse-proxied
deployments, which was observed to drop part of the burst with
Failed to connect to Vault. get_each(defaultProvider::get_many) caps concurrent unique-address
fetches at 8 by default, overridable withSECRETSPEC_PROVIDER_CONCURRENCY.
Waves replace a single unboundedthread::scopefan-out.- Vault/OpenBao HTTP sends retry up to 3 times on connect/timeout errors only
(not on HTTP 4xx/5xx), with a short backoff between attempts.
Added
-
SOPS provider (
sops://,sopsbuild feature) for reading and writing
YAML, JSON, dotenv, and INI files through the SOPS CLI, including templated
per-project/profile paths and provider-credential injection for encryption
keys and cloud authentication. Writes are serialized and atomically replace
encrypted files, with secret values passed to SOPS over standard input. -
Scaleway Secret Manager provider (
scaleway://,scalewaybuild feature) for
storing secrets in Scaleway's Secret Manager over its v1beta1 REST API.
Authenticates with an API secret key (secret_keycredential or
SCW_SECRET_KEY), targets a region (URI host orSCW_DEFAULT_REGION, default
fr-par) and project (?project_id=orSCW_DEFAULT_PROJECT_ID), and stores
convention secrets under the folder pathsecretspec/{project}/{profile}with
the key as the secret name. Nativerefreferences may select a JSON key with
fieldand a revision withversion, and are read-only. -
Cached provider aliases with ordered authoritative
fallbackroutes,
configurable local cache freshness, cache-first reads, automatic refresh
after reads and writes, andsecretspec cache clearinvalidation.A cache must be a distinct store from the route's own authoritative providers
(compared by canonical provider URI, so equivalent spellings of one store
cannot disguise a cache as its own source), must be a store SecretSpec can
delete from (keyring, pass, gopass, dotenv, or a Vault/OpenBao KV v2 mount) so
its entries can be invalidated, and must be the only entry in aproviders
list. All three are reported when the route is planned, and an unusable
max_agewhen the configuration loads.Every entry records the project and profile that own it, and SecretSpec only
changes an entry it can show is its own: a value it did not write, or one
belonging to another project or profile, is left alone by reads and refreshes
and reported bycache clearrather than deleted, since an address alone is not
proof of ownership when a store is shared. An entry marked as SecretSpec's own
but unreadable is replaced.A cached value never outlives the write that superseded it: a failed refresh, a
cache that could not be constructed, and a write that bypassed the cache with
--providerall invalidate the entry. An entry no read can serve — expired, or
written for a different route — is deleted when found rather than skipped, so an
expired value does not keep its plaintext in a store that cannot expire
anything. Where the store can expire a value itself,max_ageis applied
server-side — Vault and OpenBao set the KV v2 path'sdelete_version_after— so
a cached copy stops existing at that age even if SecretSpec is never run again,
and clearing a KV v2 entry destroys its recoverable version history.cache clearreports how many entries it actually removed, ignores provider
overrides, and clears what it can before reporting a cache store it could not.
Cache writes are audited ascache_refreshrather thanset.
(#199) -
secretspec config global init --provider <PROVIDER> --profile <PROFILE>
can save explicitly user-global defaults without interactive prompts,
including--profile noneto clear the default profile. Theglobal
namespace also supports config inspection and provider-alias commands;
existing invocations without the namespace remain compatible.
(#171) -
Secret scopes: a
[scopes]table names membership-only subsets of a
profile's secrets, so a single service or task resolves only what it declares
instead of the whole profile.check,run, andexporttake--scope
(SECRETSPEC_SCOPE); the consumer-visible set is the intersection of the
selected profile and the scope's secret list. Scopes are orthogonal to
profiles and never change a secret'srequired/default/providers or its
storage address. A composed secret in a scope still resolves its dependencies
— even ones the scope leaves out — to build its value, but those dependencies
are never exposed to the scope, and a provider warning about one calls it "a
hidden composition input" rather than naming it; a secret that is neither in the scope nor a
dependency of one is never fetched, and a scope whose intersection with the
selected profile is empty contacts no provider at all (resolve and report
results then carry an emptyprovider). A scope's own list must name at least
one secret, with no blank or repeated entries.
run --scoperemoves every manifest-declared secret the scope does not
admit from the child environment — across all profiles, even one the parent
already exported — so no value can leak into the launched process; a secret
the scope lists is kept even when the selected profile does not declare it.
export --scopeemits the scoped subset but unsets nothing, since no output
format can express an unset.set, likeimport, ignores an ambient
SECRETSPEC_SCOPEentirely, and a blank--scope(or a blank
SECRETSPEC_SCOPE) clears an inherited scope instead of deferring to it. Under
projectextends, a child scope replaces the parent scope of the same name
outright rather than unioning their secret lists. Typed SDK loaders ignore an
ambientSECRETSPEC_SCOPE, since a generated struct always expects the full
profile;importlikewise ignores scope and always copies the whole profile.
Untyped SDK/FFI builders expose explicit scope selection and return the active
scope in resolve/report results. Audit events for scopedcheck,run, and
exportoperations record the scope name as well as the keys accessed or
exposed. -
age provider (
age://) for storing dotenv-style secret sets in an
age-encrypted file, with ASCII armor by default, team recipient rosters,
direct X25519 and SSH key support, native tagged recipients, and
non-interactive age plugins. Hybrid ML-KEM-768 + X25519 keys are recommended
for new setups to protect stored ciphertext against future quantum attacks. -
Read-only systemd credential provider (
systemd-credential://) for resolving
secrets and provider authentication credentials from the current service's
$CREDENTIALS_DIRECTORY, including exact-name references and strict
filename, file-type, and text validation. -
KeePass KDBX provider (
kdbx:,kdbxbuild feature) for local encrypted
databases. It reads KDBX 3 and KDBX 4, writes KDBX 4 with atomic file
replacement, supports master passwords and key files, and can address
standard or custom entry fields through secret references. -
The
requiredfield acceptsat_least_oneandexactly_onegroup tables,
supporting overlapping alternative and mutually exclusive credentials
acrosscheck,run, and SDK resolution. -
OpenBao provider (
openbao://,openbaobuild feature) with its
own provider identity, documentation, and OpenBao CLI configuration through
BAO_ADDR,BAO_NAMESPACE,BAO_TOKEN, andBAO_TOKEN_PATH. The
provider also has OpenBao-prefixed AppRole and JWT inputs; corresponding
VAULT_*names remain compatibility fallbacks. Compatible KV and standard
authentication mechanics are shared internally with the Vault provider.
Vault-compatible addresses accept trailing slashes, and AppRole/JWT login
exchanges now honor the configured namespace. Reported provider URIs strip
endpoint credentials while retaining non-secret store and authentication
attribution. -
Vault / OpenBao JWT/OIDC authentication (
?auth=jwt) logs in through a
configured Vault role usingVAULT_JWT, or requests a short-lived OIDC token
automatically in GitHub Actions and Forgejo Actions jobs withid-token: write. The role and optional audience can be set in the provider URI or with
VAULT_JWT_ROLEandVAULT_JWT_AUDIENCE. -
The Python SDK now publishes a Windows x64 wheel to PyPI, so
pip install secretspecanduv add secretspecwork on Windows.
(#177) -
The Ruby SDK now publishes a Windows gem (
x64-mingw-ucrt) to RubyGems, so
gem install secretspecworks with RubyInstaller on Windows. -
The PHP SDK now publishes prebuilt Windows x64 extension binaries
(secretspec-php-native-<php>-nts-x86_64-pc-windows-msvc.dll) alongside the
Linux and macOS builds on each release.
Changed
- The Bitwarden Secrets Manager provider now invokes the separately installed
officialbwsCLI instead of linking the Bitwarden SDK. This removes the
SDK's restricted-license dependency from SecretSpec distributions while
preserving project-scoped reads, writes, access-token credentials, and
EU/self-hosted server selection. - Secret status output now emphasizes secret names, de-emphasizes descriptions,
and omits placeholder text when a description is unavailable, making long
checkandimportresults easier to scan.
(#139)
Fixed
- BWS CLI writes preserve secret keys and values that begin with
-, and
hostless BWS provider URIs stay pinned to Bitwarden's default server even
when ambient BWS profiles or server settings are configured. - The dotenv provider rejects variable names its parser cannot read back
(anything outside[A-Za-z_][A-Za-z0-9_.]*, for example arefitem
containing a dash) instead of writing a line that made every later read and
write of the whole file fail to parse. The rejection happens before the CLI
prompts for a value and names the offending item.
Install secretspec 0.17.0
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/cachix/secretspec/releases/download/v0.17.0/secretspec-installer.sh | shDownload secretspec 0.17.0
| File | Platform | Checksum |
|---|---|---|
| secretspec-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| secretspec-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| secretspec-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| secretspec-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| secretspec-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |