Skip to content

Latest commit

 

History

History
1859 lines (1551 loc) · 102 KB

File metadata and controls

1859 lines (1551 loc) · 102 KB

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Fixed

  • The published secretspec crate includes every fixture its tests need, so its test suite runs from the crates.io source without the repository.
  • The C resolver client reports why opening a session failed when the endpoint writes non-protocol text before its first response, instead of sometimes returning UNAVAILABLE without an error message.

[0.21.0] - 2026-09-22

SecretSpec 0.21 lets other programs request only the secrets they need, lets providers run outside SecretSpec, and preserves binary values from storage to the consuming application. It also adds Doppler and Tailscale Setec providers, project-wide provider defaults, and a Claude Code credential integration.

Changed

  • Cached values use a new format to preserve binary secrets. Existing cache entries remain readable by 0.21. SecretSpec 0.20 and earlier cannot read new entries; they fall back to the original provider and leave those entries untouched. Avoid sharing a cache store between 0.20 and 0.21 clients.

  • Command-generated values now include all stdout bytes, including spaces and a final newline. For example, openssl rand -hex 32 produces a value ending in \n; trim it in the command if that is unwanted. Existing stored values do not change. Empty or whitespace-only output is still rejected.

  • Piped secretspec set input remains trimmed text. Use --from-file - when whitespace or non-UTF-8 bytes must be preserved from stdin.

  • secretspec get no longer adds a newline when writing to a pipe or file. This makes redirects preserve the exact value. Terminal output still ends in a newline; shell command substitution behaves as before.

  • Rust secrets are byte values. Secrets::set now takes SecretBytes. Replace set(name, Some(value)) with set_text(name, &value) for text and set(name, None) with prompt_and_set(name). Custom providers must update their get, set, and get_many implementations to use SecretBytes; Provider::name now returns &str, and generator::generate returns bytes.

  • Building the new C resolver client requires system yyjson. Meson finds it through pkg-config and CMake through find_package(yyjson CONFIG). Static consumers must link -lyyjson; secretspec-resolver.pc records the dependency.

Added

  • Resolve secrets from another process. secretspec serve exposes a private, versioned stdio protocol so an application can request one declared secret without loading the whole manifest into its own process. It can return a value or a temporary file that is cleaned up when the session ends. Clients can also answer prompts, store or delete declared secrets on the same provider route, and receive expiry and revision information for their own caches. secretspec serve --read-only refuses operations that would write, including generation and prompting when they would store a value. Applications can use the Rust secretspec-ipc crate (async or blocking), the C libsecretspec-resolver library, or the protocol directly. Rust clients can also connect over SSH or an existing authenticated stream.

  • Install providers outside the SecretSpec release cycle. Trusted external executables can register as providers and serve a versioned provider protocol. They can describe their capabilities, request credentials for their own URI, and return an interaction reference that appears in the audit log for an approval flow. secretspec config provider login and secretspec set can collect missing credentials. SecretSpec passes each endpoint only the base environment plus the variables it declared, keeping other providers' tokens separate.

  • Keep binary secrets intact. Providers, fallback chains, imports, and the cache now preserve arbitrary bytes. Use secretspec set NAME --from-file FILE (or --from-file - for stdin) to store exact bytes, and as_path = true to pass them to an application through a temporary file. The file, systemd credential, environment, keyring, Google Secret Manager, Kubernetes, and Scaleway providers support binary values; AWS Secrets Manager also supports SecretBinary. Rust callers can use resolve_bytes() and resolve_named_bytes(). Text-only outputs report a UTF-8 error naming the secret. On Unix, run passes non-UTF-8 values to child processes; it rejects NUL bytes before launching them.

  • Doppler provider (doppler://PROJECT[/CONFIG]): read, write, delete, and discover secrets in an existing Doppler project. It keeps names unchanged so secrets remain usable in the Doppler dashboard and doppler run. The active SecretSpec profile selects the Doppler config unless the URI pins one. Authenticate with DOPPLER_TOKEN or a token provider credential. Doppler restrictions and values it cannot store unchanged are reported as errors.

  • Tailscale Setec provider (setec://): read, write, delete, and discover secrets through a tailnet-authenticated Setec server. It supports binary values and reads pinned to a Setec version.

  • Set a project-wide provider chain with [defaults].providers. Secrets without their own or a profile-level chain use this default, so a checked-in manifest can refer to an alias that each developer maps to their preferred store. User-global alias ref templates can expand {project}, {profile}, and {key}. Native SDK inline declarations support this through schema v2; v1 declarations still work.

  • Use SecretSpec credentials with Claude Code. secretspec claude configure installs an apiKeyHelper that retrieves Anthropic API or gateway credentials from any provider. login and logout manage them by settings scope and API resource; unconfigure removes the managed helper without replacing unrelated settings. Repository, user, worktree, and CLAUDE_CONFIG_DIR settings are supported.

  • Get editor help for configuration. Export JSON Schemas for secretspec.toml and user config.toml with secretspec schema --config project or secretspec schema --config global for autocomplete, field descriptions, and validation.

  • Generate OpenPGP and OpenSSH private keys. Use type = "openpgp_private_key" with generate.user_id, or type = "ssh_private_key". Both generate modern keys by default and offer RSA for compatibility; OpenPGP keys can be limited to signing or encryption.

  • Target a specific Bitwarden item by UUID when several items share a name. Reads and writes accept the UUID, and imports reject duplicate item fields before making changes.

  • Declare JVM SDK secrets inline with withInlineSpec instead of requiring a manifest file.

Fixed

  • Stalled HTTP providers fail in bounded time. Vault, OpenBao, Infisical, Cloudflare, Scaleway, Azure App Configuration, Doppler, and Setec use a 10-second connection timeout and a 60-second request timeout. Vault and OpenBao now retry timed-out requests as configured.

  • macOS keyring access no longer asks for the login keychain password on every run after an upgrade. A new build may ask once per existing item; choose "Always Allow" to keep later runs of that build quiet. Reads preserve the item and its access settings, even if access is denied.

  • KeePassXC KDBX 4.0 files can be written. SecretSpec upgrades them to KDBX 4.1 on write while preserving their encryption and key-derivation settings.

  • Bitwarden reads are faster and more consistent. A batch uses one vault listing, and a single read falls back to the full listing if Bitwarden's search returns only similarly named items.

  • pass, gopass, and LastPass handle whitespace and multiline values more reliably. The pass provider removes one final newline added by the pass CLI on read. Existing gopass text entries still return their trimmed first line until rewritten; new values requiring exact bytes use its binary-entry format. Ordinary single-line gopass values remain readable by older tools. LastPass rejects NUL bytes before writing rather than silently truncating them.

  • Cache planning avoids unnecessary provider reads and refuses a cache that would point to the same physical secret as its source under the active profile, preventing an overwrite or deletion of the source value.

[0.20.0] - 2026-08-31

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.

[0.19.1] - 2026-08-12

Republishes 0.19.0's command-line artifacts. The library and CLI behave exactly as in 0.19.0.

Added

  • Windows ARM64 CLI release artifacts (aarch64-pc-windows-msvc), attached to the GitHub Release as secretspec-aarch64-pc-windows-msvc.zip with a checksum. The static installer keeps selecting the x86_64 build on Windows ARM64, which runs under emulation, so download the archive directly for a native binary.

Fixed

  • The 0.19.0 GitHub Release shipped without its CLI archives, its installer, and the Swift XCFramework, so curl https://install.secretspec.dev | sh and swift package resolution of 0.19.0 both failed. Every language registry (crates.io, PyPI, npm, RubyGems, Hackage, NuGet) published 0.19.0 normally and is unaffected. Install 0.19.1 instead; SwiftPM version ranges resolve to it automatically.

[0.19.0] - 2026-08-10

Changed

  • A provider URI may no longer carry a credential. A URI with a password (scheme://user:PASSWORD@host) is rejected, and onepassword+token:// no longer accepts the service account token in its userinfo (onepassword+token://token@vault). A URI is committed to secretspec.toml, echoed into shell history, and printed by CI, so a credential written there is already disclosed and redacting it at the terminal cannot retract it. Keep the scheme and supply the credential through a provider credential (secretspec config provider login <alias>, or credentials = { ... } on the alias) or the provider's environment variable; the errors name both. An unparseable provider specification is now also redacted before it is reported.
  • secretspec get resolves through the same path as the SDK's resolve_named, so a single-secret read makes exactly the decisions batch resolution makes. It continues to read the whole profile regardless of an active scope, and audits the coordinates it actually reached.
  • 1Password field references now resolve in one batched CLI call, reducing repeated unlocks and process startup when loading multiple secrets. If a missing reference requires individual reads, those reads remain bounded and concurrent.
  • The Rust SDK's ProviderAlias now provides leaf, credentials, and credentials_mut helpers so callers can construct and inspect leaf or inline-cached aliases without depending on their storage representation.

Added

  • The Rust SDK can resolve a single secret with Secrets::resolve_named, which reads only that secret and the inputs it composes from. An unrelated missing required secret no longer fails the call, and the result distinguishes an undeclared name (including one the active scope hides) from a declared secret with no value, reporting whether that value was required.
  • Secrets::with_default_reason sets a session reason only when none is already in effect, so an embedding application can describe itself without discarding the reason its own caller supplied through with_reason or SECRETSPEC_REASON.
  • Secrets can set prompt = true to request a hidden value from the controlling terminal when secretspec run finds no stored value. Writable providers save the answer for later runs; the null provider keeps it invocation-only.
  • Profiles can opt out of inheriting [profiles.default] by setting inherit = false in their profile defaults (0.19+), allowing standalone secret sets alongside profiles that still share the default declarations.
  • Passbolt provider (passbolt://): store and read secrets in a self-hosted Passbolt server through the community-maintained go-passbolt-cli, with convention-based names, references to existing resources, and credentials supplied by the CLI configuration or SecretSpec provider environment variables.
  • Provider aliases can define native ref templates and secrets can override coordinates per leaf alias with refs, so fallback providers and import sources/destinations resolve independently. import --delete-source now preflights the whole migration, verifies all writes before cleanup, and can safely move between distinct entries in the same physical store.
  • A null provider lets non-sensitive, version-controlled environment values use their manifest defaults and lets generated secrets stay ephemeral, with a fresh value returned for each resolution and nothing written to provider storage.
  • secretspec set and interactive secretspec check now preview the resolved write destination before reading the value, including the exact file and selector for SOPS.
  • A file provider stores each secret as one plaintext UTF-8 file beneath an explicitly configured relative or absolute directory, with project/profile isolation and support for existing file-mounted secrets through ref.item.
  • Secrets can select values from stored JSON documents with RFC 6901 pointers using extract. Extraction composes with provider-native references and storage decoding; selected values are read-only so sibling document data is never overwritten or deleted.
  • Secrets can store values as standard Base64, URL-safe Base64, or hexadecimal using encoding; writes encode logical text and reads decode stored values, while as_path = true materializes arbitrary decoded bytes.
  • secretspec-ffi installs (via cargo cinstall) together with its C header and a secretspec_ffi.pc, so consumers can link it — statically or dynamically — without hand-written linker flags.
  • The Haskell SDK's new use-pkg-config cabal flag (cabal build -f use-pkg-config) resolves an installed static or shared library through pkg-config.
  • The Ruby SDK's native extension accepts a new --enable-pkg-config build flag (gem install secretspec -- --enable-pkg-config) that resolves an installed static or shared library through pkg-config.
  • The Go SDK has a new pkgconfig build tag (go build -tags pkgconfig) that links an installed static or shared library, so it also works for a go get dependency.
  • The Haskell SDK declares the archive's macOS system frameworks (SystemConfiguration, Security, CoreFoundation) in its cabal file, so GHC passes them to every link on macOS.
  • A single provider can now attach its cache directly to the same alias with uri and cache, avoiding a second wrapper alias while retaining provider credentials. Cached fallback routes remain available for multiple authoritative providers.

Fixed

  • Ruby gems for Apple silicon now use the generic arm64-darwin platform instead of including the build runner's Darwin version.
  • Windows shared secretspec-ffi installs now place the runtime DLL in the documented PREFIX/lib runtime library directory.
  • import warns when a literal source uses convention naming but a provider alias for the same storage container addresses active secrets differently through a ref template or scoped refs. Import output also retains the selected source alias, making alias-specific addressing visible without changing literal-provider semantics. (#312)
  • The error for a coordinate a provider does not support now points at refs.<alias> and alias ref templates as well as at removing the coordinate, so a field written for one store no longer has to be dropped to reach another store that organizes the secret differently. (#266)
  • The Proton Pass provider works with pass-cli 2.2.4 and later, which removed the pass-cli test subcommand the provider ran to check the session before every read and write. The check now tries pass-cli info and falls back to pass-cli test, so a single build works across pass-cli releases that disagree about which check exists. A pass-cli with neither is reported as incompatible with the SecretSpec release, instead of passing the CLI's usage text through as the error. (#279)
  • SOPS write-target previews consistently use canonical physical paths on macOS and Windows, matching the files used for writes.
  • Passbolt now updates UUID-addressed resources outside a configured folder, treats URI- and environment-selected forms of the same server as one import destination, rejects malformed provider query parameters, and avoids redundant resource listings during writes.
  • Cache entries now store their absolute expiration time, allowing SecretSpec to remove an expired entry whenever it encounters one, including at an address previously used by another project or profile. Changing max_age invalidates entries written under the previous policy. Fresh v2 entries remain usable during migration, while foreign v2 entries remain untouched. (#275)
  • run preserves non-UTF-8 environment values byte-for-byte when launching child processes on Unix.
  • Provider-scoped references now compare provider-defaulted coordinates before destructive imports, apply scoped address overrides before comparing stores, and recognize missing file destinations reached through symlinked parents. They also invalidate caches for every coordinate change without display-format collisions and retain the attempted native location in audit events when a provider read fails. Same-store import validation handles Windows provider paths without treating separators as TOML escapes.
  • Profile overrides can switch between legacy ref and provider-scoped refs without retaining both inherited address models and failing validation.
  • JSON extraction from file-backed documents now handles Windows store paths without treating path separators as TOML escapes.
  • SDK pkg-config setup now pins cargo-c's library and metadata install directories, so Go, Ruby, and Haskell reliably discover secretspec_ffi.pc across environments.
  • The keyring provider no longer intermittently fails with a "No default store has been set" error when resolving multiple secrets concurrently.
  • The SOPS provider no longer substitutes a second time into a rendered path segment, so a project or profile literally named {profile} or {project} resolves to the file you configured instead of a different one.
  • Invalid SOPS path templates are now rejected when loading serialized provider configurations instead of being accepted without validation.
  • The LastPass provider now reports its full item template rather than only the first segment. A multi-segment template such as lastpass://Shared/{project}/{profile}/{key} used to be reported as plain lastpass, which reads back as the default secretspec/{project}/{profile}/{key} template — a different folder — and lastpass://Work/TeamA/{key} read back as the literal item Work, one item for every secret. Templates that differ below their first segment are now distinguished, so repointing a cached route at a new template invalidates its cached values instead of serving the old ones until they expire. Single-segment templates are unaffected; cached entries for a multi-segment template refetch once, silently, on first run.
  • Provider fallback chains now reuse each provider and resolve independent primary misses concurrently. Azure Key Vault providers also reuse their client and serialize its initial challenge-based authentication, so chains such as providers = ["keyring", "akv"] no longer fetch every fallback in series or launch separate Azure CLI processes for the same resolution.
  • Reusing a Secrets instance now refreshes fallback providers for each resolution, so provider-side caches observe rotated values and providers use the latest reason supplied with with_reason.

[0.18.0] - 2026-08-03

Changed

  • The keyring provider now uses keyring 4's Rust-native Secret Service transport on Linux, so source builds and binaries no longer require system libdbus.
  • secretspec init --from now accepts every provider with declaration reflection, including age, AWS Parameter Store, and Bitwarden Password Manager, and accepts --project and --profile as explicit discovery context for hierarchical stores.
  • Custom Rust providers now pass discovery context to the Provider::reflect hook so hierarchical stores can select the project and profile namespace.

Fixed

  • The Bitwarden provider now treats a locked vault or a missing session as a clear authentication failure on get/set, with the same "run bw login and bw unlock, then set BW_SESSION" guidance in both cases, instead of surfacing the underlying CLI error text.
  • The Bitwarden provider now reports a missing bw CLI with install instructions instead of an authentication error: a machine without the CLI is not an authentication state, and the install guidance ("…run 'bw login' and 'bw unlock' to authenticate") used to match the not-authenticated classifier and mask the real problem.
  • Vault and OpenBao JWT authentication now allows the role to be omitted when the auth mount has a server-configured default_role, while explicit URI or environment roles continue to take precedence.
  • Vault and OpenBao AppRole authentication now supports roles configured with bind_secret_id=false by omitting secret_id from the login request when no SecretID credential is configured.
  • secretspec import --delete-source now compares resolved storage entries without conflating distinct cache address spaces, preventing equivalent provider configurations (including dotenv path aliases) from deleting the destination value. Sources without deletion support are also rejected before any destination is written.
  • The AWS Secrets Manager provider now authenticates with shared credentials file profiles backed by an active AWS login session, which previously failed because the required AWS SDK feature was not enabled. BatchGetSecretValue failures also report the full service error instead of a shortened message.

Added

  • The dotenv provider accepts a leading ~ in custom paths, such as dotenv:~/.config/my-project/.env, and resolves it to the user's home directory.
  • Vault and OpenBao AppRole and JWT authentication can target non-default auth method mounts, including printable Unicode mount names, with the auth_mount provider URI option.
  • secretspec add NAME --description "..." (available in 0.18) adds a secret declaration to the active profile while preserving the manifest's existing comments, formatting, and unrelated configuration.
  • AWS Parameter Store convention templates and bounded GetParametersByPath discovery can create declarations from the direct children of an existing hierarchy without decrypting their values.
  • Swift SDK (available in 0.18) for resolving SecretSpec manifests from macOS 12+ on Intel and Apple silicon. The SwiftPM package provides fluent and one-shot resolution, typed failures, scopes, value-free reports, provenance, environment export, codegen input, and deterministic as_path cleanup. Its checksummed XCFramework includes the shared Rust resolver, so applications do not need a Rust toolchain or separately installed native library.
  • secretspec delete removes one or more stored secret values without changing their manifest declarations, while --all requires explicit confirmation. secretspec import --delete-source verifies each destination value before deleting its source, and retains the source when an existing target differs.
  • Bitwarden Password Manager provider (bw://, bw build feature) for reading and writing secrets in a personal or organization vault through the bw CLI. Collections and organizations are addressed by name or by id (bw://myorg@dev-secrets), ?type= and ?field= select an item type and field, and ?server= asserts which self-hosted server the configuration expects. Every item type is supported (login, secure note, card, identity, SSH key), each with a default field shared by reads and writes. Item names are matched in full and case-insensitively, and an ambiguous name is refused with the colliding ids rather than resolved to an arbitrary item.
  • Dashlane provider (dashlane://) for reading secrets from a Dashlane vault through the dcli CLI. Convention secrets read the item titled secretspec/{project}/{profile}/{key}, and a ref names an existing item by title or identifier with an optional field. dashlane://note, dashlane://secret, or dashlane://password restrict the search to one content type. The provider is read-only, because dcli has no command that creates or edits a vault item; secretspec set fails with that reason. Non-interactive use is supported through DASHLANE_SERVICE_DEVICE_KEYS, which can also be injected as the service_device_keys provider credential. Injected credentials read through a private, owner-only dcli state directory of their own, because dcli otherwise prefers a device already registered on the machine and reads that identity's vault instead.
  • Keeper Secrets Manager provider (keeper://FOLDER_UID, keeper build feature) using Keeper's official Rust SDK, with convention-based records, references to existing records and fields, provider credentials, batch reads, writes, and cache-compatible deletion. SDK calls are safe from async Rust applications, and updates preserve the JSON types of Keeper fields such as dates, checkboxes, hosts, and names.
  • AWS Systems Manager Parameter Store provider (awsps://, awsps build feature) for reading and writing KMS-encrypted SecureString parameters. It supports AWS profiles and regions, an optional hierarchy prefix, customer-managed KMS keys, parameter tiers, batched reads, and references by parameter name, version, label, or ARN. Unversioned parameter-name references can be written in place; version-, label-, and ARN-pinned references are read-only. Writes reject unsupported reference coordinates before requesting a value, and versioned ARN errors point to writable parameter-name references. AWS service errors include their error codes and messages instead of only unhandled error. (#209)

[0.17.1] - 2026-08-01

Fixed

  • Vault and OpenBao AppRole and JWT authentication methods now reuse login tokens within each provider operation up to each token's reported use and lease limits, including time spent completing authentication, avoiding repeated logins without exhausting or outliving tokens during batches, writes, or deletes. Invalid batch addresses are rejected before login, and concurrent requests remain safe across Tokio runtime flavors while keeping pooled HTTP connections alive for the full operation.
  • Cached provider routes now recognize Vault and OpenBao configurations that address the same endpoint, namespace, and mount as one store, even when they use different provider names or authentication methods, so a cache cannot target its own authoritative source.
  • Provider and SDK errors now retain underlying causes such as authentication, timeout, DNS, TLS, connection, and response-parsing failures. AWS Secrets Manager errors also report AWS error codes and messages instead of only unhandled error.
  • Prebuilt Linux Go SDK and secretspec-ffi libraries now include libdbus instead of requiring the build host's libdbus-1.so.3, so they load on NixOS and other systems without a matching system library. (#214)
  • The dotenv provider's "cannot store" error now tells you to rename the secret in secretspec.toml when the name came from a manifest declaration, instead of always pointing at a ref item the config may not contain.
  • Typed loaders generated by secretspec-derive now keep temporary files for as_path secrets alive until the returned resolved secrets are dropped.

[0.17.0] - 2026-07-26

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::Client per provider instance (same OnceLock pattern as Infisical) instead of building a fresh client on every get/set/login. Concurrent get_many of 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 (default Provider::get_many) caps concurrent unique-address fetches at 8 by default, overridable with SECRETSPEC_PROVIDER_CONCURRENCY. Waves replace a single unbounded thread::scope fan-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://, sops build 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://, scaleway build feature) for storing secrets in Scaleway's Secret Manager over its v1beta1 REST API. Authenticates with an API secret key (secret_key credential or SCW_SECRET_KEY), targets a region (URI host or SCW_DEFAULT_REGION, default fr-par) and project (?project_id= or SCW_DEFAULT_PROJECT_ID), and stores convention secrets under the folder path secretspec/{project}/{profile} with the key as the secret name. Native ref references may select a JSON key with field and a revision with version, and are read-only.

  • Cached provider aliases with ordered authoritative fallback routes, configurable local cache freshness, cache-first reads, automatic refresh after reads and writes, and secretspec cache clear invalidation.

    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 a providers list. All three are reported when the route is planned, and an unusable max_age when 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 by cache clear rather 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 --provider all 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_age is applied server-side — Vault and OpenBao set the KV v2 path's delete_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 clear reports 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 as cache_refresh rather than set. (#199)

  • secretspec config global init --provider <PROVIDER> --profile <PROFILE> can save explicitly user-global defaults without interactive prompts, including --profile none to clear the default profile. The global 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, and export take --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's required/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 empty provider). A scope's own list must name at least one secret, with no blank or repeated entries. run --scope removes 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 --scope emits the scoped subset but unsets nothing, since no output format can express an unset. set, like import, ignores an ambient SECRETSPEC_SCOPE entirely, and a blank --scope (or a blank SECRETSPEC_SCOPE) clears an inherited scope instead of deferring to it. Under project extends, a child scope replaces the parent scope of the same name outright rather than unioning their secret lists. Typed SDK loaders ignore an ambient SECRETSPEC_SCOPE, since a generated struct always expects the full profile; import likewise 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 scoped check, run, and export operations 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:, kdbx build 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 required field accepts at_least_one and exactly_one group tables, supporting overlapping alternative and mutually exclusive credentials across check, run, and SDK resolution.

  • OpenBao provider (openbao://, openbao build feature) with its own provider identity, documentation, and OpenBao CLI configuration through BAO_ADDR, BAO_NAMESPACE, BAO_TOKEN, and BAO_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 using VAULT_JWT, or requests a short-lived OIDC token automatically in GitHub Actions and Forgejo Actions jobs with id-token: write. The role and optional audience can be set in the provider URI or with VAULT_JWT_ROLE and VAULT_JWT_AUDIENCE.

  • The Python SDK now publishes a Windows x64 wheel to PyPI, so pip install secretspec and uv add secretspec work on Windows. (#177)

  • The Ruby SDK now publishes a Windows gem (x64-mingw-ucrt) to RubyGems, so gem install secretspec works 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 official bws CLI 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 check and import results 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 a ref item 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.

[0.16.0] - 2026-07-17

Added

  • Composed secrets derive read-only values such as connection strings from other declared secrets using strict ${UPPERCASE_NAME} templates; names must match [A-Z][A-Z0-9_]*, $$ produces a literal dollar sign, and ordinary braces remain literal. Dependencies are order-independent, may include other compositions, and are validated for unknown references and cycles before provider access; unlike dotenv expansion, values are substituted once without ambient environment lookup, fallback operators, recursive expansion, or silent empty replacements.
  • C# SDK (Cachix.SecretSpec, available in 0.16): resolve secrets from .NET through the shared native resolver, with fluent builder and one-shot APIs, typed failure exceptions, value-free preflight reports, provenance, environment export, typed-codegen input, and deterministic cleanup of as_path files. The trimming-safe, NativeAOT-compatible NuGet package includes native resolver builds for glibc and musl Linux x64/Arm64, macOS x64/Arm64, and Windows x64/Arm64; Windows applications do not need a separate Visual C++ Redistributable.
  • Infisical provider (infisical://), for Infisical Cloud and self-hosted instances. Authenticates as a machine identity via Universal Auth, whose client_id and client_secret can be sourced as provider credentials (with INFISICAL_CLIENT_ID/INFISICAL_CLIENT_SECRET fallbacks), or with a ready-made token/INFISICAL_TOKEN. A profile names the Infisical environment, so a production profile reads the production environment; projects whose environments do not correspond to profiles pin one with ?env=, and profiles stay separate either way. Secrets live at /secretspec/{project}/{profile} (?path= overrides the prefix), with keys stored verbatim, and secrets sharing a folder are fetched in one request. A folder's imported secrets resolve too, with Infisical's own precedence. A secret's ref can name an Infisical secret by folder, key and version. Self-hosted and EU instances are named by the URI host, INFISICAL_DOMAIN, or Infisical's legacy INFISICAL_API_URL. Provider selection and Rust API documentation identify Infisical as available from SecretSpec 0.16.

[0.15.0] - 2026-07-16

Added

  • Gopass provider (gopass://) for GPG-based password manager with git-synced password store.

  • secretspec export command that resolves every secret for the active profile and writes them to stdout without running a command, in a chosen --format: shell (export KEY='value', for eval "$(secretspec export)"), dotenv, json, or gha (appends to $GITHUB_ENV and emits ::add-mask:: for each value). Unlike run it never prompts and exits non-zero on a missing required secret, so CI can gate on it.

  • Azure Key Vault provider (akv://). Authenticates via a service principal whose tenant_id, client_id, and client_secret can be sourced as provider credentials (with AZURE_TENANT_ID/AZURE_CLIENT_ID/AZURE_CLIENT_SECRET fallbacks), falling back to a signed-in Azure CLI / Azure Developer CLI session when none are available; managed identity and AKS workload identity are also available via ?auth=managed_identity and ?auth=workload_identity. Sovereign clouds can be addressed with a full DNS hostname or an explicit ?suffix= override. Project/profile/key components use lowercase, unpadded Base32 so case and punctuation remain distinct within Azure's restricted, case-insensitive secret-name namespace.

  • The awssm provider accepts kms_key_id and tag.NAME=VALUE query parameters (e.g. awssm://prod@us-east-1?kms_key_id=alias/my-key&tag.team=platform). Both are applied only when secretspec creates a secret, so accounts that enforce a customer-managed KMS key or "tag-on-create" guardrails (an SCP requiring aws:RequestTag/* on CreateSecret) can now store secrets. A pre-existing secret keeps the key and tags it was created with.

  • PHP SDK (cachix/secretspec): resolve secrets from PHP, Laravel, and Symfony over the same shared resolver as the other language SDKs. It ships as a native PHP extension that embeds the resolver (works under PHP-FPM with no ffi.enable, like ext-redis), with an ext-ffi fallback that dlopens the library at runtime for CLI and local development.

  • Provider aliases can now source their own credentials from another provider. An alias in [providers] may declare a credentials map binding a semantic, provider-specific name (such as access_token, token, role_id, or client_secret) to a source: a bare provider spec, which reads the value at the convention path, or a table with a ref giving the exact coordinates. The credential is fetched from that provider and handed to the store, so a machine token can live in the OS keyring instead of a plaintext environment variable, and is never written into the environment of processes started by secretspec run. A configured credential is authoritative; providers retain their conventional environment fallback when no explicit credential is supplied. Chains are limited to one hop, and that limit is enforced wherever the alias appears, as a chain fallback or the default provider included. Provider credentials also apply when the alias is selected with an explicit --provider <alias> or SECRETSPEC_PROVIDER, and they are fetched from their source once per invocation and profile, then reused across all secrets routed at the alias (convention-path credentials live under a profile, so switching profiles re-reads them). Each source read, and each credential stored through login, is audited with a credential marker naming the semantic credential and the source store; a credential stored through login takes effect immediately. Unsupported credential names fail validation before a source is accessed.

    [providers]
    bws = { uri = "bws://project-uuid", credentials = { access_token = "keyring" } }
    akv = { uri = "akv://myvault", credentials = { tenant_id = "keyring", client_id = "keyring", client_secret = "keyring" } }
    vault = { uri = "vault://kv/app?auth=approle", credentials = {
      role_id   = { provider = "onepassword", ref = { vault = "Infra", item = "approle", field = "role_id" } },
      secret_id = { provider = "onepassword", ref = { vault = "Infra", item = "approle", field = "secret_id" } },
    } }
  • secretspec config provider login <alias> prompts for each provider credential a provider alias declares and stores it in its source provider, so it can be read back on the next resolution. secretspec config provider add gains a repeatable --credential NAME=PROVIDER flag for declaring credential sources from the command line.

Changed

  • Rust SDK validation errors now store their detailed report out of line, reducing the size of SecretSpecError values while preserving diagnostics.
  • Generated types now describe the values resolution can actually return: omitted required still means required, secrets supplied by a manifest default or generator are non-nullable, and profile-specific types include secrets inherited from the default profile. Profile JSON Schemas are now exhaustive (additionalProperties: false) for the same reason.
  • A ref routed at a single store (an explicit --provider, a single-provider chain, or the default provider) is now checked up front, before any store is contacted, for coordinates that store cannot honor (e.g. a field ref pointed at a .env file), failing fast with a clear message instead of at fetch time. A ref on a multi-store fallback chain is still validated per store as the chain is walked, so a coordinate a later store cannot express never blocks a provider earlier in the chain that can.
  • Provider chains accept bare provider names and scheme:path shorthand (e.g. providers = ["keyring"]), the same specs --provider accepts. Previously a chain entry had to be a declared alias or a full scheme:// URI.
  • An explicitly empty providers = [] list now uses the default provider for get as well, matching how check and run already treated it.
  • A providers chain whose first entry misspells onepassword as 1password now fails up front with the corrective "use onepassword instead" message — the same hard error any other invalid primary gets — instead of warning and falling through to the rest of the chain. As a fallback entry it is still skipped with a warning, like any broken link.
  • Rust SDK: ProviderAlias::credentials is a plain map whose empty state means "no provider credentials", rather than an Option, so the two ways of spelling an alias without credentials cannot diverge.

Removed

  • The unused public Config::merge_with and Profile::merge_with methods. Configuration inheritance (extends) is now applied entirely through the internal overlay used by the loader, so these self-wins merge helpers no longer had any callers.

Fixed

  • Configuration inheritance now loads an extends hierarchy as a DAG. Shared ancestors in diamond-shaped graphs are applied once instead of being reported as cycles, later entries in extends correctly override earlier entries, and profile [defaults] are inherited across source files.
  • Runtime planning, semantic validation, Rust derive output, and JSON Schema generation now share one compiled effective-manifest model and one missing-value policy, preventing raw required/default interpretation from drifting between surfaces.
  • Profile overrides no longer need to repeat the secret's description: validation now checks each secret's effective, merged configuration, so a partial override like [profiles.development] DATABASE_URL = { default = "sqlite:///dev.db" } inherits the description (and type, for generate) from the default profile instead of failing with "missing description". The merged view is also validated for real conflicts, so a generate secret in the default profile combined with a default value from an override or a profile [defaults] table is now rejected at load instead of silently generating a random value and ignoring the default. Validation errors are reported deterministically, attributed to the profile that declares the offending field, and check and run list secrets in stable name-sorted order.
  • Provider fallback chains (providers = [...]) are now tried strictly in order: each link is resolved only when a read actually reaches it, and a broken link (an undefined alias, an unreachable store) is skipped with a warning so a working provider later in the chain still answers. check, run, and get all walk the chain the same way.
  • get and set now record an audit event when a secret's provider routing fails to resolve (for example an undefined alias), matching how check and run audit every attempted read.
  • A provider chain entry that misspells onepassword as 1password now gets the same "use onepassword instead" correction that --provider 1password gives, instead of a generic undefined-alias error.
  • Blank or whitespace-only profile and provider overrides (--profile, SECRETSPEC_PROFILE, --provider, SECRETSPEC_PROVIDER, and the Rust SDK builder) are now trimmed and treated as unset, so a padded value such as a trailing newline from $(cat file) can no longer select a nonexistent profile or provider.
  • import prints its per-secret summary in a stable, name-sorted order.
  • run no longer aborts when the environment contains a non-UTF-8 variable. Such variables are now passed through to the child process untouched, with resolved secrets overlaid on top.
  • The prebuilt Linux addons of the Node SDK are now built against glibc 2.28 (manylinux_2_28) with libdbus compiled in statically, so npm install secretspec works on Amazon Linux 2023, RHEL 8/9, and other distros with an older glibc, instead of the addon failing to load with "version `GLIBC_2.38' not found". (#136)

[0.14.0] - 2026-07-09

Added

  • ref: native secret references on secrets: a secret can name one externally managed secret by its store's own coordinates, instead of SecretSpec's {project}/{profile}/{key} naming:

    [profiles.production]
    DATABASE_URL = { description = "...", ref = { item = "db", field = "password" }, providers = ["prod_op"] }

    item is the store's own name for the secret (1Password item title, Vault KV path, AWS secret name or ARN, .env key, environment variable, ...); optional keys refine it where the store supports them: field (1Password field label, Vault KV field, AWS JSON key, keyring account), vault and section (1Password), and version (Google Secret Manager). Every provider resolves refs; coordinates a store has no equivalent for are rejected with a clear error rather than guessed at.

    The coordinates supply naming only — which store resolves them follows the same routing as every other secret (the secret's providers chain, the --provider/SECRETSPEC_PROVIDER override, or the default provider). That means refs compose with providers fallback chains, and an explicit override redirects them like any secret, e.g. at a .env fixtures file during tests. Writes are symmetric where the backend allows it: secretspec set and check prompting write through the coordinates in place (1Password op item edit, keyring, pass, dotenv, Bitwarden, Proton Pass, LastPass); Vault, AWS, and GCSM refs are read-only. Secrets sharing identical coordinates fetch once, and audit events record the coordinates in a new ref field. A ref also composes with generate: a missing referenced secret is minted and written straight to its coordinates.

  • Inline provider URIs in providers chains: chain entries that are already URIs (providers = ["onepassword://Production", "keyring"]) now pass through without declaring a [providers] alias first.

Changed

  • Faster multi-provider resolution: check, run, and SDK resolution now group secrets by store and fetch the groups concurrently instead of one after another; within a group, ref secrets batch through the store's bulk surface where it has one (AWS BatchGetSecretValue, the single Bitwarden, Proton Pass, and 1Password listings) and otherwise resolve concurrently, each unique coordinate fetched once. CLI authentication (1Password, LastPass, Proton Pass) is probed once per account/session instead of once per provider instance.
  • Provider trait speaks one address vocabulary (affects custom providers built on the Rust library): each provider now compiles SecretSpec's {project}/{profile}/{key} convention into its native coordinates via a new required convention_address method, and reads resolve every address through the same coordinate path a ref uses. The convention-only get_batch method is replaced by get_many, which takes addresses and so batches ref secrets too. A provider declares the ref coordinates it honors with supported_coords and the rest are rejected for it, and allows_set is replaced by check_writable, which returns the reason a write is refused rather than a bare false.
  • Manifest validation runs on load: the semantic rules secretspec.toml documents (a required secret cannot carry a default, generate needs a type, ref coordinates must be non-empty and non-whitespace) are now enforced whenever the config is loaded. Configs that silently violated them previously will now fail with a pointed error.

Fixed

  • onepassword: URIs carrying an item path (e.g. the onepassword://vault/Production form some older docs showed) previously discarded the path silently and targeted a vault literally named vault. Item paths — including pasted op://vault/item/field references — now fail with an error spelling out the exact ref coordinates to write instead.
  • set on a read-only ref reported "Provider '' is read-only and does not support setting values", which is untrue of Vault, AWS, and GCSM — they write the conventional layout fine and refuse only refs. The store's own reason is now shown (e.g. writing one Vault field would clobber the sibling fields at the same KV path).

[0.13.0] - 2026-07-03

Added

  • Language SDKs for Python, Go, Ruby, Node.js / TypeScript, and Haskell (secretspec-py, secretspec-go, secretspec-rb, secretspec-node, secretspec-hs). Resolve the secrets declared in your secretspec.toml from each language using the same providers, profiles, fallback chains, and generators as the CLI and the Rust SDK — no per-language configuration. Each mirrors the Rust derive crate's vocabulary: a builder taking a provider, profile, and access reason; load() returns the resolved secrets and can export them into the process environment, while a value-free report() previews how each secret would resolve without reading any value. A missing required secret raises a typed error; as_path secrets are returned as a readable file path, with an explicit (or scope-based) cleanup that removes the backing temp file.
  • secretspec-ffi crate: a small, versioned C ABI for resolving secrets from any language, plus the public Rust building blocks the SDKs are built on (Secrets::resolve() and Secrets::report()). Use it to write a binding for a language we do not ship yet.
  • secretspec schema: emits a JSON Schema for your manifest's typed shape (the union of all profiles, or one profile via --profile). Feed it to quicktype to generate idiomatic typed classes in any language, populated from each SDK's fields() map — type-safe secret access without hand-writing a generator per language.
  • secretspec check --json / --explain: a value-free report of how every declared secret resolves for the active profile — its status (resolved, missing_required, missing_optional), where the value would come from (a provider, with a credential-free URI; a generator; or a committed default), and whether it is exposed as_path. Values are never included, and both flags skip the interactive prompt and exit non-zero when a required secret is missing, so CI can gate on them. The same report is available to the Rust SDK via ValidatedSecrets::report() / ValidationErrors::report().

Fixed

  • A per-secret provider chain whose primary provider errors (e.g. an unreachable vault) and whose fallback chain yields no value now surfaces that provider error instead of silently reporting the secret as missing_required, so a provider outage is distinguishable from an unprovisioned secret.

[0.12.2] - 2026-06-22

Added

  • The pass provider accepts a store_dir query parameter (e.g. pass://?store_dir=/path/to/store) to use a password store directory other than the default ~/.password-store. It is applied as PASSWORD_STORE_DIR scoped to each pass invocation.

Fixed

  • Provider URIs now correctly round-trip query parameters whose values contain characters that are significant in a query string (&, +, #, %, and spaces). Previously such characters in the awssm prefix (and the new pass store_dir) were emitted unescaped, so the value could be silently truncated or altered when the URI was parsed back.
  • secretspec import <FROM> now accepts a provider alias (from [providers] or the global [defaults.providers]) as its source, not just a literal provider URI. Passing an unknown provider or alias now reports the available aliases.

[0.12.1] - 2026-06-15

Fixed

  • Windows: a dotenv:// provider URI built from an absolute path (e.g. dotenv://C:\path\.env) no longer fails to parse with "invalid port number". The drive-letter colon was being read as a host:port separator; such paths are now carried through the URL intact.
  • Windows: the audit log no longer fails to reset at its size cap. Truncation on the append-only handle was denied by the OS; it now truncates through a separate write handle.
  • Relative dotenv paths (e.g. dotenv:.config/.env) now resolve against the directory containing secretspec.toml instead of the current working directory. Running secretspec run --file ../secretspec.toml from a subdirectory previously failed to find the referenced .env file because it was looked up relative to the working directory rather than the project root (#59). Absolute dotenv paths are unaffected.
  • The protonpass provider now works with Proton Pass CLI pass-cli >= 2.0.3. The item list --output json payload changed shape in 2.0.3 (the item title moved from a nested content.title to a top-level title, and content was dropped from list output), which made secretspec report active secrets as missing. Both the old (<= 2.0.2) and new (>= 2.0.3) list shapes are now accepted. (#104)

[0.12.0] - 2026-06-08

Added

  • Audit logging for secret access, on by default. Every secret read and write, from both the CLI and the Rust SDK, is appended to a local per-user log as JSON Lines. Only metadata is recorded (secret names, the serving provider with any embedded credentials redacted, outcome, reason, and actor including a detected coding agent); secret values are never written. Each operation is recorded once: get and set per secret, check as a single event, run when the child process starts, and import per copied secret. Auditing never blocks secret access; if it cannot write the log it warns on stderr and continues. The log is a single file capped at 1 MiB. It is configured per machine via the [audit] table in ~/.config/secretspec/config.toml (not the project's secretspec.toml), so a cloned repository cannot redirect or silence it. The new secretspec audit command reads the log, with --project, --action, --tail/-n, and --json filters. See Audit Logging for details.
  • --reason CLI flag (and SECRETSPEC_REASON env var) records a human-readable reason for a session's secret access, forwarded to providers that support audit logging. SECRETSPEC_REASON is honored across the SDK/library too: it is resolved by Secrets::load/load_from (so secretspec-derive-generated code and other library callers can satisfy the require_reason policy and supply an audit reason without code changes), and Secrets::with_reason(...) sets it explicitly, taking precedence. The secretspec-derive-generated typed builder also gains a with_reason(...) method, so SDK callers can satisfy require_reason in code (not only via the env var). Blank or whitespace-only reasons are ignored so they cannot satisfy the policy. Backed by a new Provider::set_reason trait method (default no-op).
  • [project] require_reason policy in secretspec.toml, controlling when secret access must supply an explicit reason. Accepts "agents" (the default — require a reason only when an AI agent is detected), true (require it from every caller), or false (never). Agent detection is delegated to the detect-coding-agent crate (Claude Code, Cursor, Codex, Gemini CLI, Copilot, ...), plus a SECRETSPEC_AGENT opt-in for harnesses it does not recognize. Because the tool enforces it and it is checked into the repo, the policy applies uniformly and cannot be bypassed by an individual tool's configuration. An invalid require_reason value is rejected at config-parse time rather than silently falling back to the default. The policy is inherited through extends: a shared base config's require_reason applies to every config that extends it, unless the child sets its own. Note: the default "agents" means AI agents must now pass a reason out of the box.
  • bws provider now accepts an optional server base in the URI (bws://[server-base@]project-uuid) to target EU cloud or self hosted Bitwarden instances. When set, the identity and API endpoints are derived as https://<server-base>/identity and https://<server-base>/api; omitting it keeps the bitwarden.com US cloud default.

Changed

  • Minimum supported Rust version raised to 1.92 (required by the detect-coding-agent dependency). The devenv toolchain is pinned accordingly.

Fixed

  • Proton Pass provider now works with pass-cli >= 2.1.0 agent sessions. Since 2.1.0, audited item operations (item view, item create, item delete) fail unless PROTON_PASS_AGENT_REASON is set, which made existing secrets appear missing under an agent session. The provider now sets this variable on every pass-cli invocation. The reason is resolved as --reason/with_reason, then PROTON_PASS_AGENT_REASON, then a secretspec-versioned default (secretspec/<version> (https://secretspec.dev)); each source is normalized first, so a blank reason falls through to the next rather than masking it. It is ignored by older releases and non-agent sessions.
  • secretspec init now serializes the generated secretspec.toml with toml_edit instead of hand-interpolating strings. This fixes several cases that previously produced TOML that could not be parsed back: a project name, secret description, or default value containing a double-quote, backslash, control character (including U+007F), or newline; a secret name containing a dot (e.g. FOO.BAR, which dotenvy accepts and which silently collapsed to a nested key); and a configured project.extends, which was dropped entirely. Output is now also deterministically ordered.
  • secretspec init no longer defines a conflicting -f short flag for --from; -f is reserved for the global --file option. The duplicate short flag made secretspec init panic in debug builds and was ambiguous in release builds.

[0.11.0] - 2026-05-22

Added

  • AWS Secrets Manager (awssm) provider: support for a ?prefix= query parameter in the provider URI (e.g., awssm://us-east-1?prefix=myteam). The prefix is prepended to all secret names (myteam/secretspec/{project}/{profile}/{key}). Closes #92.
  • Provider aliases can now be declared at the project level in a top-level [providers] table of secretspec.toml. Aliases declared there are visible to per-secret providers = [...] lists and to --provider/SECRETSPEC_PROVIDER, and are merged with the existing user-level [defaults.providers] map in ~/.config/secretspec/config.toml. On name conflicts the project entry wins, so a team's checked-in mapping cannot be silently shadowed by a stale local config. Closes #79 and addresses the "share aliases via VCS" half of #90.

Fixed

  • Profile-not-found errors no longer surface as the confusing Secret 'Profile 'X' not found' not found. They now use the dedicated InvalidProfile variant and include the list of profiles defined in secretspec.toml, e.g. Invalid profile: 'production' is not defined in secretspec.toml. Available profiles: default, dev. Affects check, run, get, set, and import. Surfaced via #79.

[0.10.1] - 2026-05-11

Fixed

  • secretspec check: optional secrets that aren't set no longer render with a green ✓ and aren't counted as "found" in the trailing summary. They now display with the same blue ○ (optional) styling already used in the missing-required path, and the summary appends , N optional whenever optional secrets are absent (e.g. Summary: 4 found, 0 missing, 1 optional). If every optional secret is set, the summary line stays in its previous X found, Y missing form. Fixes #72.

[0.10.0] - 2026-05-11

Added

  • Proton Pass provider that stores secrets in a Proton Pass vault via the proton-pass CLI. Configured as protonpass://<vault>; items are organized per project / profile and read / write both go through the CLI.

Fixed

  • OnePassword provider: the auth preflight now probes op vault list instead of op whoami. Under the 1Password desktop app's delegated-session integration, op whoami reports account is not signed in even when op item get / op vault list work fine — so every secret read or write failed at preflight with a misleading "not signed in" error. op vault list exercises the actual access path and succeeds when the desktop app can serve secrets. Additionally, OP_SESSION_* environment variables (left over from eval $(op signin)) are now stripped before spawning op so a stale shell session can't shadow the desktop integration. Auth failure and install hints now point users at desktop integration as the primary local-dev path. Fixes #80.
  • Vault / OpenBao provider: HTTPS requests now trust certificates from the operating system trust store (and honor SSL_CERT_FILE / SSL_CERT_DIR), so servers fronted by a private / internal CA work without modification. Previously the bundled webpki-roots set was the only trust anchor and any non-public CA produced Failed to connect to Vault ... error sending request. Switches the reqwest workspace dependency from rustls-tls to rustls-tls-native-roots. Fixes #85.

[0.9.1] - 2026-05-07

Changed

  • Dropped the serde-envfile dependency in favor of a small in-tree .env serializer. The previous git-pinned fork blocked publishing to crates.io; the new serializer applies the same escapes (backslash, double quote, dollar, newline) that the fork added and emits keys in sorted order for stable diffs.

[0.9.0] - 2026-05-07

Fixed

  • The --provider CLI flag now correctly takes precedence over the SECRETSPEC_PROVIDER environment variable. Previously the env var was consulted before the value forwarded from --provider (via set_provider), so users could not temporarily override the provider on the command line while the env var was set. Fixes #77.
  • Per-secret providers = [...] chains now behave as a true fallback chain when an upstream provider errors (e.g. a 403 from a vault the current user cannot access). Previously the first provider's error short-circuited the whole operation; now the error is logged as a warning and the next provider in the chain is tried. The original error is only surfaced if every provider in the chain failed (so genuine outages still bubble up), or if the secret has no alternative to fall back to. Fixes #83.
  • secretspec run now removes the temporary files it creates for as_path = true secrets after the child process exits. Previously the files were leaked under /tmp because std::process::exit skipped the destructors that own them. Fixes #71.
  • Provider URIs now support spaces and special characters in names (e.g., onepassword://Home Lab). All providers receive automatically percent-decoded values via a new ProviderUrl wrapper type.
  • dotenv provider: setting a secret no longer corrupts neighboring values that contain double quotes, backslashes, dollar signs, or newlines (e.g. JSON values). The underlying serde-envfile serializer did not escape these characters; fix is pinned via a fork until lucagoslar/serde-envfile#6 lands upstream. Fixes #74.
  • --provider (and SECRETSPEC_PROVIDER) is now honored on every command even when a providers = [...] chain is configured for the secret or profile. Previously set, get, check, import, and run silently used the first provider in the chain and ignored the explicit override, making secretspec set --provider <alias> a no-op against the requested target. The flag now consistently takes precedence: set/import/ generation write only to the chosen provider, and get/validate read only from it (no chain fallback). Provider aliases declared in ~/.config/secretspec/config.toml can now be passed directly to --provider. Fixes #81.

Added

  • BWS (Bitwarden Secrets Manager) provider with async SDK integration, secret caching, and full read-write support (requires --features bws)

Changed

  • secretspec-derive now depends on secretspec with default-features = false, avoiding pulling in CLI and provider features when only the derive macro is used.

[0.8.2] - 2026-03-19

Changed

  • All provider features (gcsm, awssm, vault) are now enabled by default
  • AWS Secrets Manager (awssm) provider: batch fetching via BatchGetSecretValue API, reducing N sequential API calls to ceil(N/20) batched calls. For 30 secrets this means 2 API calls instead of 30. Note: requires the secretsmanager:BatchGetSecretValue IAM permission in addition to existing permissions.

[0.8.1] - 2026-03-15

Added

  • rsa_private_key secret generation type: generates RSA private keys in PKCS1 PEM format, defaults to 2048 bits, configurable via generate = { bits = 4096 }

Fixed

  • Check provider authentication (e.g. OnePassword, LastPass) before prompting user for secrets, via a PreflightGuard that runs the check exactly once per provider instance

[0.8.0] - 2026-03-11

Added

  • HashiCorp Vault / OpenBao (vault) provider for Vault KV v1/v2 secret storage, with support for namespaces, TLS configuration, and OpenBao compatibility (requires --features vault)
  • AWS Secrets Manager (awssm) provider for AWS secret storage integration (requires --features awssm)
  • Support running secretspec from subdirectories: the CLI now walks up the directory tree to find the nearest secretspec.toml, similar to cargo and git. Also adds a -f/--file flag (and SECRETSPEC_FILE env var) to explicitly specify the config file path (#59)

Changed

  • Extract shared block_on async helper from AWSSM and GCSM providers into provider::block_on

Fixed

  • GCSM provider no longer panics when called from within an existing tokio runtime

[0.7.2] - 2026-02-24

Added

  • Keyring and pass providers now support folder_prefix via URI (e.g., keyring://secretspec/shared/{profile}/{key}) to share secrets across projects, matching the existing OnePassword and LastPass behavior

Changed

  • Support XDG_CONFIG_HOME on macOS by switching from directories to etcetera crate. Existing macOS configs at ~/Library/Application Support/secretspec/ are automatically migrated to ~/.config/secretspec/ (#28)

Fixed

  • Reject empty values when setting a secret

[0.7.1] - 2026-02-08

Changed

  • Improved interactive prompt for missing secrets: lists all missing secrets upfront with descriptions, adds step counter ([1/3]), and uses inquire::Password for consistent masked input. Removed rpassword dependency.

Fixed

  • Use a fork of inquire to support setting multi-line secrets (#32)

[0.7.0] - 2026-02-08

Added

  • Declarative secret generation: secrets can now be auto-generated when missing by adding type and generate fields to secret config. Supported types: password, hex, base64, uuid, and command (for arbitrary shell commands). Generation triggers during check/run when a secret is missing, and the generated value is stored via the configured provider.

Changed

  • OnePassword provider: Significant performance improvement by caching authentication status and using batch fetching with parallel threads. Reduces CLI calls from 2N sequential to ~2 sequential + N parallel for N secrets.

[0.6.2] - 2026-01-27

Added

  • CLI: Add --no-prompt (-n) flag to secretspec check command for non-interactive mode. When used, the command exits with non-zero status if secrets are missing instead of prompting for values. Useful for CI/CD pipelines, scripts, and automation. (#55)

[0.6.1] - 2026-01-15

Fixed

  • OnePassword provider: Fix duplicate item creation when existing item has no extractable value. Now uses op item list for existence checks and updates by item ID to avoid ambiguity.
  • OnePassword provider: Handle "More than one item matches" error gracefully by falling back to ID-based lookup.

[0.6.0] - 2026-01-12

Added

  • Google Cloud Secret Manager (GCSM) provider for GCP secret storage integration (#53)

Fixed

  • LastPass provider: Fix creating new secrets by using correct lpass add command instead of non-existent lpass set (#54)

[0.5.1] - 2026-01-02

Changed

  • CI: Updated macOS runners from deprecated macos-13 to macos-15 (Intel) and macos-latest (ARM)

[0.5.0] - 2026-01-02

Added

  • Pass (password-store) provider for Unix password manager integration
  • ensure_secrets() method is now public in the Rust SDK
  • Support specifying full file paths (ending in .toml) in extends field, in addition to directory paths

Changed

  • Performance: avoid double validation in check() for happy path

Fixed

  • Display correct error message when extended config file is not found, instead of the misleading "No secretspec.toml found in current directory" error

[0.4.1] - 2025-11-27

Added

  • OnePassword provider: Support for SECRETSPEC_OPCLI_PATH environment variable to specify custom path to the OnePassword CLI
  • OnePassword provider: Automatic detection of Windows Subsystem for Linux 2 (WSL2) and use of op.exe on that platform
  • Documentation for as_path option in configuration reference, Rust SDK docs, and landing page
  • Documentation for per-secret providers with fallback chains on landing page

Changed

  • OnePassword provider: Use stdin instead of temporary files when creating items for WSL2 compatibility (WSL paths are invalid when passed to Windows executables)

Fixed

  • Output status/progress messages to stderr instead of stdout, fixing direnv integration where stdout was evaluated as shell code

[0.4.0] - 2025-11-24

Added

  • Profile-level default configuration: profiles.<name>.defaults section for shared settings across secrets in a profile
  • Default providers for profiles: define common providers once and have all secrets use them unless overridden
  • Default values and required settings can now be specified at profile level to reduce repetition
  • as_path option for secrets: write secret values to temporary files and return the file path instead of the value. Temporary files are automatically cleaned up when the resolved secrets are dropped in Rust SDK usage. For CLI commands (get and check), temporary files are persisted and NOT deleted after the command exits. In the Rust SDK, fields with as_path = true are generated as PathBuf or Option<PathBuf> instead of String

Changed

  • Secret required field is now Option<bool> to allow profile-level defaults to apply when not explicitly set
  • Secret default field can now inherit from profile-level defaults if not specified per-secret
  • Secret providers field can now inherit from profile-level defaults if not specified per-secret
  • Profile defaults only apply to secrets that don't explicitly set these fields

[0.3.4] - 2025-11-09

Changed

  • Secrets::check() now returns Result<ValidatedSecrets> instead of Result<()>, allowing callers to access the validated secrets

[0.3.3] - 2025-09-10

Fixed

  • CLI: Count optional secrets as "found" in the summary

[0.3.2] - 2025-09-10

Added

  • Support for piping multi-line secrets via stdin

Fixed

  • Import command now resolves secrets from all profiles, not just the active profile (fixes issue #36)
  • Fix incorrect stats in the summary for certain configurations

[0.3.1] - 2025-07-28

Fixed

  • Installers for arm/linux

[0.3.0] - 2025-07-25

Added

  • Integrate secrecy crate for secure secret handling with automatic memory zeroing
  • Add reflect() method to Provider trait for provider introspection
  • Export Provider trait from secretspec crate for use in derived code

Changed

  • Made keyring provider optional via keyring feature flag (enabled by default)
  • Unified provider parsing logic in init command to support all provider formats consistently
  • Downgraded keyring dependency to 3.6.2
  • Updated with_provider in derive macro to accept TryInto<Box<dyn Provider>> for consistent provider handling

Fixed

  • Fixed secret optionality logic: having a default value no longer makes a secret optional in generated types

[0.2.0] - 2025-07-17

Changed

  • SDK: Added set_provider() and set_profile() methods for configuration
  • SDK: Removed provider/profile parameters from set(), get(), check(), validate(), and run() methods
  • SDK: Embedded Resolved inside ValidatedSecrets

Fixed

  • Fix stdin handling for piped input in set/check commands
  • Fix SECRETSPEC_PROFILE and SECRETSPEC_PROVIDER environment variable resolution
  • Ensure CLI arguments take precedence over environment variables
  • add CLI integration tests
  • Update test script to handle non-TTY environments correctly

[0.1.2] - 2025-01-17

Fixed

  • SDK: Hide internal functions

[0.1.1] - 2025-07-16

Added

  • secretspec --version

Fixed

  • Profile inheritance: fields are merged with current profile taking precedence

[0.1.0] - 2025-07-16

Initial release of SecretSpec - a declarative secrets manager for development workflows.