Release Notes
Changed
-
Provider names remain registered when their Cargo feature is disabled, so
selecting one now identifies the feature required by the build instead of
incorrectly reporting that the provider does not exist. Provider discovery
and enabled or disabled builds now share the same metadata catalog. -
Interactive prompts now use the Console terminal backend, reducing CLI
dependencies and allowing Unix signal handling to use Signal Hook 0.4. -
SecretSpec now builds against Rand 0.10 and the TOML 1.1 ecosystem, improving
compatibility with current Linux distribution Rust packages. -
Secret resolution now applies provider fallbacks, generated and default
values, compositions, scopes, and presence constraints through one ordered
pipeline, keeping CLI and SDK resolution behavior aligned. -
The embedded C ABI is named
libsecretspecin 0.20+, replacing the
secretspec-fficrate,secretspec_ffilibrary filenames, and
secretspec_ffi.pc. Its exportedsecretspec_*symbols and
SECRETSPEC_FFI_LIBoverride are unchanged. Runtime-loading SDKs retain
pre-0.20 shared-library filename fallbacks where supported. -
The Infisical provider now uses a separate HTTP connection for Universal Auth
login, keeping the connection pool used for subsequent secret reads reliable. -
The 1Password provider keeps batched reference resolution efficient when
some items are missing: it lists missing items once per vault and retries the
batch without them, instead of falling back to oneop readprocess per
secret. Authentication and unavailable-CLI errors fail immediately, while
missing optional items whose names resemble authentication diagnostics remain
missing rather than aborting the batch. -
The value-free resolution surfaces —
secretspec check --json,
check --explain, and the SDKs' report and no-values resolutions — no longer
report a requiredgeneratesecret as resolved while no provider holds
its value. Because these surfaces deliberately mint nothing, such a secret is
now reported asmissing_requiredand the command exits non-zero, instead of
passing a CI gate against an empty store. Runsecretspec checkor
secretspec runonce to generate and store the value; the preflight then
reports it as resolved from its provider. An optionalgeneratesecret, or
one routed to a provider that never retains generated values such asnull,
is unaffected and still resolves.check --explainalso phrases such an
entry aswill generaterather thangenerated, since nothing was minted. -
Git credential configuration now works on Windows, keeps repository-local
includes valid when repositories move, distinguishes percent-encoded reserved
path bytes, avoids persisting an ambient profile, and exits quietly when its
output pipe closes on Unix. -
Dotenv parsing and rendering now use dotenv-ng throughout the dotenv
provider, age-encrypted dotenv blobs, andsecretspec export --format dotenv. Values containing$remain literal, output uses only the quoting
needed to round-trip, and bcrypt-style strings containing$2a$10$...are
no longer corrupted while reading (#73). Dotenv keys may include hyphens,
leading digits, leading dots, and Unicode. Whitespace,=,#, and control
characters remain invalid in keys. -
The Rust
secretspec-derivemacro now uses the support crates re-exported by
secretspec, so applications no longer need explicitserdeorsecrecy
dependencies just to compile generated types. -
secretspec checkwrites its human-readable report to stdout, consistently
withcheck --jsonandcheck --explain, so it can be piped and redirected
without mixing the report with diagnostics. Scripts that captured the report
from stderr must capture stdout instead;2>&1continues to work. Rust SDK
callers can select a report sink withSecrets::check_with_writer, while
the existingSecrets::checkAPI and its stderr behavior remain unchanged.
Added
-
The age provider supports deleting secrets in 0.20+:
secretspec delete,
secretspec import --delete-source, and cache invalidation now work with it,
so an age-encrypted file can serve as the local store of a cached provider
alias. -
Native SDKs (0.20+) can resolve a strict, versioned inline secret declaration
through the newsecretspec_callC ABI entry point. Inline declarations use
an explicit logical base directory for relative providers, reject unknown
fields, and require the new symbol so an older library cannot silently fall
back to a filesystem manifest. The Go, Python, Node.js, Ruby, Haskell, PHP,
C#, and Swift SDKs expose this as theirWithInlineSpec/
with_inline_specbuilder method. -
Static musl CLI release binaries for x64 and arm64 Linux are available in
0.20+, so the standalone installer andsecretspec-updatework on Alpine
without a glibc compatibility layer. -
Rust SDK (0.20+):
Spec::schema_jsonexposes the value-free JSON Schema
generated bysecretspec schemato Rust SDK callers without enabling the CLI
feature. -
Rust SDK (0.20+):
SpecBuilderpreserves comments, key order, quoting, and
unrelated syntax when adding, replacing, or removing declarations from a
Specloaded from TOML.preserved_textexposes the exact edited root
document, whileto_tomlrenders freshly formatted TOML when no preserved
document is available. Inherited declarations remain in their parent files
and are revalidated after every edit. -
The Node.js SDK publishes musl builds of the native addon in 0.20+, so
require("secretspec")works on Alpine images such asnode:alpine
(#383). npm picks the build that matches the host libc, on both x64 and
arm64. -
extractsupports INI documents in SecretSpec 0.20+, selecting an
unsectioned key with/keyor a named-section key with/section/key. -
Rust SDK (0.20+):
SecretSpecBuilder::prompt_missinglets the typed loader
generated bydeclare_secrets!prompt for and store missing required secrets
interactively, matchingSecrets::ensure_secretson the untyped SDK. It is
disabled by default (RequiredSecretMissingstill fails fast, exactly as
before) and only prompts when stdin is a real terminal. -
In 0.20+, a read-only
git-credential-secretspechelper lets Git retrieve
HTTPS usernames and tokens through SecretSpec providers without duplicating
them in Git's credential store. Its built-in manifest keeps the default
independent of the current directory and isolates values by protocol, host,
and configured path; equivalent unreserved URL encodings select one canonical
credential, including for flat-key providers.secretspec git loginand
logoutmanage those values explicitly, and manually registered embedded
helpers can use the stablePASSWORDandUSERNAMEaliases.
SMTP credential contexts supportgit send-emailwithout writing
sendemail.smtpPass, with passwords isolated by case-insensitive server,
port, and username. Path-scoped HTTP(S) credentials take precedence over
host-wide fallbacks, and username-bearing Git requests select only the
matching credential.
configureandunconfiguresafely manage repository or global Git
configuration without replacing existing helpers. The owner-only managed
file is durably written, custom-manifest symlinks are preserved, and failed
or repeated include removal leaves recoverable state. Only explicitly passed
provider and reason options are persisted, while--fileretains the
custom-manifest workflow (0.20+). -
Kubernetes provider (
k8s+<configmap|secret>://, 0.20+): store, read,
delete, and discover values in a Kubernetes ConfigMap or Secret using the
current cluster. -
EJSON provider (
ejson:, 0.20+): read string values from an encrypted
EJSON file with RFC 6901 JSON Pointer references. The private key comes from
an explicitprivate_keyprovider credential, so an existing provider such
as Google Cloud Secret Manager can supply an exact key without putting it in
the URI, environment, process arguments, or a local key file. Batch
resolution decrypts each file once, and the initial provider is intentionally
read-only. -
Azure App Configuration provider (
aac://, 0.20+): select direct
values and Azure Key Vault references by label, prefix, and tags, with Entra
ID or connection-string authentication and guarded writes, deletion, and
declaration discovery. Azure Key Vault references can pin an exact secret
version, cached-route validation compares canonical vault endpoints
independently of authentication choice, and discovery rejects ambiguous or
invalid convention keys. HTTP redirects are rejected so reads and
secret-bearing writes remain confined to the configured store endpoint. -
Rust SDK (0.20+): describe secrets without TOML through the public
Spec,
SpecBuilder,Profile, andSecretAPI. TOML parsing and code generation
use the same validated model, so Rust-first and file-backed projects share
inheritance, generation, and provider behavior, including profile-level
requiredness defaults and explicit opt-outs from inherited path, prompt, and
generation settings. Existing specs can be copied or consumed back into a
builder to add, replace, or remove declarations before rebuilding a validated
spec. This validated declaration API replaces the previously exposed raw
configuration and code-generation implementation types. Custom provider
implementations should now return declarations such as
Secret::required(...)fromProvider::reflectinstead of constructing raw
configuration secrets. -
Structured caller context (0.20+) lets CLI and SDK integrations identify the
invoking software, version, operation, and non-secret resource independently
of the user-supplied access reason. Audit records and providers receive the
context, but it never satisfies therequire_reasonpolicy. -
Code generation (0.20+) carries each secret's declared
descriptioninto the
generated JSON Schema as adescriptionkey on its property.
quicktype
turns that into a native docstring in every target language, so SDKs
generated from a manifest carry the same descriptions the manifest already
declares, instead of losing them at the schema boundary. -
The Fly.io
flyprovider (0.20+) publishes and deletes application secrets
withsecretspec setandsecretspec delete, and discovers their names with
init --from. Fly.io never exposes plaintext secret values, so the provider
clearly rejects read operations and CLI guidance recommends only supported
workflows. Writes keep values off process arguments by streaming them to
flyctl secrets setover stdin, refuse boundary whitespace thatflyctl
would silently trim, and scrub ambient Fly token variables before injecting
the token selected through the provider credential mechanism. -
The Cloudflare
cloudflareprovider (0.20+) publishes, replaces, deletes,
and discovers account-level Secrets Store entries through Cloudflare's API.
Cloudflare never returns plaintext values through its management API, so the
provider clearly reports its write-only behavior. Authentication can use a
SecretSpecapi_tokencredential,CLOUDFLARE_API_TOKEN, or the current
Wrangler OAuth, API-token, or legacy API-key session; secret values are sent
only in HTTPS request bodies (#84). -
secretspec completions <shell>(0.20+) generates completion scripts for
Bash, Elvish, Fish, Nushell, PowerShell, and Zsh directly from the CLI
definition, including descriptions and contextual suggestions for profiles,
scopes, secret names, providers, aliases, paths, and commands. Completion
reads configuration metadata only; it never queries providers or reads
secret values. -
In 0.20+, a read-only
docker-credential-secretspechelper lets Docker
retrieve registry usernames and tokens through any SecretSpec provider.
secretspec docker configureandunconfiguresafely manage per-registry
Docker credential-helper settings without replacing existing helpers, while
secretspec docker loginandlogoutmanage isolated embedded credentials;
custom manifests remain available through--file(0.20+). -
JVM SDK (0.20+): use SecretSpec from languages such as Java or Kotlin.
Fixed
-
1Password convention-secret batches now retrieve all matching items through
oneop item getprocess instead of starting one process per secret, avoiding
desktop-app connection timeouts when resolving larger manifests. Batch input
uses the CLI's structured JSON interface so reads work consistently across
1Password CLI releases (#398). -
Bitwarden Password Manager convention secrets now use
secretspec/{project}/{profile}/{key}item titles, preventing a same-named
secret in another project or profile from being read or overwritten. The
existing?folder=option customizes that title prefix. Bare items created
by releases through 0.19 can be renamed to the namespaced title or retained
with an explicitref; declaration discovery emits those legacy bare items
as refs automatically. Names remain isolated when a project or profile
contains/, andsecretspec init --from bw://recognizes convention names
case-insensitively while preserving legacy items' native references. (#369) -
On Unix,
secretspec runnow forwardsSIGTERM,SIGINT, andSIGHUPto
the command it started, allowing graceful container shutdown even when
SecretSpec is PID 1. Commands terminated by a signal now produce the
conventional128 + signalexit status instead of always exiting 1
(#382). -
Format-preserving
Specedits now keep inherited declarations separate after
semantic builder changes, resolve parent specs independently of later working
directory changes, remove synthesized profile tables when additions are
undone, and apply description validation consistently across builder origins. -
The Kubernetes provider now checks the correct Kubernetes Secret resource
permission and refusesimport --delete-sourcebefore copying values when
the source object cannot be patched. -
Closing the output pipe now ends the CLI quietly on Unix, so
secretspec export | headandsecretspec check --json | headbehave like
any other Unix tool. PreviouslyexportreportedIO error: Broken pipe
and exited 1, andcheck --jsonpanicked withfailed printing to stdout,
because Rust ignoresSIGPIPEby default and surfacedEPIPEinstead. The
entry point now restores the defaultSIGPIPEdisposition, which covers
every command that writes to stdout. -
cargo runcontinues to launch the main SecretSpec CLI after installing the
Docker credential-helper binary in the same package (0.20+). -
Embedded Docker credentials now remain isolated by registry and Docker
configuration when used with flat providers such as Dotenv. Provider keys
carry the same stable identity as the embedded project, preventing one
registry's login or logout from affecting another (0.20+). -
Docker now reports when
configurereplaces a registry's existing
SecretSpec metadata and clarifies that the stored credential was not removed
(0.20+). -
Docker can now manage the same registry independently in multiple
DOCKER_CONFIGdirectories. Helper lookup and embedded credential storage
are isolated by both registry and Docker configuration (0.20+). -
Equivalent
DOCKER_CONFIGpaths that resolve through symlinked directories
now share one Docker credential identity, so helper lookup and cleanup work
regardless of which path spelling invokes them (0.20+). -
Default Docker audit caller context no longer reports the SecretSpec release
as Docker's version. Docker remains identified as the caller while its
unknown version is omitted (0.20+). -
Docker's managed credential state is now restricted to owner-only permissions
without changing the existing mode of Docker's ownconfig.json. Atomic
updates and final-entry removal preserve a symlinked state file, and
interrupted removal can be resumed when Docker's helper entry was already
deleted (0.20+). -
Docker credentials configured through a symlinked custom manifest now retain
that logical manifest path, so relativeextendsentries continue to resolve
from the directory where the symlink was selected (0.20+). -
secretspec dockerno longer treats exportedSECRETSPEC_FILE,
SECRETSPEC_PROFILE,SECRETSPEC_PROVIDER, orSECRETSPEC_REASONas typed
configuration flags. Only an explicit--provideror--reasonis saved for
later helper calls, and ambient manifest/profile settings no longer switch or
block the embedded credential workflow (0.20+). -
Custom-manifest Docker credentials now pin a profile only when
--profileis
explicitly supplied, preventing a shell's ambient profile from becoming a
permanent helper setting (0.20+). -
Google Cloud Secret Manager convention names now use the readable,
versionedsecretspec2--{project}--{profile}--{key}layout. Distinct logical
addresses such asmy-app/prod/Kandmy/app-prod/Kcan no longer collide
on one stored secret. When the new id holds no value, reads fall back to the
matching 0.19secretspec-{project}-{profile}-{key}secret and warn once per
run, so an upgraded project keeps working with no migration step and no new
permissions: the fallback only reads, and credentials that cannot create
secrets are unaffected. Writes always use the new id, sosecretspec set
moves a secret, after which reads stop consulting the legacy id. The 0.19
secret is left in place for rollback and should only be deleted once its
value has been written under the new id. Names that releases through 0.19
accepted but the new layout cannot represent, such as a project containing
--, keep reading their 0.19 secret with a warning; writing them requires
renaming the component or addressing the secret with aref.
Secret-level IAM bindings on 0.19 ids also keep working when an unbound new
id returns permission denied, while other access failures remain errors
instead of being mistaken for missing values. Explicitrefaddresses
remain unchanged. (#219) -
Bare
bws://<project-uuid>provider URIs now target the Bitwarden US cloud
vault instead of the public marketing site, restoring reads and writes while
keeping the server pinned independently of ambientbwsconfiguration.
(#359) -
Node SDK processes using
loadAsync()orreportAsync()with AWS Secrets
Manager or Parameter Store now exit normally after resolution. Provider
runtime and TLS state is torn down on a short-lived resolver thread instead
of remaining attached to a persistent libuv worker during macOS process
shutdown. (#343) -
The
awssmandscalewayproviders now treat a JSONnullin areffield
as no value, the same as an absent key, so the provider chain continues.
Previously it was rendered as the four-character stringnull, which
satisfied a required secret and reached the program as a password or token
spelledn-u-l-l. Thebwanddashlaneproviders already behaved this way.
Anextractpointer is unchanged: it names one location and still reports a
nullthere, and the two policies now sit next to each other in one place. -
The Python and Ruby SDKs'
Resolved.close()/Resolved#closenow remove every
as_pathtemp file even when one of them cannot be removed, raising the first
such error only after the rest are cleaned up. Previously the first failure
aborted the loop and left the remaining secret files on disk, which is the
outcomecloseexists to prevent. This matches the Go SDK'sfirstErrand the
.NET SDK'sfirstError. The Ruby SDK also no longer skips a dangling symlink,
whichFile.exist?reports as absent. -
The
awssmprovider now accepts a trailing slash in?prefix=without
inserting a second slash into the AWS secret name. For example,
?prefix=myteam/resolves tomyteam/secretspec/..., matching
?prefix=myteam, and both spellings share one provider identity so import
diagnostics still recognize alias-specific references. This avoids silently
treating the secret as missing or writing to a distinct double-slash name
(#344). -
import --delete-sourcenow rejects providers that cannot delete during
preflight, before writing any destinations. Previously the import could copy
a value successfully and fail only when source cleanup began. -
Infisical secret references no longer require
?env=in the provider URI: a
refnames a folder and key but never an environment, so it now falls back to
the profile the run resolves under. One alias can therefore serve every profile
while naming secrets flat —ref = { item = "/{key}" }— instead of needing
one alias per environment. An explicit?env=still pins the environment.
When every requested secret gets Infisical's ambiguous 404, SecretSpec now
checks the environment root once without requesting secret values and reports
a missing environment or project, naming whether the profile or?env=
selected it. A genuinely absent secret or folder in an existing environment
remains unset so provider fallback still works. A credential declared with a
refstill needs?env=, so it resolves the same way whichever profile is
running. (#338) -
secretspec setagainst an Infisical secret names the environment in its
pre-write preview, which the previous description left out. -
Infisical import collision checks now recognize when aliases target the same
secret through a profile-derived versus explicit environment, or through an
absolute ref that overrides different configured path defaults, preventing
aliased destinations from overwriting one another.
Install secretspec 0.20.0
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/cachix/secretspec/releases/download/v0.20.0/secretspec-installer.sh | shDownload secretspec 0.20.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 |
| secretspec-aarch64-unknown-linux-musl.tar.xz | ARM64 MUSL Linux | checksum |
| secretspec-x86_64-unknown-linux-musl.tar.xz | x64 MUSL Linux | checksum |