<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>SecretSpec | Blog</title><description/><link>https://secretspec.dev/</link><language>en</language><item><title>SecretSpec 0.14: Secret References</title><link>https://secretspec.dev/blog/secretspec-0-14-secret-references/</link><guid isPermaLink="true">https://secretspec.dev/blog/secretspec-0-14-secret-references/</guid><pubDate>Thu, 09 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SecretSpec keeps a &lt;code dir=&quot;auto&quot;&gt;secretspec.toml&lt;/code&gt; that declares what secrets an application
needs, and resolves the values from a provider: your system keyring, 1Password,
Vault, a &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; file, and so on. Until now it stored every secret under a naming
convention it controlled, &lt;code dir=&quot;auto&quot;&gt;secretspec/{project}/{profile}/{key}&lt;/code&gt;, and that
convention was the only place it looked.&lt;/p&gt;
&lt;p&gt;That works when SecretSpec created the secret. It does not when the secret
already exists under a name something else chose: a &lt;code dir=&quot;auto&quot;&gt;db&lt;/code&gt; item in a 1Password
vault, a &lt;code dir=&quot;auto&quot;&gt;myapp/config&lt;/code&gt; path at a Vault mount, an environment variable your
platform already sets. To manage such a secret you had to copy its value into
SecretSpec’s convention, leaving two copies to rotate, or leave it out of
SecretSpec entirely.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/cachix/secretspec/releases/tag/v0.14.0&quot; title=&quot;SecretSpec 0.14 release&quot;&gt;SecretSpec 0.14&lt;/a&gt;
introduces &lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt;. A secret can name one that already exists, by the store’s own
coordinates, and SecretSpec reads and writes that secret in place:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[profiles.production]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;DATABASE_URL&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Postgres DSN&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ref&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;password&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;providers&lt;/span&gt;&lt;span&gt; = [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;prod_op&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;] }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;DATABASE_URL&lt;/code&gt; now resolves from the &lt;code dir=&quot;auto&quot;&gt;password&lt;/code&gt; field of the 1Password item
&lt;code dir=&quot;auto&quot;&gt;db&lt;/code&gt;. SecretSpec does not prepend a project or profile, and does not create a
name of its own.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-not-just-paste-the-address&quot;&gt;Why not just paste the address&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;1Password will give you an address for that field: &lt;code dir=&quot;auto&quot;&gt;op://Production/db/password&lt;/code&gt;.
The obvious design is to accept that string in the config and be done. We built
that first and removed it.&lt;/p&gt;
&lt;p&gt;A string like &lt;code dir=&quot;auto&quot;&gt;op://Production/db/password&lt;/code&gt; names the store and the secret at
the same time, which ties the secret to 1Password. The same reference cannot
then resolve from Vault in CI and 1Password on a laptop, cannot be redirected at
a &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; fixture for a test run without editing the manifest, and does not
compose with a provider fallback chain, because the chain and the address
disagree about where the secret is.&lt;/p&gt;
&lt;p&gt;SecretSpec already decides which store to use, through providers, profiles,
fallback chains, and the &lt;code dir=&quot;auto&quot;&gt;--provider&lt;/code&gt; override. A &lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt; names only the secret
and leaves the store to that existing machinery.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;coordinates&quot;&gt;Coordinates&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A &lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt; is a table, not a URL. Each key names a level of structure that some
stores have:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;vault                which container holds the item        (1Password only)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;└── item             the store&apos;s own name for the secret   (always required)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└── section      a named group of fields               (1Password only)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└── field    one component inside the item          (structured stores)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└── version   which revision to read            (GCSM only)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Only &lt;code dir=&quot;auto&quot;&gt;item&lt;/code&gt; is required, because every store names its secrets somehow. &lt;code dir=&quot;auto&quot;&gt;item&lt;/code&gt;
is the complete name: it replaces the whole convention path, with no project or
profile and no folder prefix prepended. &lt;code dir=&quot;auto&quot;&gt;ref = { item = &quot;GITHUB_PAT&quot; }&lt;/code&gt; on the
env provider reads the environment variable &lt;code dir=&quot;auto&quot;&gt;GITHUB_PAT&lt;/code&gt; and nothing else.&lt;/p&gt;
&lt;p&gt;The other keys refine &lt;code dir=&quot;auto&quot;&gt;item&lt;/code&gt; for stores that have that structure. A &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; key
holds a single value, so &lt;code dir=&quot;auto&quot;&gt;field&lt;/code&gt; on a dotenv ref is not meaningful. A Vault KV
entry is a map, so &lt;code dir=&quot;auto&quot;&gt;field&lt;/code&gt; is required. When a store has no equivalent for a
coordinate it reports an error naming that coordinate, rather than reading a
different secret:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;GitHub token&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ref&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;GITHUB_PAT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;providers&lt;/span&gt;&lt;span&gt; = [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;] }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Error: Provider operation failed: the env provider does not support the `field` coordinate. Drop `field` from the ref for `GITHUB_PAT`.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;All eleven providers resolve refs, and each rejects the coordinates it cannot
represent. A store whose secrets have no internal parts gets that rejection from
shared code, without any per-provider work.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;references-name-providers-route&quot;&gt;References name, providers route&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A &lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt; supplies the name only. Which provider resolves it follows the normal
&lt;a href=&quot;https://secretspec.dev/concepts/providers/&quot;&gt;provider resolution order&lt;/a&gt;: a &lt;code dir=&quot;auto&quot;&gt;--provider&lt;/code&gt; override, then
the secret’s &lt;code dir=&quot;auto&quot;&gt;providers&lt;/code&gt; chain, then profile and global defaults. That is the
same order every other secret uses.&lt;/p&gt;
&lt;p&gt;Because the store is not part of the reference, the same &lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt; works across
providers. Each provider in a fallback chain is asked for the same coordinates,
and one that cannot interpret them logs a warning and the chain continues:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[profiles.production]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;DATABASE_URL&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Postgres DSN&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ref&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;db&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;field&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;password&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; }, &lt;/span&gt;&lt;span&gt;providers&lt;/span&gt;&lt;span&gt; = [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;onepassword://Production&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;keyring&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;] }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Chain entries can also be inline &lt;code dir=&quot;auto&quot;&gt;scheme://&lt;/code&gt; URIs, as above, with no
&lt;code dir=&quot;auto&quot;&gt;[providers]&lt;/code&gt; alias declared first.&lt;/p&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;--provider&lt;/code&gt; override redirects a referenced secret the same way it
redirects a conventional one, so pointing a whole suite at a &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; fixture needs
no change to the manifest:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--provider&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dotenv:.env.fixtures&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cargo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;writing-through-a-ref&quot;&gt;Writing through a ref&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Reads and writes use the same coordinates. &lt;code dir=&quot;auto&quot;&gt;secretspec set&lt;/code&gt; and interactive
&lt;code dir=&quot;auto&quot;&gt;check&lt;/code&gt; write to the referenced secret in place wherever the store supports
writes:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DATABASE_URL&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# writes the `password` field of the 1Password item `db`, in place&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;1Password edits the field with &lt;code dir=&quot;auto&quot;&gt;op item edit&lt;/code&gt; and does not create items.
Keyring, pass, dotenv, Bitwarden, Proton Pass, and LastPass write their refs as
well. Vault, AWS Secrets Manager, and Google Secret Manager are read-only for
refs and report that directly, rather than claiming the provider cannot write at
all.&lt;/p&gt;
&lt;p&gt;A &lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt; also composes with &lt;code dir=&quot;auto&quot;&gt;generate&lt;/code&gt;. If the referenced secret does not exist
yet, SecretSpec generates the value and writes it to the coordinates, so the
first &lt;code dir=&quot;auto&quot;&gt;check&lt;/code&gt; populates the item everything else already reads.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;faster-resolution&quot;&gt;Faster resolution&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;check&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;run&lt;/code&gt;, and the SDKs now group secrets by store and fetch the groups
concurrently instead of one store after another. Within a group, referenced
secrets use the store’s bulk API where it has one (AWS &lt;code dir=&quot;auto&quot;&gt;BatchGetSecretValue&lt;/code&gt;, and
the single Bitwarden, Proton Pass, and 1Password listings) and otherwise resolve
concurrently, fetching each unique coordinate once. CLI authentication for
1Password, LastPass, and Proton Pass is probed once per account or session
instead of once per provider instance.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;upgrading&quot;&gt;Upgrading&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cargo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Three changes to be aware of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code dir=&quot;auto&quot;&gt;onepassword://&lt;/code&gt; URI carrying an item path used to drop the path and target a
vault literally named &lt;code dir=&quot;auto&quot;&gt;vault&lt;/code&gt;. Item paths, including pasted
&lt;code dir=&quot;auto&quot;&gt;op://vault/item/field&lt;/code&gt; strings, now fail with an error that gives the &lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt;
table to write instead. Provider URIs are store addresses only.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt; is always a table. String and URI forms are rejected, with the same
translation in the error.&lt;/li&gt;
&lt;li&gt;Manifest validation now runs on every load. Rules that &lt;code dir=&quot;auto&quot;&gt;secretspec.toml&lt;/code&gt;
documents (a required secret cannot have a &lt;code dir=&quot;auto&quot;&gt;default&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;generate&lt;/code&gt; needs a
&lt;code dir=&quot;auto&quot;&gt;type&lt;/code&gt;, ref coordinates must be non-empty) are enforced on load rather than
ignored. A manifest that violated one of them will now fail with a clear error.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See &lt;a href=&quot;https://secretspec.dev/concepts/references/&quot;&gt;Secret References&lt;/a&gt; for the full model and the
&lt;a href=&quot;https://secretspec.dev/reference/configuration/#secret-references&quot;&gt;configuration reference&lt;/a&gt; for how
each provider maps the coordinates. Questions or feedback? Join us on
&lt;a href=&quot;https://discord.gg/naMgvexb6q&quot;&gt;Discord&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>SecretSpec 0.13: SDKs for Python, Node.js, Go, Ruby, and Haskell</title><link>https://secretspec.dev/blog/secretspec-0-13-sdks/</link><guid isPermaLink="true">https://secretspec.dev/blog/secretspec-0-13-sdks/</guid><pubDate>Fri, 03 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;SecretSpec separates &lt;em&gt;what&lt;/em&gt; secrets an application needs, declared in
&lt;code dir=&quot;auto&quot;&gt;secretspec.toml&lt;/code&gt;, from &lt;em&gt;where&lt;/em&gt; the values live, a provider like your system
keyring, 1Password, or Vault. Until now, reading those resolved secrets at
runtime meant the CLI or the Rust SDK. If your service was written in Python or
Go, you shelled out to &lt;code dir=&quot;auto&quot;&gt;secretspec run&lt;/code&gt; or reimplemented resolution yourself.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/cachix/secretspec/releases/tag/v0.13.0&quot; title=&quot;SecretSpec 0.13 release&quot;&gt;SecretSpec 0.13&lt;/a&gt;
closes that gap. It ships native SDKs for five languages: Python,
Node.js / TypeScript, Go, Ruby, and Haskell. Each resolves the exact secrets your
manifest declares, through the same providers, profiles, fallback chains, and
generators as the CLI, with no per-language configuration.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;native-bindings-over-one-resolver&quot;&gt;Native bindings over one resolver&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every SDK is a thin client over the same Rust core that powers the CLI. No
provider logic, profile resolution, chain fallback, &lt;code dir=&quot;auto&quot;&gt;as_path&lt;/code&gt; materialization, or
secret generation lives in the binding. A provider added to SecretSpec works in
every language the day it lands, and every SDK behaves identically.&lt;/p&gt;
&lt;p&gt;The binding strategy is chosen per ecosystem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Python&lt;/strong&gt;: a pyo3 extension, statically linked, shipped as a self-contained
&lt;code dir=&quot;auto&quot;&gt;cp39-abi3&lt;/code&gt; wheel.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js&lt;/strong&gt;: a napi-rs addon with prebuilt per-platform packages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ruby&lt;/strong&gt;: a native C extension (mkmf) with the resolver statically linked into
a platform gem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Go&lt;/strong&gt;: the &lt;code dir=&quot;auto&quot;&gt;secretspec-ffi&lt;/code&gt; C ABI loaded at runtime via
&lt;a href=&quot;https://github.com/ebitengine/purego&quot;&gt;purego&lt;/a&gt; (no cgo).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Haskell&lt;/strong&gt;: the same C ABI, linked at build time through the Haskell FFI.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;the-same-three-steps-in-your-language&quot;&gt;The same three steps, in your language&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Each SDK mirrors the vocabulary of the Rust derive crate: a builder that takes a
provider, a profile, and an access reason, then &lt;code dir=&quot;auto&quot;&gt;load()&lt;/code&gt; to resolve, then a map
of secrets you can read or export into the environment.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Python&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; secretspec &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; SecretSpec&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;resolved &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;SecretSpec.&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;with_provider&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;keyring://&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;with_profile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;production&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;with_reason&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;boot web app&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;load&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;resolved.secrets&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;DATABASE_URL&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;.get&lt;/span&gt;&lt;span&gt;)  &lt;/span&gt;&lt;span&gt;# value, or file path for as_path&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;resolved.&lt;/span&gt;&lt;span&gt;set_as_env&lt;/span&gt;&lt;span&gt;()                         &lt;/span&gt;&lt;span&gt;# export into os.environ&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Node.js / TypeScript&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const { &lt;/span&gt;&lt;span&gt;SecretSpec&lt;/span&gt;&lt;span&gt; } = &lt;/span&gt;&lt;span&gt;require&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;resolved&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;SecretSpec&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;withProvider&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;keyring://&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;withProfile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;production&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;withReason&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;boot web app&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;load&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;resolved&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;secrets&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;DATABASE_URL&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;()); &lt;/span&gt;&lt;span&gt;// value, or as_path file path&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;resolved&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setAsEnv&lt;/span&gt;&lt;span&gt;();                              &lt;/span&gt;&lt;span&gt;// export into process.env&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Go&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;resolved&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;err&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;New&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;WithProvider&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;keyring://&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;WithProfile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;production&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;WithReason&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;boot web app&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Load&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Println&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;resolved&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Secrets&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;DATABASE_URL&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;Get&lt;/span&gt;&lt;span&gt;()) &lt;/span&gt;&lt;span&gt;// value, or as_path file path&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;resolved&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;SetAsEnv&lt;/span&gt;&lt;span&gt;()                                 &lt;/span&gt;&lt;span&gt;// export into the environment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Ruby&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;resolved&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Secretspec&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;SecretSpec&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;builder&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                                 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;with_provider&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;keyring://&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                                 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;with_profile&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;production&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                                 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;with_reason&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;boot web app&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                                 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;load&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;puts&lt;/span&gt;&lt;span&gt; resolved.&lt;/span&gt;&lt;span&gt;secrets&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;DATABASE_URL&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;# value, or as_path file path&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;resolved.&lt;/span&gt;&lt;span&gt;set_as_env!&lt;/span&gt;&lt;span&gt;                      &lt;/span&gt;&lt;span&gt;# export into ENV&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;-- Haskell&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;resolved &lt;/span&gt;&lt;span&gt;&amp;#x3C;-&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;S.load&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;( S.builder&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt; S.withProvider &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;keyring://&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt; S.withProfile &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;production&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&amp;#x26;&lt;/span&gt;&lt;span&gt; S.withReason &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;boot web app&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;S.setAsEnv resolved &lt;/span&gt;&lt;span&gt;-- export into the environment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Across all of them, &lt;code dir=&quot;auto&quot;&gt;load()&lt;/code&gt; resolves every declared secret, a missing required
secret raises a typed &lt;code dir=&quot;auto&quot;&gt;MissingRequiredError&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;as_path&lt;/code&gt; secrets come back as
a readable file path with a cleanup that removes the backing temp file. The
access reason feeds the same audit log and &lt;code dir=&quot;auto&quot;&gt;require_reason&lt;/code&gt; policy from
&lt;a href=&quot;https://secretspec.dev/blog/secretspec-0-12-audit-logs-and-coding-agents/&quot;&gt;0.12&lt;/a&gt;, so a Go service is
as accountable as the CLI.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;write-your-own-binding&quot;&gt;Write your own binding&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Under all five SDKs sits a new crate, &lt;code dir=&quot;auto&quot;&gt;secretspec-ffi&lt;/code&gt;: a small, versioned C ABI
for resolving secrets. If we do not ship your language yet, you can bind to it
directly. It also exposes the public Rust building blocks the SDKs share,
&lt;code dir=&quot;auto&quot;&gt;Secrets::resolve()&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;Secrets::report()&lt;/code&gt;, so a Rust program reaches the same
value-carrying and value-free entry points.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;typed-secrets-one-schema-for-every-language&quot;&gt;Typed secrets, one schema for every language&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;secretspec.toml&lt;/code&gt; already knows the shape of your secrets, so 0.13 can hand that
shape to your type system. &lt;code dir=&quot;auto&quot;&gt;secretspec schema&lt;/code&gt; emits a JSON Schema for your
manifest, the union of all profiles or one profile with &lt;code dir=&quot;auto&quot;&gt;--profile&lt;/code&gt;. Pipe it
through &lt;a href=&quot;https://quicktype.io&quot;&gt;quicktype&lt;/a&gt; to generate idiomatic typed classes in
any language, then populate them from each SDK’s &lt;code dir=&quot;auto&quot;&gt;fields()&lt;/code&gt; map:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;schema&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;quicktype&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-s&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;schema&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--top-level&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SecretSpec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--lang&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-o&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;secrets_gen.py&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;typed &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Secrets.&lt;/span&gt;&lt;span&gt;from_dict&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;resolved.&lt;/span&gt;&lt;span&gt;fields&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;typed.database_url&lt;/span&gt;&lt;span&gt;)  &lt;/span&gt;&lt;span&gt;# typed str&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;One schema drives every language’s type system, with no hand-written emitter per
language.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;install&quot;&gt;Install&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;pip&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;span&gt;                              &lt;/span&gt;&lt;span&gt;# Python&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;span&gt;                              &lt;/span&gt;&lt;span&gt;# Node.js / TypeScript&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;gem&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;span&gt;                              &lt;/span&gt;&lt;span&gt;# Ruby&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;go&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;github.com/cachix/secretspec/secretspec-go&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span&gt;# Go&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;For Haskell, add &lt;code dir=&quot;auto&quot;&gt;secretspec&lt;/code&gt; from Hackage to your &lt;code dir=&quot;auto&quot;&gt;build-depends&lt;/code&gt;. The CLI and
Rust SDK upgrade as usual:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cargo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;See the &lt;a href=&quot;https://secretspec.dev/sdk/overview/&quot;&gt;SDK overview&lt;/a&gt; for the per-language guides. Questions or
feedback? Join us on &lt;a href=&quot;https://discord.gg/naMgvexb6q&quot;&gt;Discord&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>SecretSpec 0.12: audit logs and coding agents</title><link>https://secretspec.dev/blog/secretspec-0-12-audit-logs-and-coding-agents/</link><guid isPermaLink="true">https://secretspec.dev/blog/secretspec-0-12-audit-logs-and-coding-agents/</guid><pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A coding agent reaches for the same secrets you do, but on its own initiative and
many times a session: a read looks identical whether it came from you running a
deploy or an agent exploring the codebase.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/cachix/secretspec/releases/tag/v0.12.0&quot; title=&quot;SecretSpec 0.12 release&quot;&gt;SecretSpec 0.12&lt;/a&gt;
makes that access accountable. It ships three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Audit log&lt;/strong&gt; — every secret read and write is appended to a local,
per-user JSONL log. On by default. Values are never recorded.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reason-on-access&lt;/strong&gt; — secret access can require a human-readable reason,
enforced for coding agents by default.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;secretspec audit&lt;/code&gt; command&lt;/strong&gt; — filter and summarize the log, or pipe raw
JSON Lines to &lt;code dir=&quot;auto&quot;&gt;jq&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;aside aria-label=&quot;Behavior change in 0.12&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Behavior change in 0.12&lt;/p&gt;&lt;div&gt;&lt;p&gt;If you run SecretSpec inside a coding agent, secret access now &lt;strong&gt;fails&lt;/strong&gt; until a
reason is supplied. This is the new default (&lt;code dir=&quot;auto&quot;&gt;require_reason = &quot;agents&quot;&lt;/code&gt;). Opt
out with &lt;code dir=&quot;auto&quot;&gt;require_reason = false&lt;/code&gt; in the &lt;code dir=&quot;auto&quot;&gt;[project]&lt;/code&gt; table. Existing providers
and library callers keep working unchanged. See &lt;a href=&quot;#upgrading&quot;&gt;Upgrading&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;h2 id=&quot;the-audit-log&quot;&gt;The audit log&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Every secret read and write, from the CLI and the Rust SDK, is appended to a
local log as &lt;a href=&quot;https://jsonlines.org/&quot;&gt;JSON Lines&lt;/a&gt;, one event per line. Secret
&lt;strong&gt;values are never written&lt;/strong&gt;, only metadata: the secret name, the profile, the
provider that served it (with any embedded credentials redacted), the outcome,
the reason, and who was asking, including the detected coding agent.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;v&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;ts&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;2026-06-04T17:04:00.893Z&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;action&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;project&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;my-app&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;profile&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;production&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;key&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;DATABASE_URL&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;provider&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;keyring://&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;outcome&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;found&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;reason&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;deploy web frontend&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;actor&quot;&lt;/span&gt;&lt;span&gt;: { &lt;/span&gt;&lt;span&gt;&quot;user&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;alice&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;agent&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;claude-code&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;is_agent&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;version&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;0.12.0&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The log lives in your per-user state directory
(&lt;code dir=&quot;auto&quot;&gt;~/.local/state/secretspec/audit.log&lt;/code&gt;) and is created readable only by you. Read
it with any tool, or use the new &lt;code dir=&quot;auto&quot;&gt;secretspec audit&lt;/code&gt; command for filtering and a
readable summary:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Last 20 entries, formatted&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;audit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-n&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Only `run` events for one project&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;audit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--project&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my-app&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--action&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Raw JSON Lines, piped to jq&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;audit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--json&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;jq&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;select(.outcome == &quot;missing&quot;)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It is configured in your &lt;strong&gt;user-global config&lt;/strong&gt;
(&lt;code dir=&quot;auto&quot;&gt;~/.config/secretspec/config.toml&lt;/code&gt;), not the project’s &lt;code dir=&quot;auto&quot;&gt;secretspec.toml&lt;/code&gt;, so a
repository you clone can’t quietly turn off or redirect your audit log. The log is
a single file capped at 1 MiB, a size-bounded recent record rather than permanent
compliance history; forward it to a central system if you need that. To turn it
off entirely:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;~/.config/secretspec/config.toml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[audit]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;enabled&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;See &lt;a href=&quot;https://secretspec.dev/concepts/audit/&quot;&gt;Audit Logging&lt;/a&gt; for the full record schema and options.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;supplying-a-reason&quot;&gt;Supplying a reason&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;When a coding agent like Claude Code reaches for a secret without a reason, the
access is refused and the agent is told exactly what to do next:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;$ secretspec run -- npm test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Error: Accessing secrets requires a reason. Provide one with --reason&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&quot;&amp;#x3C;why you are accessing these secrets&gt;&quot;, the SECRETSPEC_REASON environment&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;variable, or Secrets::with_reason() in the SDK. (Policy: require_reason in&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[project] of secretspec.toml — defaults to &quot;agents&quot;; set it to false to&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;disable.)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Claude Code reads that message, states why it needs the secret, and retries:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--reason&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;run the test suite before opening a PR&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;npm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Both the refusal and the successful retry land in the audit log, so the reason
is tied to the access. There are three ways to supply a reason:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Source&lt;/th&gt;&lt;th&gt;Scope&lt;/th&gt;&lt;th&gt;Precedence&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;--reason&lt;/code&gt; flag&lt;/td&gt;&lt;td&gt;CLI&lt;/td&gt;&lt;td&gt;highest&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;Secrets::with_reason()&lt;/code&gt;&lt;/td&gt;&lt;td&gt;SDK&lt;/td&gt;&lt;td&gt;overrides env&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;SECRETSPEC_REASON&lt;/code&gt;&lt;/td&gt;&lt;td&gt;CLI + SDK + derive&lt;/td&gt;&lt;td&gt;lowest&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# CLI: the most explicit option, overrides the others&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;run&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--reason&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;deploying release 0.12&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./deploy.sh&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// SDK: the programmatic equivalent of --reason&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;secrets&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Secrets&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span&gt;load&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/* ... */&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;with_reason&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;nightly backup job&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Env: lowest precedence, but honored everywhere&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SECRETSPEC_REASON&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;nightly backup job&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;SECRETSPEC_REASON&lt;/code&gt; is resolved by &lt;code dir=&quot;auto&quot;&gt;Secrets::load&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;load_from&lt;/code&gt;, which means
&lt;code dir=&quot;auto&quot;&gt;secretspec-derive&lt;/code&gt;-generated code and other library callers satisfy the policy
and supply an audit reason &lt;strong&gt;without any code changes&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Whichever path you use, blank or whitespace-only reasons are ignored, so they
can’t quietly satisfy the policy. Under the hood this is backed by a new
&lt;code dir=&quot;auto&quot;&gt;Provider::set_reason&lt;/code&gt; trait method (a no-op by default), so existing providers
keep working unchanged.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;configuring-when-a-reason-is-required&quot;&gt;Configuring when a reason is required&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The new &lt;code dir=&quot;auto&quot;&gt;require_reason&lt;/code&gt; policy in the &lt;code dir=&quot;auto&quot;&gt;[project]&lt;/code&gt; table controls when a reason
is mandatory:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[project]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;my-app&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;require_reason&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;agents&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;# require it from agents (default), or true / false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;&quot;agents&quot;&lt;/code&gt; (the default): require a reason only when a coding agent is detected.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;true&lt;/code&gt;: require it from every caller.&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;false&lt;/code&gt;: never require it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because the policy lives in &lt;code dir=&quot;auto&quot;&gt;secretspec.toml&lt;/code&gt; and is enforced by SecretSpec, it
applies to everyone and every CI runner, and is inherited through &lt;code dir=&quot;auto&quot;&gt;extends&lt;/code&gt;.
Coding agents are spotted by the
&lt;a href=&quot;https://crates.io/crates/detect-coding-agent&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;detect-coding-agent&lt;/code&gt;&lt;/a&gt; crate
(Claude Code, Cursor, Codex, Gemini CLI, Copilot, and more); set
&lt;code dir=&quot;auto&quot;&gt;SECRETSPEC_AGENT&lt;/code&gt; for a harness it doesn’t recognize.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;upgrading&quot;&gt;Upgrading&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;cargo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;secretspec&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Remember the new default: agents must pass a reason: set &lt;code dir=&quot;auto&quot;&gt;require_reason = false&lt;/code&gt;
to opt out.&lt;/p&gt;
&lt;p&gt;Questions or feedback? Join us on &lt;a href=&quot;https://discord.gg/naMgvexb6q&quot;&gt;Discord&lt;/a&gt;.&lt;/p&gt;</content:encoded></item></channel></rss>