SecretSpec 0.17: Scopes, secrets caching, SOPS, age, and systemd credentials

domenkozar1 pts0 comments

SecretSpec 0.17: Scopes, secrets caching, SOPS, age, and systemd credentials | SecretSpec<br>Skip to content

SecretSpec 0.17: Scopes, secrets caching, SOPS, age, and systemd credentials

Jul 27, 2026<br>Domen Kožar

SecretSpec 0.17<br>ships:

Scopes : let each service or task resolve<br>only its declared subset of a profile.

Secrets caching : cache a slow fallback<br>route in a local secret store with bounded freshness and explicit<br>invalidation.

Cross-secret validation :<br>declare alternative or mutually exclusive credentials instead of marking<br>every value independently required or optional.

GitHub and Forgejo Actions : resolve a<br>profile in one workflow step and expose its secrets to the steps that follow.

New providers : now with 20 providers, use<br>SOPS, age, KeePass<br>KDBX,<br>OpenBao, Scaleway Secret<br>Manager, and systemd<br>credentials through the same SecretSpec<br>interface, with JWT/OIDC authentication for Vault and<br>OpenBao.

Scopes<br>Section titled “Scopes”

A profile describes how secrets resolve for an environment. A<br>scope now describes which of those secrets one consumer<br>may receive:

secretspec.toml[profiles.default]

DATABASE_URL = { description = "Database" }

API_KEY = { description = "API key" }

QUEUE_TOKEN = { description = "Queue token" }

[scopes.api]

secrets = ["DATABASE_URL", "API_KEY"]

[scopes.worker]

secrets = ["DATABASE_URL", "QUEUE_TOKEN"]

Terminal windowsecretspec run --scope api -- ./api

secretspec run --scope worker -- ./worker

Composed secrets may still read hidden dependencies to build a visible value,<br>but those inputs are not exposed to the child. The same scope selection is<br>available to check, export, and the SDK<br>builders. Scopes minimize secret delivery; they are not an<br>authorization boundary when the child itself holds provider credentials.

Carrying the selected scope through resolver requests and results required a<br>breaking change to<br>secretspec-ffi.<br>All SecretSpec SDKs have been updated for 0.17 to support<br>scopes, so applications should upgrade their SDK package and bundled native<br>resolver together.

Secrets caching<br>Section titled “Secrets caching”

Many cloud providers take long enough to resolve a secret that their latency<br>becomes part of every development command.

A single 1Password lookup can take roughly one second .

SecretSpec providers implement get_many so a backend can resolve several<br>values together. Relatively few secret stores and CLIs expose a true bulk-read<br>operation, however, so many providers still have to perform separate lookups.

Waiting on a remote service or its CLI every time makes check, run, and<br>application startup feel slow, especially as a project grows.

A provider alias can now combine its authoritative fallback route with a local<br>cache:

secretspec.toml[providers]

vault = "vault://vault.example.com:8200/secret"

local = "keyring://secretspec/cache/{project}/{profile}/{key}"

fast_vault = {

fallback = ["vault"],

cache = { provider = "local", max_age = "8h" }

[profiles.default.defaults]

providers = ["fast_vault"]

Fresh entries avoid contacting the remote provider. A miss or expired entry<br>falls through to Vault and refreshes the cache; writes<br>update the authoritative provider first and then refresh or invalidate its<br>cached copy. Route changes, reference changes, and writes that bypass the<br>cached alias also invalidate the entry.

The cache is a real copy of the secret, so SecretSpec requires a distinct store<br>that it can delete from and records ownership before changing an entry.<br>secretspec cache clear [NAME] forces the next read back through the<br>authoritative route.

Vault and OpenBao KV v2 caches are<br>the only providers that handle max_age as server-side expiry properly.

None of SecretSpec’s current local providers has strong native support for<br>expiry. They can remove an expired entry the next time SecretSpec sees it, but<br>cannot ensure the local copy disappears at its deadline if SecretSpec never<br>runs again.

Our planned FactorSeal<br>provider in Future work is intended to close that gap with an<br>explicit API for credential eviction among the other goals.

Cross-secret validation<br>Section titled “Cross-secret validation”

Profiles can now express credential alternatives directly:

secretspec.toml[profiles.default]

PASSWORD = { description = "Password", required = { at_least_one = "auth" } }

ACCESS_TOKEN = { description = "Token", required = { at_least_one = "auth" } }

GITHUB_TOKEN = { description = "GitHub token", required = { exactly_one = "github_auth" } }

GITHUB_APP_KEY = { description = "GitHub App private key", required = { exactly_one = "github_auth" } }

The auth group accepts a password, an access token, or both. The<br>github_auth group requires exactly one credential and rejects configurations<br>that provide both the token and the app key.

New providers<br>Section titled “New providers”

SOPS brings the encrypted-file workflow from our recent<br>SOPS comparison behind SecretSpec’s<br>provider-independent CLI and SDKs. SecretSpec delegates encryption...

secretspec secrets providers scopes secret cache

Related Articles