github cachix/secretspec v0.21.0
0.21.0 - 2026-09-22

latest release: secretspec-go/v0.21.0
3 hours ago

Release Notes

Changed

  • Cached values are written in cache envelope v4 so binary values survive
    caching (0.21+). Entries written by earlier releases stay readable, but
    SecretSpec 0.20 and earlier cannot read v4 entries: they warn, read the
    authoritative provider instead, and never replace or clear those entries.
    Do not share one cache store between 0.20 and 0.21.

  • Command generators store their output exactly, including a trailing newline
    (0.21+). A generator such as command = "openssl rand -hex 32" now produces
    a value ending in \n; use printf, tr -d '\n', or an equivalent to omit
    it. Previously generated values are unaffected. Output that is empty or only
    Unicode whitespace is still rejected.

  • secretspec get writes the exact value without a trailing newline when
    stdout is a pipe or file; a terminal still gets one (0.21+). $(...)
    substitution is unaffected.

  • The Rust API is byte-native (0.21+). Secrets::set takes SecretBytes:
    replace set(name, Some(value)) with set_text(name, &value) and
    set(name, None) with prompt_and_set(name). The Provider trait's get,
    set, and get_many use SecretBytes, Provider::name returns &str, and
    generator::generate returns bytes.

Added

  • Versioned local IPC (0.21+). secretspec serve runs a private stdio
    resolver that answers the Secret Resolution Protocol, and out-of-tree
    provider endpoints can be installed and discovered as trusted external
    providers. Highlights:

    • Independent clients: the Rust secretspec-ipc crate (async, plus a
      blocking feature that needs no async runtime, and SSH or existing
      authenticated streams for remote connections) and the pure C
      libsecretspec-resolver. Both enforce the same framing limits, request
      deadlines (clamped to 300 seconds), and error kinds, and report an endpoint
      that writes non-protocol text to stdout by name.
    • Exact-name resolution with inline values or resolver-owned file leases,
      binary values, secret expiry (expires_at_unix_ms) kept separate from
      cache freshness (refresh_at_unix_ms), and optional value-bound revision
      tokens for downstream cache invalidation.
    • Optional resolver.set and resolver.delete methods store or remove a
      declared secret on the route the session reads from, bounded by the active
      scope. secretspec serve --read-only advertises resolution only and
      refuses any resolution that would write, including generation and
      prompting.
    • prompt = true declarations resolve through a client.prompt callback to
      the launching process when it says it can answer; headless clients are
      never asked. The C client exposes this without callbacks through
      SECRETSPEC_RESOLVER_ANSWER_PROMPTS.
    • External providers request URI-specific credentials at runtime.
      config provider login and secretspec set prompt for missing ones and
      store answers in the configured credential source or a system keyring
      namespace private to the provider URI. Endpoints receive only a base
      environment plus the variables their discovery claim declares in
      environment, never other providers' tokens.
    • Endpoints answer rpc.discover with a self-contained OpenRPC description.
      Sessions carry declared project context, an optional requested
      authorization lifetime, and opaque interaction references that are kept in
      the audit log for approval surfaces.
    • Provider discovery and lease files are isolated with Unix permissions and
      Windows ACLs.

    Building libsecretspec-resolver from source needs a system yyjson
    (pkg-config for Meson, find_package(yyjson CONFIG) for CMake); static
    consumers add -lyyjson, which secretspec-resolver.pc records as
    Requires.private.

  • Tailscale Setec provider (setec://, 0.21+): store, retrieve, discover,
    and delete secrets, including binary values, through a tailnet-authenticated
    Setec server, with reads pinned to a Setec version.

  • Doppler provider (doppler://PROJECT[/CONFIG], 0.21+): read, write, and
    delete secrets over Doppler's REST API, authenticated with DOPPLER_TOKEN or
    the token provider credential. Secret names are stored verbatim in the
    Doppler config named by the SecretSpec profile, or in a config pinned in the
    URI, so they stay readable through doppler run and the Doppler dashboard.
    Batch reads fetch only the declared names, init --from discovers names
    without reading values, and values assembled from ${...} references arrive
    resolved. Doppler's reserved names, and names or values Doppler cannot store
    unchanged, are refused rather than rewritten. Configs are read concurrently,
    a manifest too large to name in one request URI is split across several, and
    a rate-limited or failed request is retried after Doppler's suggested wait.
    A restricted secret Doppler will not serve to a personal or CLI token is
    reported as the refusal it is, and is not deleted either. Projects and configs
    must already exist. A cached alias whose cache and source name the same
    Doppler secret under the active profile is refused.

  • Secret values are byte-native (0.21+). Values flow through providers,
    fallback chains, imports, and the cache as arbitrary bytes.
    secretspec set --from-file accepts exact byte input, as_path preserves it
    byte-for-byte, and Rust callers can use resolve_bytes() and
    resolve_named_bytes(). The file, systemd credential, environment, keyring,
    Google Secret Manager, Kubernetes, and Scaleway providers preserve binary
    values, and AWS Secrets Manager supports SecretBinary. run passes
    non-UTF-8 values on Unix and rejects NULs before starting the child. Text-only
    consumers (SDK text responses, exports, SOPS, LastPass) return explicit UTF-8
    errors that name the affected secret. Provider credentials retain their bytes,
    and unusable explicit credentials, including empty values, never silently
    select an environment fallback with another identity.

  • JSON Schemas for secretspec.toml and user config.toml provide editor
    autocomplete, hover descriptions, and structural validation. Export schemas
    matching the installed CLI with secretspec schema --config project or
    secretspec schema --config global (0.21+).

  • Projects can select a default provider chain with [defaults].providers
    (0.21+). Secret and profile provider chains retain precedence, and the
    project default can name a user-global alias whose ref template expands
    {project}, {profile}, and {key} for the active project. Native SDK
    inline declarations expose the same setting through inline schema v2; v1
    declarations remain accepted.

  • Claude Code can retrieve Anthropic API and LLM gateway credentials from any
    SecretSpec provider through its native apiKeyHelper. secretspec claude configure and unconfigure safely manage repository or user settings,
    including worktrees and CLAUDE_CONFIG_DIR, without replacing unrelated
    helpers. login and logout manage credentials isolated by settings scope
    and API resource; custom manifests remain available through --file (0.21+).

  • Bitwarden Password Manager references accept an exact item UUID for reads and
    writes, so one of several same-named items can be addressed without renaming
    it (0.21+). Imports that would target one item field twice are rejected
    before writing.

  • OpenPGP and OpenSSH private keys can be generated with type = "openpgp_private_key" (with an explicit generate.user_id) and type = "ssh_private_key" (0.21+). OpenPGP keys use signing, encryption, or combined
    capability profiles; both default to Ed25519/Curve25519, with configurable
    RSA available for compatibility.

  • The JVM SDK supports inline secret declarations through withInlineSpec
    (0.21+).

Fixed

  • HTTP providers (Vault, OpenBao, Infisical, Cloudflare, Scaleway, Azure App
    Configuration, Doppler, and Setec) now use a 10 second connect timeout and a
    60 second request timeout, so a stalled connection fails instead of hanging
    run or check. Vault and OpenBao retries on timeout now take effect.

  • On macOS, the keyring provider no longer prompts for the login keychain
    password on every run after SecretSpec is upgraded. Keychain items are bound
    to the code signature of the build that created them, which changes with
    every release of an ad hoc signed build (Nix, Homebrew, cargo install).
    The first read of each secret by a new build may prompt once; approve it with
    "Always Allow" so later runs of that build stay silent. SecretSpec explains
    how to avoid repeated prompts, leaves items untouched during reads, and
    retries in-place writes after requesting access when an older build created
    the item (0.21+).

  • macOS keyring reads preserve existing items and their access settings even
    when approval or a later write fails. Keychain lookups no longer change the
    prompt setting for other operations in the same process (0.21+).

  • KeePass KDBX 4.0 databases can be written after creation or editing in
    KeePassXC. Writes upgrade the file format to KDBX 4.1 while preserving
    encryption and key-derivation settings.

  • Bitwarden Password Manager resolves a batch of secrets with a single bw list items instead of one listing per secret. Single reads whose
    bw list items --search prefilter returns only similarly named items now
    fall back to the full listing, so they find the same items as batch reads
    and writes.

  • pass, gopass, and LastPass preserve whitespace and multiline secrets across
    generation and subsequent reads. pass entries are stored newline terminated
    like the pass CLI writes them, and exactly one final newline is removed on
    read, so entries created with pass insert resolve to their password.
    gopass keeps storing single-line values as plain text entries that gopass show and earlier releases read, and stores multiline, whitespace-padded, or
    non-UTF-8 values in its lossless binary-entry format; existing text entries
    still return only their trimmed first line until they are written again.
    LastPass's CLI newline handling no longer changes stored values.

  • LastPass rejects NUL-containing values before writing instead of silently
    truncating them; use a manifest encoding such as base64 to store these values.

Install secretspec 0.21.0

Install prebuilt binaries via shell script

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/cachix/secretspec/releases/download/v0.21.0/secretspec-installer.sh | sh

Download secretspec 0.21.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

Don't miss a new secretspec release

NewReleases is sending notifications on new releases.