Release Notes
Added
-
Gopass provider (
gopass://) for GPG-based password manager with git-synced password store. -
secretspec exportcommand 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', foreval "$(secretspec export)"),dotenv,
json, orgha(appends to$GITHUB_ENVand emits::add-mask::for each
value). Unlikerunit 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
whosetenant_id,client_id, andclient_secretcan be sourced as provider
credentials (withAZURE_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_identityand
?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
awssmprovider acceptskms_key_idandtag.NAME=VALUEquery
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/*onCreateSecret) 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, likeext-redis), with anext-ffifallback 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 acredentialsmap binding a semantic,
provider-specific name (such asaccess_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 arefgiving 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 bysecretspec 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 throughlogin, is audited with acredential
marker naming the semantic credential and the source store; a credential
stored throughlogintakes 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=PROVIDERflag for declaring credential
sources from the command line.
Changed
- Rust SDK validation errors now store their detailed report out of line,
reducing the size ofSecretSpecErrorvalues while preserving diagnostics. - Generated types now describe the values resolution can actually return:
omittedrequiredstill means required, secrets supplied by a manifest
default or generator are non-nullable, and profile-specific types include
secrets inherited from thedefaultprofile. Profile JSON Schemas are now
exhaustive (additionalProperties: false) for the same reason. - A
refrouted 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. afieldref pointed
at a.envfile), failing fast with a clear message instead of at fetch time.
Arefon 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:pathshorthand
(e.g.providers = ["keyring"]), the same specs--provideraccepts.
Previously a chain entry had to be a declared alias or a fullscheme://URI. - An explicitly empty
providers = []list now uses the default provider for
getas well, matching howcheckandrunalready treated it. - A
providerschain whose first entry misspellsonepasswordas
1passwordnow fails up front with the corrective "useonepassword
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::credentialsis a plain map whose empty state means
"no provider credentials", rather than anOption, so the two ways of spelling
an alias without credentials cannot diverge.
Removed
- The unused public
Config::merge_withandProfile::merge_withmethods.
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
extendshierarchy as a DAG. Shared
ancestors in diamond-shaped graphs are applied once instead of being reported
as cycles, later entries inextendscorrectly 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 rawrequired/defaultinterpretation 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 (andtype, forgenerate)
from the default profile instead of failing with "missing description".
The merged view is also validated for real conflicts, so agenerate
secret in the default profile combined with adefaultvalue 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, andcheckandrunlist 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, andgetall walk the chain the same way. getandsetnow record an audit event when a secret's provider routing
fails to resolve (for example an undefined alias), matching howcheckand
runaudit every attempted read.- A provider chain entry that misspells
onepasswordas1passwordnow gets
the same "useonepasswordinstead" 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. importprints its per-secret summary in a stable, name-sorted order.runno 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, sonpm install secretspecworks 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)
Install secretspec 0.15.0
Install prebuilt binaries via shell script
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/cachix/secretspec/releases/download/v0.15.0/secretspec-installer.sh | shDownload secretspec 0.15.0
| File | Platform | Checksum |
|---|---|---|
| secretspec-aarch64-apple-darwin.tar.xz | Apple Silicon macOS | checksum |
| secretspec-x86_64-apple-darwin.tar.xz | Intel macOS | checksum |
| secretspec-x86_64-pc-windows-msvc.zip | x64 Windows | checksum |
| secretspec-aarch64-unknown-linux-gnu.tar.xz | ARM64 Linux | checksum |
| secretspec-x86_64-unknown-linux-gnu.tar.xz | x64 Linux | checksum |