SecretSpec 0.20: Git, Docker, inline specs, and five new providers
SecretSpec 0.20 brings SecretSpec-managed credentials to Git and Docker, lets applications declare secrets directly in code, adds five providers, and expands support for JVM and Alpine applications.
This release includes:
- Git and Docker credential helpers: let ordinary Git and Docker commands retrieve credentials from any SecretSpec provider without copying them into another credential store.
- Declarations in application code: build secret specifications in Rust and prompt for missing values from typed loaders.
- libsecretspec and SDKs:
pass inline specifications from eight SDKs, attach caller context, and adopt
the new name for
secretspec-ffi. - Five new providers: use Azure App Configuration, Kubernetes, EJSON, Fly.io, and Cloudflare Secrets Store.
- JVM and Alpine support: resolve secrets from Java and Kotlin, install static musl CLI builds, and run the Node.js SDK in Alpine images.
- Other changes: forward container signals, report missing generated secrets correctly, emit shell completions, and make dotenv values round-trip.
Git and Docker credential helpers
Section titled “Git and Docker credential helpers”Git and Docker can now read credentials directly from any SecretSpec provider. Configure each helper once, then keep using the usual Git and Docker commands.
Git credentials
Section titled “Git credentials”The Git integration registers a helper for a host, then stores its token through your normal SecretSpec provider:
$ secretspec git configure \ --url https://github.com \ --username YOUR_USERNAME$ secretspec git login https://github.comAfter that, normal commands invoke git-credential-secretspec automatically:
$ git clone https://github.com/OWNER/PRIVATE_REPOSITORY.git$ git pushThe helper works before a repository has been cloned and can scope credentials
by host or URL path. It also supports SMTP credentials for git send-email, so
the password does not need to live in sendemail.smtpPass.
Docker credentials
Section titled “Docker credentials”The Docker integration uses one helper per registry. Configure the registry and store its token separately:
$ secretspec docker configure \ --registry ghcr.io \ --username YOUR_USERNAME$ secretspec docker login ghcr.iodocker pull, docker push, docker build, and Docker Compose can now retrieve
the value through docker-credential-secretspec:
$ docker pull ghcr.io/OWNER/IMAGE:TAGDocker’s config.json contains only the helper configuration, not the
credential value. Separate DOCKER_CONFIG directories can use different
credentials for the same registry.
The helpers only read credentials. Use secretspec git login / logout and
secretspec docker login / logout to change stored values. Use configure
and unconfigure to add or remove the helpers themselves.
Declarations in application code
Section titled “Declarations in application code”secretspec.toml remains the portable contract shared by the CLI and every
SDK. Applications can now define that contract directly when keeping a separate
manifest is inconvenient.
Rust-first specifications
Section titled “Rust-first specifications”Rust applications can use the new public Spec, Profile, and Secret types
to build a specification in code:
use secretspec::{Profile, Secret, Secrets, Spec};
let spec = Spec::builder("checkout") .provider("env", "env://") .secret( "DATABASE_URL", Secret::required("PostgreSQL connection URL").providers(["env"]), ) .profile( "production", Profile::new().secret( "SENTRY_DSN", Secret::required("Production Sentry endpoint"), ), ) .build()?;
let mut secrets = Secrets::from_spec(spec)?;secrets.set_profile("production");let resolved = secrets.resolve()?;Rust-built specs use the same validation as secretspec.toml.
Spec::schema_json generates the same JSON Schema as secretspec schema, and
SpecBuilder can update a file-backed spec without losing comments or
formatting.
See Describing secrets in Rust for the complete builder and format-preserving editing API.
Typed Rust loaders generated by declare_secrets! can now call
prompt_missing() to ask for and store
missing required values. Prompting remains opt-in.
libsecretspec and SDKs
Section titled “libsecretspec and SDKs”SecretSpec 0.20 gives the SDKs the same new capabilities across languages. It
also renames secretspec-ffi to
libsecretspec. Packaged SDKs handle the rename automatically;
update your build only if you link or load libsecretspec directly.
Inline specifications
Section titled “Inline specifications”The SDKs for Go, Python, Node.js, Ruby, Haskell, PHP, C#, and Swift can now accept an inline specification. This is useful when the application already owns its configuration or cannot rely on a manifest on disk. For example, in Go:
spec := map[string]any{ "project": map[string]any{"name": "checkout"}, "profiles": map[string]any{"default": map[string]any{ "secrets": map[string]any{ "API_TOKEN": map[string]any{"description": "API token"}, }, }},}
resolved, err := secretspec.New(). WithInlineSpec(spec, "/logical/project"). Load()The base directory tells SecretSpec where to resolve relative provider paths and inherited manifests. Invalid fields and unsupported spec versions fail with an error.
Caller context
Section titled “Caller context”Git and Docker set caller context automatically, recording which tool and operation requested a secret. CLI and SDK callers can provide the same context:
$ secretspec get GITHUB_TOKEN \ --caller git \ --caller-version 2.51.0 \ --caller-operation credential_get \ --caller-resource github.com \ --reason "push release tag"Audit records include the caller name, version, operation,
and resource. This is separate from
require_reason,
so policies can record both which tool accessed a secret and why.
JVM and Alpine support
Section titled “JVM and Alpine support”The new JVM SDK lets Java, Kotlin, and other JVM applications load SecretSpec secrets directly. Add it with Gradle:
dependencies { implementation("org.cachix:secretspec-jvm:0.20.0")}Then load secrets with the same builder pattern as the other SDKs:
import org.cachix.secretspec.SecretSpec;
try (var resolved = SecretSpec.builder() .withProvider("keyring://") .withProfile("production") .withReason("boot web app") .load()) { System.out.println(resolved.secret("DATABASE_URL").get()); resolved.setAsSystemProperties();}The package supports JDK 11 or newer on Linux, macOS, and Windows, including x64 and Arm64 systems. It includes everything needed to resolve secrets, so the application does not need a separate SecretSpec CLI or Rust toolchain.
The standalone installer and secretspec-update now work on Alpine Linux. The
Node.js SDK also works directly in images such as node:alpine, without a glibc
compatibility layer.
Five new providers
Section titled “Five new providers”SecretSpec 0.20 adds five providers, bringing the total to 33.
Azure App Configuration
Section titled “Azure App Configuration”Use Azure App Configuration to read and manage ordinary key-values or follow references to Azure Key Vault secrets.
$ az login$ secretspec set DATABASE_URL --provider aac://payments-production$ secretspec run --provider aac://payments-production -- ./paymentsIt works with service principals, Azure CLI sessions, managed identity, workload identity, and connection strings. Provider URLs can select labels, prefixes, and tags.
Kubernetes
Section titled “Kubernetes”Kubernetes stores values in a ConfigMap or Secret using the current kubeconfig context:
$ secretspec set DATABASE_URL \ --provider k8s+secret://app-credentials@production$ secretspec run \ --provider k8s+secret://app-credentials@production \ -- ./applicationYou can use convention keys or point declarations at existing .data entries.
The provider supports reads, writes, deletion, and declaration discovery.
Shopify’s EJSON lets teams keep encrypted secrets alongside application source. Secret values are encrypted with a public key and decrypted with the matching private key, while the JSON structure stays visible.
SecretSpec’s EJSON provider reads string values from those
files. A ref.item selects any RFC 6901 JSON Pointer, while convention
addresses use /{project}/{profile}/{key}.
Supply the EJSON private key as a SecretSpec provider credential. It can come from Google Cloud Secret Manager, the system keyring, or any other readable provider. The EJSON provider is read-only.
Fly.io
Section titled “Fly.io”Use Fly.io to publish and delete application secrets with
flyctl. Secret values stay out of process arguments, and app-scoped deploy
tokens can come from provider credentials. Fly.io does not return plaintext, so
keep the original value in a readable provider and use fly as the deployment
destination.
Cloudflare Secrets Store
Section titled “Cloudflare Secrets Store”Use Cloudflare Secrets Store to publish and delete
account-level secrets through Cloudflare’s API. Authenticate with a scoped API
token from another provider or an existing Wrangler session. Cloudflare does
not return plaintext, so keep the original value in a readable provider and use
cloudflare as the deployment destination.
Other changes
Section titled “Other changes”Shell completions now come directly from the CLI definition:
$ source <(secretspec completions bash)Bash, Elvish, Fish, Nushell, PowerShell, and Zsh now complete commands,
profiles, scopes, secret names, providers, files, and executables. Completion
does not read secret values. See
secretspec completions for persistent
installation instructions (#330).
0.20 also includes these CLI and automation changes:
- On Unix,
secretspec runforwardsSIGTERM,SIGINT, andSIGHUPto its child, so containers can shut down gracefully (#391). check --json,check --explain, and SDK reports now mark an unprovisioned requiredgeneratedeclaration asmissing_requiredinstead of resolved. Runsecretspec checkorsecretspec runonce to generate and store it (#394).- Human-readable
secretspec checkoutput moves to stdout, matching its JSON and explain modes. Diagnostics remain on stderr (#372). - Dotenv files, age-encrypted dotenv blobs, and
export --format dotenvnow use dotenv-ng. Dollar signs and bcrypt-style values now round-trip correctly (#73). extractsupports INI documents, selecting unsectioned keys with/keyand named-section keys with/section/key(#386).- The age provider can now delete secrets, enabling
secretspec delete,import --delete-source, and age-backed provider caches (#328). - Closing a stdout pipe is quiet on Unix, so commands such as
secretspec export | headbehave like other Unix tools (#377).
Provider-specific fixes keep 1Password batches fast when optional items are missing (#401), prevent Bitwarden convention names from colliding across projects and profiles (#390), make Infisical Universal Auth sessions more reliable (#402), and let Node.js applications exit cleanly after AWS resolution (#365).
Upgrading
Section titled “Upgrading”$ cargo install secretspecFor most users, 0.20 is a drop-in upgrade. The new integrations, providers, and inline declarations are opt-in. Check these cases before upgrading:
- scripts that captured human-readable
secretspec checkoutput from stderr should capture stdout; - Google Cloud Secret Manager writes now use the collision-safe
secretspec2--{project}--{profile}--{key}convention. Reads fall back to the matching 0.19 name until a write moves the value, leaving the old secret in place for rollback; - Bitwarden Password Manager convention items now use
secretspec/{project}/{profile}/{key}titles.init --from bw://preserves references to legacy bare items so they can keep working or be migrated deliberately.
Rust applications using secretspec-derive can remove direct serde or
secrecy dependencies if they were needed only for generated types. Code using
the old raw configuration or code-generation APIs should move to Spec,
SpecBuilder, Profile, and Secret.
See the full changelog for every change and fix in this release.
What’s next
Section titled “What’s next”Resolver and provider IPC
Section titled “Resolver and provider IPC”The resolver and provider IPC protocols are currently RFCs in PR
#362. The resolver
protocol
would let applications request individual secrets from secretspec serve. The
provider
protocol
would let external executables act as SecretSpec providers. Both are targeted
for 0.21 and may change during review.
Nix secret-provider plumbing
Section titled “Nix secret-provider plumbing”Work on the Nix integration identified that SecretSpec first needs the IPC layer above, so the concrete integration is blocked until 0.21 is released. In the meantime, the underlying refactoring has been split into a generic secret-provider interface for Nix.
devenv machines
Section titled “devenv machines”Separately, devenv’s experimental machines
interface aims to handle NixOS
installation, NixOS and nix-darwin deployment, and home-manager activation in
one workflow. Its SecretSpec integration can supply deployment credentials
without putting their values in Nix evaluation or store paths. This work follows
devenv’s own release schedule and is not part of SecretSpec 0.21.
Questions or feedback? Join us on Discord.
Fencer for Open Source
Section titled “Fencer for Open Source”In partnership with SecretSpec, Fencer has expanded its offering to open source. Public repositories now get free static analysis, dependency scanning, secret scanning, and GitHub configuration scanning, with no credit card and no expiry.
We recommend Fencer for code security scanning, and Fencer recommends SecretSpec for secrets management. Fencer finds credentials and other risks already in a repository, while SecretSpec helps keep the next secret in the right provider instead of source code or a dotenv file.