I checked Claude Code 2.1.257 after authenticating to several remote MCP
servers. The file ~/.claude/.credentials.json had mode 0600, as it should,
but it also contained a top-level mcpOAuth object with the access tokens.
Here is the shape of one Cloudflare entry, with every credential value
redacted:
The browser flow makes the secret easy to miss. Run claude mcp login, approve
access in the browser, and return to a connected MCP server. Nobody manually
created a token, copied it from a dashboard, or pasted it into a configuration
file.
But Claude Code still received a credential. It must persist that credential
if the connection is to survive a restart.
OAuth is valuable here. Claude Code can discover the authorization server,
request specific scopes, complete the authorization-code exchange, refresh an
access token, and revoke the grant. Anthropic’s MCP
documentation
also lets users pin the scopes Claude Code requests.
What OAuth does not specify is a secure local vault.
API tokens can also be scoped, limited to particular resources, assigned an
expiry, rotated, and revoked independently. OAuth standardizes delegation and
renewal, while avoiding the copy-and-paste ceremony. Those are substantial
benefits, but they do not turn the resulting bearer token into something that
is safe to leave in plaintext.
Property
OAuth credential
Scoped API token
Must be stored by the client
Yes
Yes
Can have limited permissions
Yes
Yes
Can expire
Yes
Yes
Can be revoked independently
Usually
Yes
Can be replayed if stolen
Yes
Yes
Standard interactive delegation
Yes
Provider-specific
Standard automatic renewal
Often
Usually external
OAuth solves how Claude Code obtains and renews a delegated credential.
Secret storage solves what happens to that credential between uses. They are
separate concerns.
Claude Code should not decide that every Linux user’s MCP tokens belong in the
same plaintext file. The persistence layer should be replaceable:
Claude Code OAuth client
│
▼
credential-store interface
│
▼
SecretSpec TypeScript SDK
│
▼
user-selected SecretSpec provider
The right integration point is the SecretSpec Node.js / TypeScript
SDK. It embeds the Rust resolver, so the TypeScript side does not
need bespoke code for each backend. Claude Code could serialize one MCP OAuth
credential per server and ask SecretSpec to load, save, or delete it using the
provider the user or organization selected.
SecretSpec 0.20 has 33 provider
integrations. They cover local
keyrings, password managers, encrypted files, cloud secret managers, and
deployment destinations. Providers declare their capabilities, so a credential
store can require readable and writable storage while still using the same
interface everywhere.
The current TypeScript SDK exposes SecretSpec’s provider-independent resolver.
We would add the small get/set/delete credential-store surface Claude
Code needs rather than reimplement 33 integrations in its codebase.
For example, set the system keyring as the default
provider in the local user configuration:
A team might instead require OpenBao or a cloud secret
manager. A headless workstation might use an age-encrypted
store. The OAuth flow would stay exactly the same; only
persistence would change.
What Codex does: Codex makes MCP OAuth storage configurable. Its
configuration
reference
documents auto, file, and keyring backends. Setting
mcp_oauth_credentials_store = "keyring" selects the system keyring. This is
not a general secret-provider interface, but it avoids making a plaintext
credential file the only option on Linux.
Coming in SecretSpec 0.21: We are working on versioned resolver and
provider IPC for
zero-dependency integrations. Applications will be able to use SecretSpec
providers over a local protocol without embedding an SDK or provider code.
We are working to make open source tools retrieve credentials from a
user-selected secret store instead of copying them into another plaintext
file. SecretSpec now provides Git and
Docker credential helpers, and we have proposed a
generic, operation-scoped secret resolver interface for
Nix.
Because these projects are open source, we can inspect their credential
boundaries and contribute safer ones upstream.
That is one of the practical security benefits of open source: when a secret
crosses the wrong boundary, users do not have to wait for the vendor to decide
that the boundary matters.
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.
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.
The 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.
The Docker integration uses one helper per registry.
Configure the registry and store its token separately:
Terminal window
$secretspecdockerconfigure\
--registryghcr.io\
--usernameYOUR_USERNAME
$secretspecdockerloginghcr.io
docker pull, docker push, docker build, and Docker Compose can now retrieve
the value through docker-credential-secretspec:
Terminal window
$dockerpullghcr.io/OWNER/IMAGE:TAG
Docker’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.
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-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.
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.
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:
The base directory tells SecretSpec where to resolve relative provider paths
and inherited manifests. Invalid fields and unsupported spec versions fail
with an error.
Git and Docker set caller context automatically, recording which tool and
operation requested a secret. CLI and SDK callers can provide the same context:
Terminal window
$secretspecgetGITHUB_TOKEN\
--callergit\
--caller-version2.51.0\
--caller-operationcredential_get\
--caller-resourcegithub.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.
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.
It works with service principals, Azure CLI sessions, managed identity,
workload identity, and connection strings. Provider URLs can select labels,
prefixes, and tags.
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.
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.
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.
Shell completions now come directly from the CLI definition:
Terminal window
$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 run forwards SIGTERM, SIGINT, and SIGHUP to its
child, so containers can shut down gracefully
(#391).
check --json, check --explain, and SDK reports now mark an unprovisioned
required generate declaration as
missing_required instead of resolved. Run secretspec check or
secretspec run once to generate and store it
(#394).
Human-readable secretspec check output moves to stdout, matching its JSON
and explain modes. Diagnostics remain on stderr
(#372).
Dotenv files, age-encrypted dotenv blobs, and export --format dotenv now use
dotenv-ng. Dollar signs and bcrypt-style values now round-trip correctly
(#73).
extract supports INI
documents, selecting unsectioned keys with /key and 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 | head behave 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).
For 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 check output 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.
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.
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.
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.
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.
We have released dotenv-ng 1.0, a
modern Rust implementation for loading and rendering .env files. It began as
a fork of dotenvy after its parser
changed a secret while reading it.
The file was intact. Reading it through the dotenv provider returned a
different value because dotenvy treated the dollar-prefixed fragments as
variable substitutions. The failure appeared later as an authentication error,
not a parse error.
An upstream request to make substitution configurable had been open since
2024. A pull
request arrived in 2026 but
targeted an unreleased API. A migration tool cannot require users to recognize
and escape parser syntax inside their secrets.
The original Rust dotenv crate stopped releasing in 2020 and was eventually
marked unmaintained by
RustSec, which listed
dotenvy as an alternative.
Dotenvy’s description still calls it “a well-maintained fork.” Its latest
published version, 0.15.7, was released on March 22,
2023. A Rust forum
discussion
noted the two-year release gap in 2025. By the time the bcrypt bug blocked
SecretSpec, it was more than three years.
There is an uncomfortable irony in a maintained fork repeating its upstream’s
release problem. Its maintainers do not owe us a release, but SecretSpec needed
breaking fixes on a schedule we control.
We first considered a small patch. Auditing the parser uncovered more problems
around JSON, Windows paths, Unicode names, precedence, and partial environment
mutation.
dotenv-ng therefore starts from dotenvy 0.15.7 but deliberately breaks
compatibility where correctness requires it. Version 1.0 adds:
a source-aware parser with structured errors;
literal dollar signs by default, with substitution available only when a
caller explicitly enables it;
a broader key grammar that supports dashes, leading digits, leading dots,
and Unicode;
a renderer that adds only the quoting and escaping needed to parse a value
back unchanged;
validation before process-environment mutation; and
an explicit unsafe boundary around that mutation.
Property tests exercise arbitrary Unicode and syntax-heavy values, check that
quoting is used only when necessary, and round-trip complete documents. The
parser and renderer, the core of the rewrite, both have 100% line coverage.
Secret storage changes as a project grows. Values move from local files to
password managers, from one naming convention to another, and sometimes
between providers with completely different data models. The application still
expects the same API_KEY or DATABASE_URL at the end.
SecretSpec 0.19
treats those changes as a normal workflow instead of a one-off migration
script.
This release includes:
Provider-specific storage
layouts: give each provider its own
address, transform stored values, import existing files, and preview exact
write references.
Config belongs in secrets: resolve
profile-specific config alongside stored secrets, generate ephemeral values,
and securely prompt during secretspec run.
Passbolt provider: read and write secrets in a
self-hosted Passbolt server, with credentials supplied by another provider
when needed.
API_KEY is the name used by your application. In 1Password, the same value
might be the token field of an item named old-api-item.
A ref gives SecretSpec this store address. item
names the entry. Coordinates such as field, section, and vault locate a
value inside structured stores. The providers list still decides which
stores to try.
Before 0.19, every provider in a secret’s route received the same ref, even
though stores such as 1Password and dotenv organize secrets differently. Now
each provider alias can template its usual layout, while refs.<alias> handles
exceptions:
secretspec.toml
[providers]
legacy = "onepassword://Legacy"
production = {
uri = "onepassword://Production",
ref = { item = "{project}-{profile}", field = "{key}" }
}
local = { uri = "dotenv://.env", ref = { item = "{key}" } }
production reads the API_KEY field from a <project>-production 1Password
item. The local fallback reads the dotenv key API_KEY. If legacy is
selected explicitly, refs.legacy reads the token field from old-api-item.
For each provider, refs.<alias> takes precedence over the alias’s ref
template, which takes precedence over the provider convention. Templates
accept {project}, {profile}, and {key} in every address field. Existing
route-wide ref declarations remain supported.
Because scoped references also apply to imports, that exception can describe a
migration source without joining the normal fallback route:
This reads from refs.legacy and writes through the production template. It
also works between distinct entries in one physical store. SecretSpec rejects
the import if both addresses resolve to the same entry.
extract = { format = "json", pointer = "/database/password" }
}
JSON strings become their unquoted contents. Numbers, booleans, objects, and
arrays keep their JSON representation. Extracted declarations are read-only.
set, delete, prompting, generation, and import cannot overwrite the source
document.
encoding defines the textual representation in provider storage:
Supported encodings are standard Base64, URL-safe Base64, and hexadecimal.
Writes encode the logical value. Reads decode the stored value. Decoded UTF-8
can be returned directly. Set as_path = true to materialize arbitrary bytes
in a file.
File stores one plaintext UTF-8 file per secret beneath
a required root. Convention paths use {project}/{profile}/{key}. ref.item
selects an existing relative path, including a file mounted at runtime.
Writes use atomic replacement and create private Unix files and directories.
The provider rejects traversal and nested symlinks. It does not encrypt its
contents.
The file provider is also a migration adapter for directories that already
contain one file per secret. A provider ref template maps the source layout,
while the destination alias independently maps the same declarations into its
native store:
secretspec.toml
[providers]
legacy_files = {
uri = "file:./old-secrets",
ref = { item = "{profile}/{key}" }
}
production = {
uri = "onepassword://Production",
ref = { item = "{project}-{profile}", field = "{key}" }
For API_KEY, the source is old-secrets/production/API_KEY. The destination
is the API_KEY field in the <project>-production 1Password item. The source
files do not need to follow the SecretSpec convention. With
--delete-source, 0.19 preflights every mapped source and destination, verifies
every copied value, and only then removes the plaintext source files.
Preflight, write, or verification failures leave every source untouched. A
destination with a different existing value keeps its corresponding source.
Enter value for API_KEY (profile: production): ********
SOPS reports the canonical encrypted file and exact sops set selector. Other
providers report their native item or path. A missing profile or unexpected
template is visible before SecretSpec receives the new value.
Null always reports a missing value and stores nothing.
This lets manifest defaults provide non-sensitive values without adding a
storage backend. One resolution can now return profile-specific configuration
and provider-backed secrets together. This follows the separation described in
Secrets Don’t Belong in Config.
APP_MODE resolves to local, staging, or production based on the selected
profile. Each override inherits the description and null route from
[profiles.default]. Only the value is repeated.
The result is one declaration model for values the application needs, whether
they come from a secret store or directly from the manifest. Config can travel
through the same profile, scope, SDK, and run workflow without pretending it
needs encrypted persistence.
The null provider can also generate a fresh value for each resolution. Use it
for session tokens, test credentials, and other values that should exist only
for one process invocation. Persistent credentials should continue to use a
writable provider.
Set prompt = true on a declaration to let secretspec run securely request
its value when the configured providers do not have one:
secretspec.toml
[profiles.default]
DEPLOY_PASSWORD = {
description = "One-time deployment password",
prompt = true,
providers = ["null"]
}
Terminal window
$ secretspec run -- ./deploy
? Enter value for DEPLOY_PASSWORD (profile: default):
A writable provider saves the answer, turning the prompt into first-use
provisioning. The null provider keeps it ephemeral and injects it only into
that invocation. The hidden prompt reads from the controlling terminal, so the
child’s stdin remains available for pipes and redirects. If no controlling
terminal exists, run fails before starting the child. Declarations without
prompt = true retain the existing fail-on-missing behavior.
Passbolt is the third new provider in 0.19. SecretSpec now has 27 providers.
Passbolt reads and writes resources in a
self-hosted Passbolt server through go-passbolt-cli. Convention values use
the resource secretspec/{project}/{profile}/{key} and its password field.
References can select existing resources by UUID or exact name and address the
password, username, uri, or description field.
secretspec.toml
[providers]
bootstrap = "keyring://"
[providers.passbolt_team]
uri = "passbolt://?server=https://pass.example.com&folder=a9230ec4-5507-4870-b8b5-b3f500587e4c"
The OpenPGP private key and passphrase can come from another SecretSpec
provider. Environment fallbacks and the Passbolt CLI configuration are also
supported. Folder-scoped providers support declaration discovery with
init --from.
Remote secret reads pay for authentication, process startup, and network
round-trips before the application can start. SecretSpec 0.19 reduces that work
both across invocations and within one resolution.
A single authoritative provider can now define uri, credentials, and
cache on the same alias:
secretspec.toml
[providers]
local = "keyring://secretspec/cache/{project}/{profile}/{key}"
azure = {
uri = "akv://team-vault",
credentials = { client_secret = "keyring" },
cache = { provider = "local", max_age = "8h" }
}
[profiles.development.defaults]
providers = ["azure"]
The cached fallback form introduced in 0.17 remains available when several
authoritative providers can answer.
Cache entries now include their absolute expiration time and originating
max_age. SecretSpec removes an expired entry whenever it encounters one, and
changing max_age invalidates entries written under the previous policy.
Fallback resolution also reuses provider instances and handles independent
primary misses concurrently. Azure Key Vault reuses its client and serializes
initial challenge-based authentication, avoiding repeated Azure CLI processes
within one resolution.
1Password field references now resolve together through one op inject call,
instead of starting op read separately for every field. This reduces CLI
startup and repeated unlock overhead when one profile loads several fields. If
the batch contains a missing reference, SecretSpec falls back to bounded
concurrent reads so it can preserve per-secret missing-value behavior without
serializing the whole profile.
In the cold-cache benchmark from
the implementation PR, a
representative profile with 25 field references resolved in 11.890 seconds,
down from 96.294 seconds. The batch used 3 op processes instead of 27, making
that run 8.10 times faster.
This section describes the 0.19 names. In SecretSpec 0.20+, the component is
named libsecretspec, installed with cargo cinstall -p libsecretspec, and
provides libsecretspec.pc.
cargo cinstall -p secretspec-ffi now installs the library, C header, and a
secretspec_ffi.pc file containing the complete link metadata. Go builds can
use the pkgconfig tag, Ruby native extensions accept --enable-pkg-config,
and Haskell builds use the use-pkg-config Cabal flag. The same metadata
supports installed static or shared libraries.
Haskell now declares its required macOS system frameworks. The Rust SDK’s
ProviderAlias type also exposes leaf, credentials, and credentials_mut
helpers for configuration tooling.
Existing route-wide ref declarations, inheriting profiles, and cached
fallback aliases remain compatible. All new configuration fields and providers
are opt-in.
These items are not part of 0.19. They are open work for future releases:
Native Windows ARM64 CLI archive (target: 0.19.1): add
secretspec-aarch64-pc-windows-msvc.zip and its checksum to GitHub Releases
so the CLI can run natively on Windows ARM64. The static installer will
continue to select the x64 build on Windows ARM devices until it supports the
native archive, and standalone updates depend on
axoupdater supporting Windows ARM64.
Secret lifecycle commands: add
declarations, delete stored values, and move secrets between providers
without leaving the source copy behind.
Provider-backed discovery:
initialize a manifest from an age file, an AWS Parameter Store hierarchy, or
a Bitwarden collection without writing any values to it.
SecretSpec has always kept the declaration in secretspec.toml separate from
the stored value. 0.18 brings both sides of that lifecycle into the CLI.
secretspec add adds a declaration to the selected
profile while preserving the manifest’s comments, formatting, and unrelated
tables:
Terminal window
$ secretspec add STRIPE_API_KEY --description "Stripe API access token"
✓ Added secret 'STRIPE_API_KEY' to profile 'default' in secretspec.toml
Set its value with: secretspec set STRIPE_API_KEY --profile default
$ secretspec set STRIPE_API_KEY
Enter value for STRIPE_API_KEY: ********
✓ Secret 'STRIPE_API_KEY' saved to keyring (profile: default)
add never asks for or stores the value. The declaration can be reviewed and
committed before each developer or deployment supplies its own value.
secretspec delete does the inverse on the
storage side: it removes a value without changing the declaration. The next
check therefore reports the secret as missing instead of quietly removing
the application’s requirement:
Terminal window
$ secretspec delete STRIPE_API_KEY
Deleted 'STRIPE_API_KEY'
Deleted 1 secret value; 0 already absent
Deletion is idempotent, invalidates an associated cache entry, and follows the
same primary-write-provider routing as set. delete --all requires an
interactive confirmation, or an explicit --yes in non-interactive use.
Provider migrations can now remove each source value after proving the move
succeeded:
import --delete-source reads the destination back
and compares it with the source before deleting anything. An identical value
already at the destination is safe to remove from the source; a conflicting
value leaves the source intact. SecretSpec also rejects a source without
deletion support before writing the destination and recognizes equivalent
provider spellings as the same store, so a migration cannot delete the value
it just wrote through another alias.
Together these commands keep the distinction explicit: add changes what the
application declares, set and delete change one environment’s stored
value, and import --delete-source moves that value between stores.
The first SecretSpec command in an existing project is often
secretspec init --from .env. In 0.18,
init --from accepts every provider that can discover
declarations, including age, AWS Parameter Store, and Bitwarden Password
Manager.
Hierarchical stores also receive an explicit project and profile so SecretSpec
looks only inside the namespace the new manifest will use:
Discovery writes names and generated descriptions, never secret values. After
reviewing the manifest, keep the discovered provider as the profile’s source
or use secretspec import to copy the now-declared values somewhere else.
0.18 brings SecretSpec to 24 providers, with four additions spanning personal
password managers, machine-oriented vaults, and cloud parameter storage.
Bitwarden Password Manager is separate from the existing
Bitwarden Secrets Manager provider. The new bw:// provider uses the official
bw CLI to read and write regular vault items: logins, secure notes, cards,
identities, and SSH keys. It can address organizations and collections by name
or ID, restrict a provider to one item type or field, discover declarations,
and point ref secrets at existing items. A
?server= guard verifies that the CLI is logged into the expected self-hosted
instance instead of silently reading the wrong vault.
Keeper Secrets Manager uses Keeper’s official Rust
SDK, so it does not need a separate CLI. A keeper://FOLDER_UID provider reads,
writes, batches, and deletes convention records shared with a KSM application;
refs can select an existing record and field. Its client configuration can
come from KSM_CONFIG, a protected configuration file, or SecretSpec
provider credentials.
AWS Systems Manager Parameter Store stores every
value as a KMS-encrypted SecureString. The awsps:// provider uses the
standard AWS credential and region chains and supports shared-config profiles,
hierarchy prefixes, complete {project} / {profile} / {key} templates,
customer-managed KMS keys, and parameter tiers. Refs can select an existing
parameter by name, version, label, or ARN; unversioned name refs are writable,
while pinned revisions remain read-only. Its bounded hierarchy discovery uses
GetParametersByPath without decrypting values.
Dashlane reads secrets, secure notes, and logins
through the dcli CLI. It is intentionally read-only because dcli cannot
create or edit vault items. A ref can address an existing item by title or
identifier and select one of its fields. CI can provide
DASHLANE_SERVICE_DEVICE_KEYS directly or source the same
service_device_keys input from another SecretSpec provider.
A project can route different secrets through any combination of them:
Provider choice still stays outside application code. The CLI and every SDK
resolve the same declaration regardless of which of these aliases supplies a
value.
The SDK exposes fluent and one-shot resolution, typed failures, value-free
preflight reports, scopes, provenance, environment export, and JSON input for
generated Swift models. Calling close() deterministically removes temporary
files created for as_path secrets.
The SwiftPM release contains a checksummed XCFramework with the Rust resolver,
so an application needs neither a Rust toolchain nor a separately installed
SecretSpec library.
Vault and OpenBao deployments do
not always use the default approle and jwt mount names. Their provider URIs
can now choose a mount relative to /v1/auth:
AppRole authentication can omit secret_id when the server role is configured
with bind_secret_id=false. JWT authentication can likewise omit its role when
the selected mount has a server-configured default_role. Explicit URI,
environment, or provider-credential inputs continue to take precedence.