Skip to content

0.20.0 - 2026-08-31

Latest

Choose a tag to compare

@github-actions github-actions released this 31 Aug 18:19
2b63f45

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 libsecretspec in 0.20+, replacing the
    secretspec-ffi crate, secretspec_ffi library filenames, and
    secretspec_ffi.pc. Its exported secretspec_* symbols and
    SECRETSPEC_FFI_LIB override 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 one op read process 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 required generate secret as resolved while no provider holds
    its value. Because these surfaces deliberately mint nothing, such a secret is
    now reported as missing_required and the command exits non-zero, instead of
    passing a CI gate against an empty store. Run secretspec check or
    secretspec run once to generate and store the value; the preflight then
    reports it as resolved from its provider. An optional generate secret, or
    one routed to a provider that never retains generated values such as null,
    is unaffected and still resolves. check --explain also phrases such an
    entry as will generate rather than generated, 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, and secretspec 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-derive macro now uses the support crates re-exported by
    secretspec, so applications no longer need explicit serde or secrecy
    dependencies just to compile generated types.

  • secretspec check writes its human-readable report to stdout, consistently
    with check --json and check --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>&1 continues to work. Rust SDK
    callers can select a report sink with Secrets::check_with_writer, while
    the existing Secrets::check API 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 new secretspec_call C 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 their WithInlineSpec /
    with_inline_spec builder method.

  • Static musl CLI release binaries for x64 and arm64 Linux are available in
    0.20+, so the standalone installer and secretspec-update work on Alpine
    without a glibc compatibility layer.

  • Rust SDK (0.20+): Spec::schema_json exposes the value-free JSON Schema
    generated by secretspec schema to Rust SDK callers without enabling the CLI
    feature.

  • Rust SDK (0.20+): SpecBuilder preserves comments, key order, quoting, and
    unrelated syntax when adding, replacing, or removing declarations from a
    Spec loaded from TOML. preserved_text exposes the exact edited root
    document, while to_toml renders 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 as node:alpine
    (#383). npm picks the build that matches the host libc, on both x64 and
    arm64.

  • extract supports INI documents in SecretSpec 0.20+, selecting an
    unsectioned key with /key or a named-section key with /section/key.

  • Rust SDK (0.20+): SecretSpecBuilder::prompt_missing lets the typed loader
    generated by declare_secrets! prompt for and store missing required secrets
    interactively, matching Secrets::ensure_secrets on the untyped SDK. It is
    disabled by default (RequiredSecretMissing still fails fast, exactly as
    before) and only prompts when stdin is a real terminal.

  • In 0.20+, a read-only git-credential-secretspec helper 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 login and
    logout manage those values explicitly, and manually registered embedded
    helpers can use the stable PASSWORD and USERNAME aliases.
    SMTP credential contexts support git send-email without 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.
    configure and unconfigure safely 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 --file retains 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 explicit private_key provider 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, and Secret API. 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(...) from Provider::reflect instead 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 the require_reason policy.

  • Code generation (0.20+) carries each secret's declared description into the
    generated JSON Schema as a description key 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 fly provider (0.20+) publishes and deletes application secrets
    with secretspec set and secretspec 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 set over stdin, refuse boundary whitespace that flyctl
    would silently trim, and scrub ambient Fly token variables before injecting
    the token selected through the provider credential mechanism.

  • The Cloudflare cloudflare provider (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
    SecretSpec api_token credential, 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-secretspec helper lets Docker
    retrieve registry usernames and tokens through any SecretSpec provider.
    secretspec docker configure and unconfigure safely manage per-registry
    Docker credential-helper settings without replacing existing helpers, while
    secretspec docker login and logout manage 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
    one op item get process 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 explicit ref; declaration discovery emits those legacy bare items
    as refs automatically. Names remain isolated when a project or profile
    contains /, and secretspec init --from bw:// recognizes convention names
    case-insensitively while preserving legacy items' native references. (#369)

  • On Unix, secretspec run now forwards SIGTERM, SIGINT, and SIGHUP to
    the command it started, allowing graceful container shutdown even when
    SecretSpec is PID 1. Commands terminated by a signal now produce the
    conventional 128 + signal exit status instead of always exiting 1
    (#382).

  • Format-preserving Spec edits 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 refuses import --delete-source before copying values when
    the source object cannot be patched.

  • Closing the output pipe now ends the CLI quietly on Unix, so
    secretspec export | head and secretspec check --json | head behave like
    any other Unix tool. Previously export reported IO error: Broken pipe
    and exited 1, and check --json panicked with failed printing to stdout,
    because Rust ignores SIGPIPE by default and surfaced EPIPE instead. The
    entry point now restores the default SIGPIPE disposition, which covers
    every command that writes to stdout.

  • cargo run continues 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 configure replaces 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_CONFIG directories. Helper lookup and embedded credential storage
    are isolated by both registry and Docker configuration (0.20+).

  • Equivalent DOCKER_CONFIG paths 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 own config.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 relative extends entries continue to resolve
    from the directory where the symlink was selected (0.20+).

  • secretspec docker no longer treats exported SECRETSPEC_FILE,
    SECRETSPEC_PROFILE, SECRETSPEC_PROVIDER, or SECRETSPEC_REASON as typed
    configuration flags. Only an explicit --provider or --reason is 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 --profile is
    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,
    versioned secretspec2--{project}--{profile}--{key} layout. Distinct logical
    addresses such as my-app/prod/K and my/app-prod/K can no longer collide
    on one stored secret. When the new id holds no value, reads fall back to the
    matching 0.19 secretspec-{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, so secretspec 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 a ref.
    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. Explicit ref addresses
    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 ambient bws configuration.
    (#359)

  • Node SDK processes using loadAsync() or reportAsync() 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 awssm and scaleway providers now treat a JSON null in a ref field
    as no value, the same as an absent key, so the provider chain continues.
    Previously it was rendered as the four-character string null, which
    satisfied a required secret and reached the program as a password or token
    spelled n-u-l-l. The bw and dashlane providers already behaved this way.
    An extract pointer is unchanged: it names one location and still reports a
    null there, and the two policies now sit next to each other in one place.

  • The Python and Ruby SDKs' Resolved.close()/Resolved#close now remove every
    as_path temp 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
    outcome close exists to prevent. This matches the Go SDK's firstErr and the
    .NET SDK's firstError. The Ruby SDK also no longer skips a dangling symlink,
    which File.exist? reports as absent.

  • The awssm provider now accepts a trailing slash in ?prefix= without
    inserting a second slash into the AWS secret name. For example,
    ?prefix=myteam/ resolves to myteam/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-source now 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
    ref names 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
    ref still needs ?env=, so it resolves the same way whichever profile is
    running. (#338)

  • secretspec set against 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 | sh

Download 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