<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Monosecret | Blog</title><description/><link>https://ifiokjr.github.io/</link><language>en</language><item><title>Claude Code Stores OAuth Tokens in Plaintext</title><link>https://ifiokjr.github.io/monosecret/blog/claude-code-stores-oauth-tokens-in-plaintext/</link><guid isPermaLink="true">https://ifiokjr.github.io/monosecret/blog/claude-code-stores-oauth-tokens-in-plaintext/</guid><pubDate>Wed, 02 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Claude Code’s &lt;a href=&quot;https://code.claude.com/docs/en/mcp#authenticate-with-remote-mcp-servers&quot;&gt;MCP documentation says authentication tokens are “stored
securely”&lt;/a&gt;.
On Linux, that currently means plaintext JSON protected by file permissions.&lt;/p&gt;
&lt;p&gt;I checked Claude Code 2.1.257 after authenticating to several remote MCP
servers. The file &lt;code dir=&quot;auto&quot;&gt;~/.claude/.credentials.json&lt;/code&gt; had mode &lt;code dir=&quot;auto&quot;&gt;0600&lt;/code&gt;, as it should,
but it also contained a top-level &lt;code dir=&quot;auto&quot;&gt;mcpOAuth&lt;/code&gt; object with the access tokens.
Here is the shape of one Cloudflare entry, with every credential value
redacted:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;~/.claude/.credentials.json&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;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;mcpOAuth&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;cloudflare-observability|…&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;accessToken&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&amp;#x3C;redacted&gt;&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;clientId&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&amp;#x3C;redacted&gt;&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;discoveryState&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&amp;#x3C;redacted&gt;&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;redirectUri&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&amp;#x3C;redacted&gt;&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;serverName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;cloudflare-observability&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;serverUrl&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&amp;#x3C;redacted&gt;&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;&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;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This matches Anthropic’s &lt;a href=&quot;https://code.claude.com/docs/en/team#credential-management&quot;&gt;credential-management documentation&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;macOS:&lt;/strong&gt; uses the encrypted macOS Keychain, falling back to
&lt;code dir=&quot;auto&quot;&gt;~/.claude/.credentials.json&lt;/code&gt; when the Keychain is unavailable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux:&lt;/strong&gt; uses &lt;code dir=&quot;auto&quot;&gt;~/.claude/.credentials.json&lt;/code&gt; with mode &lt;code dir=&quot;auto&quot;&gt;0600&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Windows:&lt;/strong&gt; uses &lt;code dir=&quot;auto&quot;&gt;%USERPROFILE%\.claude\.credentials.json&lt;/code&gt;, inheriting the
access controls of the user’s profile directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is a much narrower claim than most people hear when a product says a
credential is “stored securely.”&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;oauth-did-not-solve-secret-storage&quot;&gt;OAuth did not solve secret storage&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The browser flow makes the secret easy to miss. Run &lt;code dir=&quot;auto&quot;&gt;claude mcp login&lt;/code&gt;, 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.&lt;/p&gt;
&lt;p&gt;But Claude Code still received a credential. It must persist that credential
if the connection is to survive a restart.&lt;/p&gt;
&lt;p&gt;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 &lt;a href=&quot;https://code.claude.com/docs/en/mcp#authenticate-with-remote-mcp-servers&quot;&gt;MCP documentation&lt;/a&gt;
also lets users pin the scopes Claude Code requests.&lt;/p&gt;
&lt;p&gt;What OAuth does not specify is a secure local vault.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Property&lt;/th&gt;&lt;th&gt;OAuth credential&lt;/th&gt;&lt;th&gt;Scoped API token&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Must be stored by the client&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Can have limited permissions&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Can expire&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Can be revoked independently&lt;/td&gt;&lt;td&gt;Usually&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Can be replayed if stolen&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Standard interactive delegation&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;Provider-specific&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Standard automatic renewal&lt;/td&gt;&lt;td&gt;Often&lt;/td&gt;&lt;td&gt;Usually external&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;claude-code-needs-a-credential-store-interface&quot;&gt;Claude Code needs a credential-store interface&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Claude Code should not decide that every Linux user’s MCP tokens belong in the
same plaintext file. The persistence layer should be replaceable:&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;Claude Code OAuth client&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;&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;credential-store interface&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;&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;Monosecret TypeScript SDK&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;&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;user-selected Monosecret provider&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The right integration point is the &lt;a href=&quot;https://ifiokjr.github.io/sdk/nodejs/&quot;&gt;Monosecret Node.js / TypeScript SDK&lt;/a&gt;. 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 Monosecret to load, save, or delete it using the
provider the user or organization selected.&lt;/p&gt;
&lt;p&gt;Monosecret 0.20 has &lt;a href=&quot;https://ifiokjr.github.io/concepts/providers/#available-providers&quot;&gt;33 provider integrations&lt;/a&gt;. 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.&lt;/p&gt;
&lt;p&gt;The current TypeScript SDK exposes Monosecret’s provider-independent resolver.
We would add the small &lt;code dir=&quot;auto&quot;&gt;get&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;set&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;delete&lt;/code&gt; credential-store surface Claude
Code needs rather than reimplement 33 integrations in its codebase.&lt;/p&gt;
&lt;p&gt;For example, set the &lt;a href=&quot;https://ifiokjr.github.io/providers/keyring/&quot;&gt;system keyring&lt;/a&gt; as the default
provider in the local user configuration:&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;monosecret&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;global&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&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;keyring&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--profile&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A team might instead require &lt;a href=&quot;https://ifiokjr.github.io/providers/openbao/&quot;&gt;OpenBao&lt;/a&gt; or a cloud secret
manager. A headless workstation might use an &lt;a href=&quot;https://ifiokjr.github.io/providers/age/&quot;&gt;age-encrypted store&lt;/a&gt;. The OAuth flow would stay exactly the same; only
persistence would change.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What Codex does:&lt;/strong&gt; Codex makes MCP OAuth storage configurable. Its
&lt;a href=&quot;https://learn.chatgpt.com/docs/config-file/config-reference#mcp_oauth_credentials_store&quot;&gt;configuration reference&lt;/a&gt;
documents &lt;code dir=&quot;auto&quot;&gt;auto&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;file&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;keyring&lt;/code&gt; backends. Setting
&lt;code dir=&quot;auto&quot;&gt;mcp_oauth_credentials_store = &quot;keyring&quot;&lt;/code&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Coming in Monosecret 0.21:&lt;/strong&gt; We are working on &lt;a href=&quot;https://github.com/ifiokjr/monosecret/pull/362&quot;&gt;versioned resolver and provider IPC&lt;/a&gt; for
zero-dependency integrations. Applications will be able to use Monosecret
providers over a local protocol without embedding an SDK or provider code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div&gt;&lt;h2 id=&quot;making-open-source-software-more-secure&quot;&gt;Making open source software more secure&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We are working to make open source tools retrieve credentials from a
user-selected secret store instead of copying them into another plaintext
file. Monosecret now provides &lt;a href=&quot;https://ifiokjr.github.io/integrations/git/&quot;&gt;Git&lt;/a&gt; and
&lt;a href=&quot;https://ifiokjr.github.io/integrations/docker/&quot;&gt;Docker&lt;/a&gt; credential helpers, and we have proposed a
generic, operation-scoped &lt;a href=&quot;https://github.com/NixOS/nix/pull/16339&quot;&gt;secret resolver interface for Nix&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Because these projects are open source, we can inspect their credential
boundaries and contribute safer ones upstream.&lt;/p&gt;
&lt;p&gt;We cannot make the equivalent fix in Claude Code. Its &lt;a href=&quot;https://github.com/anthropics/claude-code&quot;&gt;public repository&lt;/a&gt; does not include the
core CLI implementation, and its &lt;a href=&quot;https://github.com/anthropics/claude-code/blob/main/LICENSE.md&quot;&gt;license is all rights reserved&lt;/a&gt;.
We can support the &lt;a href=&quot;https://github.com/anthropics/claude-code/issues/73582&quot;&gt;open request for secure, pluggable credential
storage&lt;/a&gt; and propose a
Monosecret TypeScript integration, but only Anthropic can change Claude Code’s
MCP OAuth storage today.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</content:encoded></item><item><title>We Are Forking dotenvy into dotenv-ng</title><link>https://ifiokjr.github.io/monosecret/blog/we-are-forking-dotenvy-into-dotenv-ng/</link><guid isPermaLink="true">https://ifiokjr.github.io/monosecret/blog/we-are-forking-dotenvy-into-dotenv-ng/</guid><pubDate>Sat, 15 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We have released &lt;a href=&quot;https://github.com/cachix/dotenv-ng&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;dotenv-ng&lt;/code&gt;&lt;/a&gt; 1.0, a
modern Rust implementation for loading and rendering &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; files. It began as
a fork of &lt;a href=&quot;https://github.com/allan2/dotenvy&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;dotenvy&lt;/code&gt;&lt;/a&gt; after its parser
changed a secret while reading it.&lt;/p&gt;
&lt;p&gt;That may sound contradictory. Monosecret is still on a mission to &lt;a href=&quot;https://ifiokjr.github.io/blog/secrets-dont-belong-in-config/&quot;&gt;eliminate
environment variables as a secrets
interface&lt;/a&gt;, and we have written about
&lt;a href=&quot;https://ifiokjr.github.io/blog/where-env-went-wrong/&quot;&gt;where &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; went wrong&lt;/a&gt;. It should not be the
final home of a secret.&lt;/p&gt;
&lt;p&gt;But migrating away from &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; starts with reading it
correctly.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-fork-dotenvy&quot;&gt;Why fork dotenvy?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The immediate failure was &lt;a href=&quot;https://github.com/cachix/monosecret/issues/73&quot;&gt;Monosecret issue #73&lt;/a&gt;. A dotenv file contained a
value with bcrypt fragments:&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;TEST&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;foo:$2a$10$TWoviNHS27HJMw1PKe4tBeIMlms6tWdYS9hKoHANKCQhluDlEt/gu&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The file was intact. Reading it through the dotenv provider returned a
different value because &lt;code dir=&quot;auto&quot;&gt;dotenvy&lt;/code&gt; treated the dollar-prefixed fragments as
variable substitutions. The failure appeared later as an authentication error,
not a parse error.&lt;/p&gt;
&lt;p&gt;An upstream request to make substitution configurable had been &lt;a href=&quot;https://github.com/allan2/dotenvy/issues/113&quot;&gt;open since 2024&lt;/a&gt;. A &lt;a href=&quot;https://github.com/allan2/dotenvy/pull/167&quot;&gt;pull request&lt;/a&gt; arrived in 2026 but
targeted an unreleased API. A migration tool cannot require users to recognize
and escape parser syntax inside their secrets.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-maintenance-gap&quot;&gt;The maintenance gap&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The original Rust &lt;code dir=&quot;auto&quot;&gt;dotenv&lt;/code&gt; crate stopped releasing in 2020 and was eventually
marked &lt;a href=&quot;https://rustsec.org/advisories/RUSTSEC-2021-0141.html&quot;&gt;unmaintained by RustSec&lt;/a&gt;, which listed
dotenvy as an alternative.&lt;/p&gt;
&lt;p&gt;Dotenvy’s description still calls it “a well-maintained fork.” Its latest
published version, &lt;a href=&quot;https://github.com/allan2/dotenvy/releases/tag/v0.15.7&quot;&gt;0.15.7, was released on March 22, 2023&lt;/a&gt;. A &lt;a href=&quot;https://users.rust-lang.org/t/recommended-crate-for-storing-keys-for-web-site-database/133305/11&quot;&gt;Rust forum discussion&lt;/a&gt;
noted the two-year release gap in 2025. By the time the bcrypt bug blocked
Monosecret, it was more than three years.&lt;/p&gt;
&lt;p&gt;There is an uncomfortable irony in a maintained fork repeating its upstream’s
release problem. Its maintainers do not owe us a release, but Monosecret needed
breaking fixes on a schedule we control.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-does-dotenv-ng-improve-upon&quot;&gt;What does dotenv-ng improve upon?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We first considered a small patch. Auditing the parser uncovered more problems
around JSON, Windows paths, Unicode names, precedence, and partial environment
mutation.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;dotenv-ng&lt;/code&gt; therefore starts from dotenvy 0.15.7 but deliberately breaks
compatibility where correctness requires it. Version 1.0 adds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a source-aware parser with structured errors;&lt;/li&gt;
&lt;li&gt;literal dollar signs by default, with substitution available only when a
caller explicitly enables it;&lt;/li&gt;
&lt;li&gt;a broader key grammar that supports dashes, leading digits, leading dots,
and Unicode;&lt;/li&gt;
&lt;li&gt;a renderer that adds only the quoting and escaping needed to parse a value
back unchanged;&lt;/li&gt;
&lt;li&gt;validation before process-environment mutation; and&lt;/li&gt;
&lt;li&gt;an explicit &lt;code dir=&quot;auto&quot;&gt;unsafe&lt;/code&gt; boundary around that mutation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The complete compatibility and API changes are recorded in the &lt;a href=&quot;https://github.com/cachix/dotenv-ng/blob/v1.0.0/CHANGELOG.md&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;dotenv-ng&lt;/code&gt; 1.0 changelog&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;try-dotenv-ng-10&quot;&gt;Try dotenv-ng 1.0&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The package is available on &lt;a href=&quot;https://crates.io/crates/dotenv-ng&quot;&gt;crates.io&lt;/a&gt;.
Applications can keep the familiar &lt;code dir=&quot;auto&quot;&gt;dotenv&lt;/code&gt; crate name with a dependency
alias:&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;[dependencies]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;dotenv&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;package&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;dotenv-ng&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;version&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;1&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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Starting in Monosecret 0.20, dotenv-ng powers dotenv parsing and rendering
throughout Monosecret.&lt;/p&gt;</content:encoded></item><item><title>Monosecret 0.19: Moving and importing secrets between providers</title><link>https://ifiokjr.github.io/monosecret/blog/monosecret-0-19-moving-and-importing-secrets-between-providers/</link><guid isPermaLink="true">https://ifiokjr.github.io/monosecret/blog/monosecret-0-19-moving-and-importing-secrets-between-providers/</guid><pubDate>Tue, 11 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;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 &lt;code dir=&quot;auto&quot;&gt;API_KEY&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;DATABASE_URL&lt;/code&gt; at the end.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/cachix/monosecret/releases/tag/v0.19.0&quot; title=&quot;Monosecret 0.19 release&quot;&gt;Monosecret 0.19&lt;/a&gt;
treats those changes as a normal workflow instead of a one-off migration
script.&lt;/p&gt;
&lt;p&gt;This release includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;#provider-specific-storage-layouts&quot;&gt;Provider-specific storage layouts&lt;/a&gt;&lt;/strong&gt;: give each provider its own
address, transform stored values, import existing files, and preview exact
write references.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;#config-belongs-in-secrets&quot;&gt;Config belongs in secrets&lt;/a&gt;&lt;/strong&gt;: resolve
profile-specific config alongside stored secrets, generate ephemeral values,
and securely prompt during &lt;code dir=&quot;auto&quot;&gt;monosecret run&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;#passbolt-provider&quot;&gt;Passbolt provider&lt;/a&gt;&lt;/strong&gt;: read and write secrets in a
self-hosted Passbolt server, with credentials supplied by another provider
when needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;#faster-remote-provider-workflows&quot;&gt;Faster remote-provider workflows&lt;/a&gt;&lt;/strong&gt;: attach a cache directly to
an authoritative provider and batch 1Password field reads.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;#smaller-improvements&quot;&gt;Smaller improvements&lt;/a&gt;&lt;/strong&gt;: create standalone profiles
and install complete pkg-config metadata for native SDK consumers.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;provider-specific-storage-layouts&quot;&gt;Provider-specific storage layouts&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;API_KEY&lt;/code&gt; is the name used by your application. In 1Password, the same value
might be the &lt;code dir=&quot;auto&quot;&gt;token&lt;/code&gt; field of an item named &lt;code dir=&quot;auto&quot;&gt;old-api-item&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;a href=&quot;https://ifiokjr.github.io/concepts/references/&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt;&lt;/a&gt; gives Monosecret this store address. &lt;code dir=&quot;auto&quot;&gt;item&lt;/code&gt;
names the entry. Coordinates such as &lt;code dir=&quot;auto&quot;&gt;field&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;section&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;vault&lt;/code&gt; locate a
value inside structured stores. The &lt;code dir=&quot;auto&quot;&gt;providers&lt;/code&gt; list still decides which
stores to try.&lt;/p&gt;
&lt;p&gt;Before 0.19, every provider in a secret’s route received the same &lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt;, even
though stores such as 1Password and dotenv organize secrets differently. Now
each provider alias can template its usual layout, while &lt;code dir=&quot;auto&quot;&gt;refs.&amp;#x3C;alias&gt;&lt;/code&gt; handles
exceptions:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;monosecret.toml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[providers]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;legacy&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;onepassword://Legacy&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;production&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;uri&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;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&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;{project}-{profile}&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;{key}&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;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;local&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;uri&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;dotenv://.env&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;{key}&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;/div&gt;&lt;/div&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;API_KEY&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;description&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;API key&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;providers&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;span&gt;&quot;&lt;/span&gt;&lt;span&gt;local&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;refs&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;legacy&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;old-api-item&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;token&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;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;production&lt;/code&gt; reads the &lt;code dir=&quot;auto&quot;&gt;API_KEY&lt;/code&gt; field from a &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;project&gt;-production&lt;/code&gt; 1Password
item. The &lt;code dir=&quot;auto&quot;&gt;local&lt;/code&gt; fallback reads the dotenv key &lt;code dir=&quot;auto&quot;&gt;API_KEY&lt;/code&gt;. If &lt;code dir=&quot;auto&quot;&gt;legacy&lt;/code&gt; is
selected explicitly, &lt;code dir=&quot;auto&quot;&gt;refs.legacy&lt;/code&gt; reads the &lt;code dir=&quot;auto&quot;&gt;token&lt;/code&gt; field from &lt;code dir=&quot;auto&quot;&gt;old-api-item&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For each provider, &lt;code dir=&quot;auto&quot;&gt;refs.&amp;#x3C;alias&gt;&lt;/code&gt; takes precedence over the alias’s &lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt;
template, which takes precedence over the provider convention. Templates
accept &lt;code dir=&quot;auto&quot;&gt;{project}&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;{profile}&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;{key}&lt;/code&gt; in every address field. Existing
route-wide &lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt; declarations remain supported.&lt;/p&gt;
&lt;p&gt;Because scoped references also apply to imports, that exception can describe a
migration source without joining the normal fallback route:&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;monosecret&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;legacy&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--profile&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;production&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--delete-source&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This reads from &lt;code dir=&quot;auto&quot;&gt;refs.legacy&lt;/code&gt; and writes through the &lt;code dir=&quot;auto&quot;&gt;production&lt;/code&gt; template. It
also works between distinct entries in one physical store. Monosecret rejects
the import if both addresses resolve to the same entry.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;transform-stored-values&quot;&gt;Transform stored values&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Two new secret fields transform a stored value before it reaches the
application.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;extract&lt;/code&gt; selects a value from JSON with an
&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc6901&quot;&gt;RFC 6901 JSON Pointer&lt;/a&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;monosecret.toml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[providers]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;runtime&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;file:///run/secrets&lt;/span&gt;&lt;span&gt;&quot;&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;[profiles.production]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;DATABASE_PASSWORD&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;description&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Database password&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;providers&lt;/span&gt;&lt;span&gt; = [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;runtime&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;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;application.json&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;extract&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;format&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;pointer&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;/database/password&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;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;JSON strings become their unquoted contents. Numbers, booleans, objects, and
arrays keep their JSON representation. Extracted declarations are read-only.
&lt;code dir=&quot;auto&quot;&gt;set&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;delete&lt;/code&gt;, prompting, generation, and import cannot overwrite the source
document.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;encoding&lt;/code&gt; defines the textual representation in provider storage:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;monosecret.toml&lt;/span&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;TEXT_CONFIG&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;Encoded configuration&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;encoding&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;base64&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;CLIENT_KEYSTORE&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;description&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Binary client keystore&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;providers&lt;/span&gt;&lt;span&gt; = [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;runtime&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;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;client.p12.b64&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;encoding&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;base64&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;as_path&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;true&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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;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 &lt;code dir=&quot;auto&quot;&gt;as_path = true&lt;/code&gt; to materialize arbitrary bytes
in a file.&lt;/p&gt;
&lt;p&gt;Transforms run in this order:&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;provider or cache → encoding decode → JSON extraction → as_path&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This allows, for example, one declaration to decode a Base64-encoded JSON
document and select one field from it.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;import-without-reshaping-the-source&quot;&gt;Import without reshaping the source&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://ifiokjr.github.io/providers/file/&quot;&gt;File&lt;/a&gt;&lt;/strong&gt; stores one plaintext UTF-8 file per secret beneath
a required root. Convention paths use &lt;code dir=&quot;auto&quot;&gt;{project}/{profile}/{key}&lt;/code&gt;. &lt;code dir=&quot;auto&quot;&gt;ref.item&lt;/code&gt;
selects an existing relative path, including a file mounted at runtime.&lt;/p&gt;
&lt;p&gt;Writes use atomic replacement and create private Unix files and directories.
The provider rejects traversal and nested symlinks. It does not encrypt its
contents.&lt;/p&gt;
&lt;p&gt;The file provider is also a migration adapter for directories that already
contain one file per secret. A provider &lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt; template maps the source layout,
while the destination alias independently maps the same declarations into its
native store:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;monosecret.toml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[providers]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;legacy_files&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;uri&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;file:./old-secrets&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;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;{profile}/{key}&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;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;production&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;uri&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;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&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;{project}-{profile}&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;{key}&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;/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;[profiles.production.defaults]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;providers&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;/div&gt;&lt;/div&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;API_KEY&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;Production API key&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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&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;monosecret&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;legacy_files&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--profile&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;production&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--delete-source&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;For &lt;code dir=&quot;auto&quot;&gt;API_KEY&lt;/code&gt;, the source is &lt;code dir=&quot;auto&quot;&gt;old-secrets/production/API_KEY&lt;/code&gt;. The destination
is the &lt;code dir=&quot;auto&quot;&gt;API_KEY&lt;/code&gt; field in the &lt;code dir=&quot;auto&quot;&gt;&amp;#x3C;project&gt;-production&lt;/code&gt; 1Password item. The source
files do not need to follow the Monosecret convention. With
&lt;code dir=&quot;auto&quot;&gt;--delete-source&lt;/code&gt;, 0.19 preflights every mapped source and destination, verifies
every copied value, and only then removes the plaintext source files.&lt;/p&gt;
&lt;p&gt;Preflight, write, or verification failures leave every source untouched. A
destination with a different existing value keeps its corresponding source.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;see-the-reference-before-writing&quot;&gt;See the reference before writing&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;monosecret set&lt;/code&gt; and interactive &lt;code dir=&quot;auto&quot;&gt;monosecret check&lt;/code&gt; now print the resolved
write reference before reading a value:&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;$ monosecret set API_KEY --profile production --provider sops://secrets.enc.yaml&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Writing secret &apos;API_KEY&apos; to sops://secrets.enc.yaml?format=yaml (profile: production)&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;target: /work/my-app/secrets.enc.yaml [&quot;my-app&quot;][&quot;production&quot;][&quot;API_KEY&quot;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Enter value for API_KEY (profile: production): ********&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;SOPS reports the canonical encrypted file and exact &lt;code dir=&quot;auto&quot;&gt;sops set&lt;/code&gt; selector. Other
providers report their native item or path. A missing profile or unexpected
template is visible before Monosecret receives the new value.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;config-belongs-in-secrets&quot;&gt;Config belongs in secrets&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://ifiokjr.github.io/providers/null/&quot;&gt;Null&lt;/a&gt;&lt;/strong&gt; 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
&lt;a href=&quot;https://ifiokjr.github.io/blog/secrets-dont-belong-in-config/&quot;&gt;Secrets Don’t Belong in Config&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;monosecret.toml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[profiles.default]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;APP_MODE&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;Application mode&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;local&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;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;null&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;/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;[profiles.staging]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;APP_MODE&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;staging&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;/div&gt;&lt;/div&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;APP_MODE&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;default&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;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;APP_MODE&lt;/code&gt; resolves to &lt;code dir=&quot;auto&quot;&gt;local&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;staging&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;production&lt;/code&gt; based on the selected
profile. Each override inherits the description and &lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt; route from
&lt;code dir=&quot;auto&quot;&gt;[profiles.default]&lt;/code&gt;. Only the value is repeated.&lt;/p&gt;
&lt;p&gt;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 &lt;code dir=&quot;auto&quot;&gt;run&lt;/code&gt; workflow without pretending it
needs encrypted persistence.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;ephemeral-values&quot;&gt;Ephemeral values&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;prompt-for-missing-secrets-during-run&quot;&gt;Prompt for missing secrets during run&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Set &lt;code dir=&quot;auto&quot;&gt;prompt = true&lt;/code&gt; on a declaration to let &lt;code dir=&quot;auto&quot;&gt;monosecret run&lt;/code&gt; securely request
its value when the configured providers do not have one:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;monosecret.toml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[profiles.default]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;DEPLOY_PASSWORD&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;description&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;One-time deployment password&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;prompt&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;providers&lt;/span&gt;&lt;span&gt; = [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;null&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;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&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;$ monosecret run -- ./deploy&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;? Enter value for DEPLOY_PASSWORD (profile: default):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;A writable provider saves the answer, turning the prompt into first-use
provisioning. The &lt;code dir=&quot;auto&quot;&gt;null&lt;/code&gt; 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, &lt;code dir=&quot;auto&quot;&gt;run&lt;/code&gt; fails before starting the child. Declarations without
&lt;code dir=&quot;auto&quot;&gt;prompt = true&lt;/code&gt; retain the existing fail-on-missing behavior.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;passbolt-provider&quot;&gt;Passbolt provider&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Passbolt is the third new provider in 0.19. Monosecret now has 27 providers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://ifiokjr.github.io/providers/passbolt/&quot;&gt;Passbolt&lt;/a&gt;&lt;/strong&gt; reads and writes resources in a
self-hosted Passbolt server through &lt;code dir=&quot;auto&quot;&gt;go-passbolt-cli&lt;/code&gt;. Convention values use
the resource &lt;code dir=&quot;auto&quot;&gt;monosecret/{project}/{profile}/{key}&lt;/code&gt; and its &lt;code dir=&quot;auto&quot;&gt;password&lt;/code&gt; field.
References can select existing resources by UUID or exact name and address the
&lt;code dir=&quot;auto&quot;&gt;password&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;username&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;uri&lt;/code&gt;, or &lt;code dir=&quot;auto&quot;&gt;description&lt;/code&gt; field.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;monosecret.toml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[providers]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;bootstrap&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;/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;[providers.passbolt_team]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;uri&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;passbolt://?server=https://pass.example.com&amp;#x26;folder=a9230ec4-5507-4870-b8b5-b3f500587e4c&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;credentials&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;private_key&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;bootstrap&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;passphrase&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;bootstrap&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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The OpenPGP private key and passphrase can come from another Monosecret
provider. Environment fallbacks and the Passbolt CLI configuration are also
supported. Folder-scoped providers support declaration discovery with
&lt;code dir=&quot;auto&quot;&gt;init --from&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;faster-remote-provider-workflows&quot;&gt;Faster remote-provider workflows&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Remote secret reads pay for authentication, process startup, and network
round-trips before the application can start. Monosecret 0.19 reduces that work
both across invocations and within one resolution.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;cache-one-authoritative-provider&quot;&gt;Cache one authoritative provider&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;A single authoritative provider can now define &lt;code dir=&quot;auto&quot;&gt;uri&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;credentials&lt;/code&gt;, and
&lt;code dir=&quot;auto&quot;&gt;cache&lt;/code&gt; on the same alias:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;monosecret.toml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[providers]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;local&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;keyring://monosecret/cache/{project}/{profile}/{key}&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;azure&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;uri&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;akv://team-vault&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;credentials&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;client_secret&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;cache&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;&quot;&lt;/span&gt;&lt;span&gt;local&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;max_age&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;8h&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;/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;[profiles.development.defaults]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;providers&lt;/span&gt;&lt;span&gt; = [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;azure&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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The cached &lt;code dir=&quot;auto&quot;&gt;fallback&lt;/code&gt; form introduced in 0.17 remains available when several
authoritative providers can answer.&lt;/p&gt;
&lt;p&gt;Cache entries now include their absolute expiration time and originating
&lt;code dir=&quot;auto&quot;&gt;max_age&lt;/code&gt;. Monosecret removes an expired entry whenever it encounters one, and
changing &lt;code dir=&quot;auto&quot;&gt;max_age&lt;/code&gt; invalidates entries written under the previous policy.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;batch-1password-field-reads&quot;&gt;Batch 1Password field reads&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;1Password field references now resolve together through one &lt;code dir=&quot;auto&quot;&gt;op inject&lt;/code&gt; call,
instead of starting &lt;code dir=&quot;auto&quot;&gt;op read&lt;/code&gt; 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, Monosecret falls back to bounded
concurrent reads so it can preserve per-secret missing-value behavior without
serializing the whole profile.&lt;/p&gt;
&lt;p&gt;In the cold-cache benchmark from
&lt;a href=&quot;https://github.com/cachix/monosecret/pull/317&quot;&gt;the implementation PR&lt;/a&gt;, a
representative profile with 25 field references resolved in 11.890 seconds,
down from 96.294 seconds. The batch used 3 &lt;code dir=&quot;auto&quot;&gt;op&lt;/code&gt; processes instead of 27, making
that run 8.10 times faster.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;smaller-improvements&quot;&gt;Smaller improvements&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;standalone-profiles&quot;&gt;Standalone profiles&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Profiles inherit &lt;code dir=&quot;auto&quot;&gt;[profiles.default]&lt;/code&gt; unless their defaults set
&lt;code dir=&quot;auto&quot;&gt;inherit = false&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;monosecret.toml&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[profiles.default]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;DEV_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;Developer database&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;LOCAL_DEBUG_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;Local debugging token&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;false&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;[profiles.production.defaults]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;inherit&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;providers&lt;/span&gt;&lt;span&gt; = [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;vault://vault.example.com:8200/secret&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;/div&gt;&lt;/div&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;Production database&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;API_KEY&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;Production API key&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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;production&lt;/code&gt; contains only its own declarations and fields. Other profiles in
the same manifest can continue to inherit the default profile.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;pkg-config-metadata-for-monosecret_ffi&quot;&gt;pkg-config metadata for monosecret_ffi&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;cargo cinstall -p monosecret_ffi&lt;/code&gt; now installs the library, C header, and a
&lt;code dir=&quot;auto&quot;&gt;monosecret_ffi.pc&lt;/code&gt; file containing the complete link metadata. Go builds can
use the &lt;code dir=&quot;auto&quot;&gt;pkgconfig&lt;/code&gt; tag, Ruby native extensions accept &lt;code dir=&quot;auto&quot;&gt;--enable-pkg-config&lt;/code&gt;,
and Haskell builds use the &lt;code dir=&quot;auto&quot;&gt;use-pkg-config&lt;/code&gt; Cabal flag. The same metadata
supports installed static or shared libraries.&lt;/p&gt;
&lt;p&gt;Haskell now declares its required macOS system frameworks. The Rust SDK’s
&lt;code dir=&quot;auto&quot;&gt;ProviderAlias&lt;/code&gt; type also exposes &lt;code dir=&quot;auto&quot;&gt;leaf&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;credentials&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;credentials_mut&lt;/code&gt;
helpers for configuration tooling.&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;monosecret&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Existing route-wide &lt;code dir=&quot;auto&quot;&gt;ref&lt;/code&gt; declarations, inheriting profiles, and cached
fallback aliases remain compatible. All new configuration fields and providers
are opt-in.&lt;/p&gt;
&lt;p&gt;0.19 also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fixes &lt;a href=&quot;https://github.com/cachix/monosecret/issues/268&quot;&gt;concurrent keyring initialization&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;preserves &lt;a href=&quot;https://github.com/cachix/monosecret/issues/140&quot;&gt;non-UTF-8 environment values in &lt;code dir=&quot;auto&quot;&gt;run&lt;/code&gt; on
Unix&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;renders &lt;a href=&quot;https://github.com/cachix/monosecret/pull/271&quot;&gt;SOPS path templates in one pass&lt;/a&gt; and &lt;a href=&quot;https://github.com/cachix/monosecret/commit/bd448ad821d251f1d38a4235a1db868372bb2bd3&quot;&gt;validates deserialized path templates&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;preserves &lt;a href=&quot;https://github.com/cachix/monosecret/issues/272&quot;&gt;complete multi-segment LastPass templates in route
comparisons&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/cachix/monosecret/issues/283&quot;&gt;refreshes fallback providers when a Rust &lt;code dir=&quot;auto&quot;&gt;Secrets&lt;/code&gt; instance is
reused&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;adds &lt;a href=&quot;https://github.com/cachix/monosecret/pull/315&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;Secrets::resolve_named&lt;/code&gt;&lt;/a&gt;
for resolving one secret without unrelated missing requirements.&lt;/li&gt;
&lt;li&gt;rejects &lt;a href=&quot;https://github.com/cachix/monosecret/pull/315&quot;&gt;credentials embedded in provider URIs&lt;/a&gt;. Use alias credentials or
provider environment variables instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See the &lt;a href=&quot;https://github.com/cachix/monosecret/blob/main/CHANGELOG.md&quot;&gt;full changelog&lt;/a&gt;
for every change and fix in this release.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;future-work&quot;&gt;Future work&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;These items are not part of 0.19. They are open work for future releases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Native Windows ARM64 CLI archive (target: 0.19.1)&lt;/strong&gt;: add
&lt;code dir=&quot;auto&quot;&gt;monosecret-aarch64-pc-windows-msvc.zip&lt;/code&gt; 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
&lt;a href=&quot;https://github.com/axodotdev/axoupdater/pull/357&quot;&gt;axoupdater supporting Windows ARM64&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;WinGet packaging&lt;/strong&gt;: publish the initial package tracked in
&lt;a href=&quot;https://github.com/microsoft/winget-pkgs/pull/413776&quot;&gt;microsoft/winget-pkgs#413776&lt;/a&gt;,
then automate stable updates through
&lt;a href=&quot;https://github.com/cachix/monosecret/pull/297&quot;&gt;Monosecret #297&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/cachix/monosecret/issues/300&quot;&gt;Notification and approval integrations&lt;/a&gt;&lt;/strong&gt;: send new
secret access requests to services such as email, Slack, or WhatsApp for
approval.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/cachix/monosecret/issues/310&quot;&gt;JVM SDK&lt;/a&gt;&lt;/strong&gt;: expose the
shared Monosecret resolver to Java, Kotlin, and other JVM languages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/cachix/monosecret/issues/240&quot;&gt;Dart SDK&lt;/a&gt;&lt;/strong&gt;: bring the
shared resolver to Dart and Flutter applications.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every team has a secrets story. Come tell us yours on
&lt;a href=&quot;https://discord.gg/naMgvexb6q&quot;&gt;Discord&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Where .env Went Wrong</title><link>https://ifiokjr.github.io/monosecret/blog/where-env-went-wrong/</link><guid isPermaLink="true">https://ifiokjr.github.io/monosecret/blog/where-env-went-wrong/</guid><pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;aside aria-label=&quot;Adapted from upstream Monosecret&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Adapted from upstream Monosecret&lt;/p&gt;&lt;div&gt;&lt;p&gt;This evergreen article was originally published by the upstream Monosecret project. It is retained with its original author attribution; technical examples have been adapted for Monosecret.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; is one of software’s most successful accidents.&lt;/p&gt;
&lt;p&gt;It starts as a shortcut for three &lt;code dir=&quot;auto&quot;&gt;export&lt;/code&gt; commands. Then it becomes the
project’s configuration schema, secret store, environment model, onboarding
guide, CI interface, and deployment format.&lt;/p&gt;
&lt;p&gt;Environment variables do one job well: deliver strings to a process. &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt;
turned that delivery mechanism into a source of truth.&lt;/p&gt;
&lt;p&gt;A convenience became architecture. That is where &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; went wrong.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;environment-variables-only-deliver-values&quot;&gt;Environment variables only deliver values&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Environment variables solve a small problem: getting values into a running
process. The application can read &lt;code dir=&quot;auto&quot;&gt;DATABASE_URL&lt;/code&gt; without knowing whether a
developer, CI system, or secrets manager supplied it.&lt;/p&gt;
&lt;p&gt;A &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; file makes those values easy to save and reload. That is useful. But
teams also use the file to describe what the application needs. &lt;code dir=&quot;auto&quot;&gt;KEY=value&lt;/code&gt;
cannot say whether a value is required, secret, safe to commit, available only
in production, or restricted to one service.&lt;/p&gt;
&lt;p&gt;Those requirements outlive any process and any developer laptop. They belong
in a durable project declaration. &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; stores values for delivery; it cannot
define the application’s secret model.&lt;/p&gt;
&lt;aside aria-label=&quot;How does Monosecret solve this?&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;How does Monosecret solve this?&lt;/p&gt;&lt;div&gt;&lt;p&gt;Monosecret separates the committed
&lt;a href=&quot;https://ifiokjr.github.io/concepts/declarative/&quot;&gt;declaration&lt;/a&gt; from value
&lt;a href=&quot;https://ifiokjr.github.io/concepts/providers/&quot;&gt;storage&lt;/a&gt; and delivery. The CLI and
&lt;a href=&quot;https://ifiokjr.github.io/sdk/overview/&quot;&gt;SDKs&lt;/a&gt; resolve the same declaration regardless of where values
live or how applications receive them.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;h2 id=&quot;a-string-is-not-a-schema&quot;&gt;A string is not a schema&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Consider a typical example file:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;.env.example&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;DATABASE_URL&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;REDIS_URL&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;redis://localhost:6379&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;STRIPE_API_KEY&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;DEBUG&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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The file raises more questions than it answers. Does an empty value mean
required or optional? Is &lt;code dir=&quot;auto&quot;&gt;REDIS_URL&lt;/code&gt; a development default? Is
&lt;code dir=&quot;auto&quot;&gt;STRIPE_API_KEY&lt;/code&gt; production-only? Is &lt;code dir=&quot;auto&quot;&gt;DEBUG&lt;/code&gt; a boolean?&lt;/p&gt;
&lt;p&gt;Dotenv cannot encode those answers. Node.js documents that
&lt;a href=&quot;https://nodejs.org/api/environment_variables.html#variable-values&quot;&gt;every value becomes a string&lt;/a&gt;.
A &lt;a href=&quot;https://github.com/motdotla/dotenv/issues/51&quot;&gt;dotenv issue about booleans&lt;/a&gt;, opened in 2015, still
collects reactions from developers surprised that &lt;code dir=&quot;auto&quot;&gt;&quot;false&quot;&lt;/code&gt; is truthy.&lt;/p&gt;
&lt;p&gt;Teams put the missing information elsewhere: validation code, a README,
&lt;code dir=&quot;auto&quot;&gt;.env.example&lt;/code&gt;, or a teammate’s memory. These sources drift.&lt;/p&gt;
&lt;p&gt;The file also makes &lt;code dir=&quot;auto&quot;&gt;DEBUG&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;STRIPE_API_KEY&lt;/code&gt; look equivalent. One is an
ordinary setting that belongs in Git. The other grants authority and needs
access control and rotation. Mixing them makes the whole file sensitive.&lt;/p&gt;
&lt;p&gt;Without an explicit declaration, missing values fail late: the application
discovers them only when code tries to use them.&lt;/p&gt;
&lt;aside aria-label=&quot;How does Monosecret solve this?&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;How does Monosecret solve this?&lt;/p&gt;&lt;div&gt;&lt;p&gt;The &lt;a href=&quot;https://ifiokjr.github.io/reference/configuration/&quot;&gt;Monosecret declaration&lt;/a&gt; records names,
descriptions, required values, and safe defaults. &lt;code dir=&quot;auto&quot;&gt;monosecret check&lt;/code&gt; and
&lt;code dir=&quot;auto&quot;&gt;monosecret run&lt;/code&gt; validate those requirements before the application starts,
while ordinary settings such as &lt;code dir=&quot;auto&quot;&gt;DEBUG&lt;/code&gt; remain in application configuration.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;h2 id=&quot;then-the-file-starts-to-multiply&quot;&gt;Then the file starts to multiply&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A new requirement usually creates another file:&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;.env&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.env.local&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.env.development&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.env.development.local&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.env.test&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;.env.production&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The filenames become an environment model. Suffixes define scope, load order
defines inheritance, and copying a file becomes deployment.&lt;/p&gt;
&lt;p&gt;This reverses the
&lt;a href=&quot;https://12factor.net/config&quot;&gt;Twelve-Factor App’s guidance&lt;/a&gt;. Its point was that
environment variables should be independent controls because named
environments become brittle as deployments multiply. &lt;code dir=&quot;auto&quot;&gt;.env.production&lt;/code&gt;
recreates that grouping in a filename.&lt;/p&gt;
&lt;p&gt;Now every new value must be added to &lt;code dir=&quot;auto&quot;&gt;.env.example&lt;/code&gt;, documented in a README,
validated in code, and copied into the right real files. Miss one and the
environments drift.&lt;/p&gt;
&lt;aside aria-label=&quot;How does Monosecret solve this?&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;How does Monosecret solve this?&lt;/p&gt;&lt;div&gt;&lt;p&gt;&lt;a href=&quot;https://ifiokjr.github.io/concepts/profiles/&quot;&gt;Profiles&lt;/a&gt; express real requirement differences as sparse
overlays on &lt;code dir=&quot;auto&quot;&gt;profiles.default&lt;/code&gt;. Each deployment selects its values through
providers instead of maintaining a complete, copied secret file.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;h2 id=&quot;there-is-no-env-spec&quot;&gt;There is no &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; spec&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; looks standardized, but every parser defines its own format.
&lt;a href=&quot;https://nodejs.org/api/environment_variables.html#env-files&quot;&gt;Node.js documents the lack of a formal
specification&lt;/a&gt;,
as does
&lt;a href=&quot;https://github.com/theskumar/python-dotenv#file-format&quot;&gt;python-dotenv&lt;/a&gt;. Each
loader makes its own choices.&lt;/p&gt;
&lt;p&gt;python-dotenv expands &lt;code dir=&quot;auto&quot;&gt;${NAME}&lt;/code&gt; but not &lt;code dir=&quot;auto&quot;&gt;$NAME&lt;/code&gt;. Node dotenv delegates &lt;a href=&quot;https://github.com/motdotla/dotenv#variable-expansion&quot;&gt;variable expansion&lt;/a&gt; to another
tool. Docker Compose supports its own
&lt;a href=&quot;https://github.com/compose-spec/compose-spec/blob/main/spec.md#interpolation&quot;&gt;shell-style operators&lt;/a&gt;.
Vite even supports references in reverse order, then
&lt;a href=&quot;https://main.vite.dev/guide/env-and-mode#expanding-variables-in-reverse-order&quot;&gt;warns&lt;/a&gt;
that the same expression will not work in a shell or Docker Compose.&lt;/p&gt;
&lt;p&gt;Comments and quotes differ too. Node dotenv changed the meaning of &lt;code dir=&quot;auto&quot;&gt;#&lt;/code&gt; in
unquoted values in version 15 as a &lt;a href=&quot;https://github.com/motdotla/dotenv#comments&quot;&gt;breaking change&lt;/a&gt;. One devenv user found
that
&lt;a href=&quot;https://github.com/cachix/devenv/issues/1333&quot;&gt;quotes became part of an exported key&lt;/a&gt;.&lt;/p&gt;
&lt;aside aria-label=&quot;How does Monosecret solve this?&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;How does Monosecret solve this?&lt;/p&gt;&lt;div&gt;&lt;p&gt;Monosecret defines one TOML declaration and one resolution model shared by its
CLI and &lt;a href=&quot;https://ifiokjr.github.io/sdk/overview/&quot;&gt;SDKs&lt;/a&gt;. Dotenv parsing is confined to the
&lt;a href=&quot;https://ifiokjr.github.io/providers/dotenv/&quot;&gt;compatibility provider&lt;/a&gt;, so changing loaders or storage
backends does not change the application’s declaration.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;h2 id=&quot;which-value-wins&quot;&gt;Which value wins?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Parsers also disagree about precedence.
&lt;a href=&quot;https://github.com/motdotla/dotenv#path&quot;&gt;Node dotenv&lt;/a&gt; normally lets the first
file win. &lt;a href=&quot;https://github.com/compose-spec/compose-spec/blob/main/spec.md#env_file&quot;&gt;Docker Compose&lt;/a&gt;
lets the last &lt;code dir=&quot;auto&quot;&gt;env_file&lt;/code&gt; win, then lets the &lt;code dir=&quot;auto&quot;&gt;environment&lt;/code&gt; section override
that. &lt;a href=&quot;https://main.vite.dev/guide/env-and-mode#env-loading-priorities&quot;&gt;Vite&lt;/a&gt;
gives an existing process variable priority over its files.&lt;/p&gt;
&lt;p&gt;Docker Compose gives two similar names different behavior. &lt;code dir=&quot;auto&quot;&gt;env_file:&lt;/code&gt; supplies
variables to a container but does not use them to interpolate &lt;code dir=&quot;auto&quot;&gt;compose.yaml&lt;/code&gt;.
&lt;code dir=&quot;auto&quot;&gt;docker compose --env-file&lt;/code&gt; does affect interpolation. In &lt;a href=&quot;https://github.com/docker/compose/issues/9443&quot;&gt;an issue closed as working as designed&lt;/a&gt;, a maintainer described
the option’s name as unfortunately chosen.&lt;/p&gt;
&lt;aside aria-label=&quot;How does Monosecret solve this?&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;How does Monosecret solve this?&lt;/p&gt;&lt;div&gt;&lt;p&gt;Monosecret applies one deterministic &lt;a href=&quot;https://ifiokjr.github.io/concepts/providers/fallback/#provider-selection-order&quot;&gt;provider resolution order&lt;/a&gt;. Per-secret
routes and fallbacks are explicit in the declaration, and the same resolver
applies them across the CLI and SDKs.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;h2 id=&quot;who-loaded-env-first&quot;&gt;Who loaded &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; first?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Precedence also depends on timing. Node dotenv’s &lt;a href=&quot;https://github.com/motdotla/dotenv/blob/94f6542d5c8b1ab211cab0dcd8f7aa907dd39124/README.md#L406-L435&quot;&gt;ES module guidance&lt;/a&gt;
needs special handling when imported modules read the environment during
initialization. Vite warns that Bun’s automatic &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; loading can interfere
with &lt;a href=&quot;https://main.vite.dev/guide/env-and-mode#env-files&quot;&gt;Vite’s own loading order&lt;/a&gt;. &lt;code dir=&quot;auto&quot;&gt;VITE_*&lt;/code&gt; values are
replaced at build time and become part of the
&lt;a href=&quot;https://main.vite.dev/guide/env-and-mode#env-variables&quot;&gt;client bundle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The same line can become a runtime secret, a build-time constant, or a public
browser value. The loader decides based on timing and context.&lt;/p&gt;
&lt;p&gt;At that point &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; behaves like a small program, with control flow spread
across filenames, flags, working directories, parent processes, and library
versions.&lt;/p&gt;
&lt;aside aria-label=&quot;How does Monosecret solve this?&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;How does Monosecret solve this?&lt;/p&gt;&lt;div&gt;&lt;p&gt;&lt;a href=&quot;https://ifiokjr.github.io/reference/cli/#run&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;monosecret run&lt;/code&gt;&lt;/a&gt; resolves and validates secrets before
launching the child, so its complete environment exists from process startup.
Applications using an SDK load the declaration explicitly instead of depending
on a module-import side effect.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;h2 id=&quot;an-ignored-file-is-still-a-file&quot;&gt;An ignored file is still a file&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The dotenv project says &lt;a href=&quot;https://github.com/motdotla/dotenv#should-i-commit-my-env-file&quot;&gt;not to commit &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt;&lt;/a&gt;.
&lt;code dir=&quot;auto&quot;&gt;.gitignore&lt;/code&gt; prevents one accident. It does not add encryption, access control,
auditing, or revocation.&lt;/p&gt;
&lt;p&gt;The file can still end up in editor backups, chat messages, archives, support
bundles, container build contexts, and old laptops. A devenv integration was
&lt;a href=&quot;https://github.com/cachix/devenv/issues/1694&quot;&gt;reported to copy &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; contents into the Nix
store&lt;/a&gt;, where paths are not
confidential. When a developer leaves, there is no file access to revoke.
Each credential they received is a separate copy.&lt;/p&gt;
&lt;p&gt;Even a secret stored in 1Password, Vault, a cloud secret manager, or a system
keyring must be copied into plaintext before a dotenv-based application can use
it. The local copy has fewer controls than the original.&lt;/p&gt;
&lt;p&gt;Environment-variable delivery has limits too. Docker mounts managed secrets as
files because environment variables can
&lt;a href=&quot;https://docs.docker.com/engine/swarm/secrets/#build-support-for-docker-secrets-into-your-images&quot;&gt;leak between containers&lt;/a&gt;.
A process also gets one global map, so a frontend build, worker, migration, and
web service often receive the same secrets even when each needs only a few.
Dotenv has no way to express that scope.&lt;/p&gt;
&lt;aside aria-label=&quot;How does Monosecret solve this?&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;How does Monosecret solve this?&lt;/p&gt;&lt;div&gt;&lt;p&gt;The committed declaration contains no secret values. Providers supply storage,
encryption, identity, and access control, while the
&lt;a href=&quot;https://ifiokjr.github.io/concepts/audit/&quot;&gt;metadata-only audit log&lt;/a&gt; records local access. &lt;a href=&quot;https://ifiokjr.github.io/concepts/scopes/&quot;&gt;Scopes (0.2+)&lt;/a&gt; let each service or command resolve only its
declared subset.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;div&gt;&lt;h2 id=&quot;let-env-become-small-again&quot;&gt;Let &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; become small again&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The useful part of &lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; is the short path from “this application needs a
value” to “the application can run.”&lt;/p&gt;
&lt;p&gt;Keep it as an adapter for tools that expect &lt;code dir=&quot;auto&quot;&gt;KEY=value&lt;/code&gt;, or use it for ordinary
local settings. Do not make it define the project’s requirements, store durable
copies of secrets, encode environments in filenames, or decide which services
receive which values.&lt;/p&gt;
&lt;p&gt;A durable design separates three jobs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a committed declaration says which secrets the application needs;&lt;/li&gt;
&lt;li&gt;protected storage controls who can read their values;&lt;/li&gt;
&lt;li&gt;explicit delivery gives each process only the values it needs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each piece can then change independently. A team can change storage without
rewriting the application, validate requirements before startup, and limit each
component to its own secrets.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-monosecret-applies-this&quot;&gt;How Monosecret applies this&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Monosecret puts the declaration in a file that is safe to commit:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;monosecret.toml&lt;/span&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;payments&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;revision&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;&quot;&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;[profiles.default]&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 connection string&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;REDIS_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;Redis connection string&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;required&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;STRIPE_API_KEY&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;Stripe API key&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;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[profiles.development]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;REDIS_URL&lt;/span&gt;&lt;span&gt; = { &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;redis://localhost:6379&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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This file records requirements, defaults, and descriptions without containing
secret values. &lt;a href=&quot;https://ifiokjr.github.io/concepts/providers/&quot;&gt;Providers&lt;/a&gt; choose where values live, and
&lt;a href=&quot;https://ifiokjr.github.io/concepts/profiles/&quot;&gt;profiles&lt;/a&gt; describe real differences in requirements.&lt;/p&gt;
&lt;p&gt;Existing programs can adopt Monosecret without code changes:&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;monosecret&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;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./server&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This command injects resolved secrets into the child process environment. It is
useful during migration, while the preferred integration is a
&lt;a href=&quot;https://ifiokjr.github.io/sdk/overview/&quot;&gt;Monosecret SDK&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With an SDK, the application resolves its declaration directly. This removes
the environment-variable handoff used by &lt;code dir=&quot;auto&quot;&gt;monosecret run&lt;/code&gt;. Values stored in a
keyring, password manager, or Vault never enter the global process environment.
Applications that require a file can receive a
&lt;a href=&quot;https://ifiokjr.github.io/reference/configuration/#as_path-option&quot;&gt;temporary file&lt;/a&gt; instead. &lt;a href=&quot;https://ifiokjr.github.io/concepts/scopes/&quot;&gt;Scopes (0.2+)&lt;/a&gt; let each component resolve only the secrets it
declares.&lt;/p&gt;
&lt;p&gt;Migration can be gradual. Monosecret initializes a declaration from an existing
file:&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;monosecret&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;init&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dotenv:.env&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This copies names without copying values. The current file can remain a
provider during the transition:&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;monosecret&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;check&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&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&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;div&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;monosecret&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&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./server&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Values can then move to a system keyring, password manager, Vault, or another
provider without changing the names the application reads.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;.env&lt;/code&gt; can remain for ordinary local settings. Existing applications can keep
environment-variable delivery while they migrate. Applications using an SDK or
file-based delivery can remove secrets from their process environments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Monosecret aims to eliminate environment variables for secrets altogether.&lt;/strong&gt;&lt;/p&gt;</content:encoded></item><item><title>But I Use SOPS</title><link>https://ifiokjr.github.io/monosecret/blog/but-i-use-sops/</link><guid isPermaLink="true">https://ifiokjr.github.io/monosecret/blog/but-i-use-sops/</guid><pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;aside aria-label=&quot;Adapted from upstream Monosecret&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Adapted from upstream Monosecret&lt;/p&gt;&lt;div&gt;&lt;p&gt;This evergreen article was originally published by the upstream Monosecret project. It is retained with its original author attribution; technical examples have been adapted for Monosecret.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Whenever I show someone Monosecret, I often hear the same response:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But I use SOPS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://getsops.io/docs/&quot;&gt;SOPS&lt;/a&gt; is good. It encrypts files so they can
live in Git without exposing their plaintext values.&lt;/p&gt;
&lt;p&gt;But Monosecret solves a different problem: how applications declare, find, and
consume secrets.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-does-your-application-use-the-secret&quot;&gt;How does your application use the secret?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Once you have encrypted &lt;code dir=&quot;auto&quot;&gt;secrets.yaml&lt;/code&gt;, how does your Python service consume
it? What about your Go worker or Node.js app?&lt;/p&gt;
&lt;p&gt;You still need to decrypt the file, inject its values, select the right file for
each environment, validate required keys, and repeat that integration for every
language.&lt;/p&gt;
&lt;p&gt;And if you release the project as open source, that choice does not stay yours.
With SOPS baked into the setup, everyone who runs or contributes to the project
must adopt SOPS and its key management, whatever secrets tooling they already
use.&lt;/p&gt;
&lt;p&gt;Monosecret starts at the other end. The project
&lt;a href=&quot;https://ifiokjr.github.io/concepts/declarative/&quot;&gt;declares what the application needs&lt;/a&gt; without storing
any values:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;monosecret.toml&lt;/span&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;payments&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;revision&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;1.0&lt;/span&gt;&lt;span&gt;&quot;&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;[profiles.default]&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 connection string&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;STRIPE_API_KEY&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;Stripe secret key&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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The same secret can come from a developer’s
&lt;a href=&quot;https://ifiokjr.github.io/providers/keyring/&quot;&gt;system keyring&lt;/a&gt; or CI
&lt;a href=&quot;https://ifiokjr.github.io/providers/env/&quot;&gt;environment variables&lt;/a&gt;, while a more sensitive production
environment resolves it from &lt;a href=&quot;https://ifiokjr.github.io/providers/vault/&quot;&gt;Vault&lt;/a&gt;. Applications use the
same declaration through nine SDKs for &lt;a href=&quot;https://ifiokjr.github.io/sdk/rust/&quot;&gt;Rust&lt;/a&gt;,
&lt;a href=&quot;https://ifiokjr.github.io/sdk/python/&quot;&gt;Python&lt;/a&gt;, &lt;a href=&quot;https://ifiokjr.github.io/sdk/go/&quot;&gt;Go&lt;/a&gt;,
&lt;a href=&quot;https://ifiokjr.github.io/sdk/ruby/&quot;&gt;Ruby&lt;/a&gt;, &lt;a href=&quot;https://ifiokjr.github.io/sdk/nodejs/&quot;&gt;Node.js/TypeScript&lt;/a&gt;,
&lt;a href=&quot;https://ifiokjr.github.io/sdk/haskell/&quot;&gt;Haskell&lt;/a&gt;, &lt;a href=&quot;https://ifiokjr.github.io/sdk/php/&quot;&gt;PHP&lt;/a&gt;, &lt;a href=&quot;https://ifiokjr.github.io/sdk/csharp/&quot;&gt;C#&lt;/a&gt;, and
&lt;a href=&quot;https://ifiokjr.github.io/sdk/swift/&quot;&gt;Swift (0.2+)&lt;/a&gt; without knowing the provider.&lt;/p&gt;
&lt;p&gt;Encrypted files also make the key workflow a project-wide requirement. Adding a
teammate means adding their key to &lt;code dir=&quot;auto&quot;&gt;.sops.yaml&lt;/code&gt; and re-encrypting every file;
removing one means rekeying and rotating the affected secrets, since their key
already saw the plaintext. Monosecret leaves identity and access to the
provider: onboarding to Vault or a cloud secrets manager is granting a role,
and offboarding is revoking it.&lt;/p&gt;
&lt;p&gt;SOPS may be enough today. As your team grows more sensitive to how secrets are
handled, you may want Vault’s access policies and centralized audit trail. If
applications know about SOPS, each one needs migrating. If they know only
Monosecret, you change the &lt;a href=&quot;https://ifiokjr.github.io/concepts/providers/&quot;&gt;provider configuration&lt;/a&gt;; SDK
calls and secret names stay the same.&lt;/p&gt;
&lt;p&gt;The same resolver provides &lt;a href=&quot;https://ifiokjr.github.io/concepts/profiles/&quot;&gt;profiles&lt;/a&gt;,
&lt;a href=&quot;https://ifiokjr.github.io/reference/configuration/#secret-variable-options&quot;&gt;required-secret checks&lt;/a&gt;,
&lt;a href=&quot;https://ifiokjr.github.io/concepts/providers/fallback/&quot;&gt;per-secret provider routing and fallback&lt;/a&gt;,
&lt;a href=&quot;https://ifiokjr.github.io/concepts/references/&quot;&gt;provider-native references&lt;/a&gt;,
&lt;a href=&quot;https://ifiokjr.github.io/reference/configuration/#as_path-option&quot;&gt;temporary files&lt;/a&gt;, and
&lt;a href=&quot;https://ifiokjr.github.io/concepts/audit/&quot;&gt;metadata-only audit logs&lt;/a&gt;. You build the integration once,
not once per provider and language.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;different-layers-different-jobs&quot;&gt;Different layers, different jobs&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;SOPS protects a file. Monosecret gives applications a provider-independent
interface. The selected provider remains responsible for storage, encryption,
identity, access control, and availability.&lt;/p&gt;
&lt;p&gt;I wrote a fuller &lt;a href=&quot;https://ifiokjr.github.io/comparison/&quot;&gt;Monosecret comparison&lt;/a&gt; showing exactly where
Monosecret ends, where providers begin, and which responsibilities belong to
each layer.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-monosecret-goes-next&quot;&gt;Where Monosecret goes next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Because applications talk to an interface instead of a file, the interface can
grow without touching them. Three open proposals point where it is heading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ifiokjr/monosecret/issues/188&quot;&gt;Project security requirements&lt;/a&gt;
would let a project declare the guarantees a provider must meet, such as
encryption at rest or an audit trail, and reject providers that fall short.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ifiokjr/monosecret/issues/11&quot;&gt;Lease-aware refresh&lt;/a&gt; would
let running applications follow key rotation and short-lived credentials
instead of restarting for a new value.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://ifiokjr.github.io/providers/sops/&quot;&gt;SOPS provider&lt;/a&gt; (0.2+) brings SOPS itself behind the
same SDK interface, making your encrypted files one more place secrets can
come from.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With that provider, perhaps “But I use SOPS” just needs two more words:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But I use SOPS with Monosecret.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If encrypted files fit your workflow, keep using SOPS. Just recognize the
boundary: encryption at rest is not an application secrets interface.&lt;/p&gt;</content:encoded></item><item><title>Secrets Don’t Belong in Config</title><link>https://ifiokjr.github.io/monosecret/blog/secrets-dont-belong-in-config/</link><guid isPermaLink="true">https://ifiokjr.github.io/monosecret/blog/secrets-dont-belong-in-config/</guid><pubDate>Mon, 20 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;aside aria-label=&quot;Adapted from upstream Monosecret&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Adapted from upstream Monosecret&lt;/p&gt;&lt;div&gt;&lt;p&gt;This evergreen article was originally published by the upstream Monosecret project. It is retained with its original author attribution; technical examples have been adapted for Monosecret.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;p&gt;Applications should not require passwords, API keys, or tokens in their
configuration files.&lt;/p&gt;
&lt;p&gt;Configuration describes behavior. It belongs in git, code review, bug reports,
and developer machines.&lt;/p&gt;
&lt;p&gt;A secret grants authority. It needs restricted access and independent rotation.&lt;/p&gt;
&lt;p&gt;Putting both in one file couples different lifecycles and audiences. If rotating
a password requires regenerating application configuration, the interface has
coupled them too tightly.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;nixos-contains-110-workarounds-for-this&quot;&gt;NixOS contains 110 workarounds for this&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We &lt;a href=&quot;https://github.com/NixOS/nixpkgs/issues/24288#issuecomment-5024009774&quot;&gt;audited all 445 NixOS modules that handle a real secret&lt;/a&gt;
in nixpkgs at commit &lt;code dir=&quot;auto&quot;&gt;141f212&lt;/code&gt;, classifying each by where its secret value ends
up.&lt;/p&gt;













































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Where the secret value ends up&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;Modules&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;Share&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Merged into a config file at runtime&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;110&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;25%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Inlined into a config in &lt;code dir=&quot;auto&quot;&gt;/nix/store&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;42&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;9%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Delivered as an environment variable&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;161&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;36%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Left in a dedicated file opened by the app&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;58&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;13%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Loaded through systemd credentials&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;53&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;12%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Passed as a command-line argument&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;19&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;4%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Classification uncertain&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;2&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;—&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The interesting number is 110. A quarter of the modules retrieve a secret
safely, then copy it into configuration because that is the only interface the
application accepts.&lt;/p&gt;
&lt;p&gt;These modules use &lt;code dir=&quot;auto&quot;&gt;envsubst&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;replace-secret&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;jq&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;yq&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;sed&lt;/code&gt;, or custom
code to assemble a restricted file at startup. The result can be secure, but
every module now owns application-specific, security-sensitive glue just to
combine two inputs that should have remained separate.&lt;/p&gt;
&lt;p&gt;This is not unique to NixOS. The same workaround appears as an entrypoint
script, Helm template, init container, or CI interpolation step on other
platforms.&lt;/p&gt;
&lt;p&gt;As a side note, 42 modules can inline secrets into the world-readable
&lt;code dir=&quot;auto&quot;&gt;/nix/store&lt;/code&gt;. That direct security problem is tracked in
&lt;a href=&quot;https://github.com/NixOS/nixpkgs/issues/24288&quot;&gt;nixpkgs issue #24288&lt;/a&gt;. The 110
runtime mergers make the broader point: even when deployment authors avoid the
leak, the missing separation still creates work.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;give-secrets-their-own-interface&quot;&gt;Give secrets their own interface&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Applications should accept secret values through a dedicated runtime channel,
such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;code dir=&quot;auto&quot;&gt;password_file&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;token_file&lt;/code&gt; setting;&lt;/li&gt;
&lt;li&gt;a systemd credential;&lt;/li&gt;
&lt;li&gt;a narrowly scoped environment variable;&lt;/li&gt;
&lt;li&gt;or an external secret provider.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These mechanisms are not equally safe: environment variables can be inherited,
arguments can appear in process listings, and files still need correct
permissions. What separation does guarantee is that the deployer no longer has
to manufacture a second, secret-bearing version of the configuration.&lt;/p&gt;
&lt;p&gt;The principle is simple; implementing it across environments is not. Local
development might use a system keyring, CI environment variables, and
production 1Password or Vault. Without a shared abstraction, each environment
needs its own naming, lookup, validation, and injection glue.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-i-got-it-wrong-in-cachix&quot;&gt;How I got it wrong in Cachix&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Cachix historically stored its auth token and per-cache signing keys in
&lt;code dir=&quot;auto&quot;&gt;~/.config/cachix/cachix.dhall&lt;/code&gt;, alongside cache names and other configuration.
It was convenient, but the file had to be treated as a secret even though much
of it was ordinary configuration.&lt;/p&gt;
&lt;p&gt;A typical file mixed them directly:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;~/.config/cachix/cachix.dhall&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{ authToken = &quot;XXX-AUTH-TOKEN&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;, binaryCaches =&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;[ { name = &quot;mycache&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;, secretKey = &quot;XXX-SIGNING-KEY&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;&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;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The cache name is configuration; the auth token and signing key are secrets.
You could not share the cache configuration without also sharing credentials.&lt;/p&gt;
&lt;p&gt;devenv 2.2 separates the token through its declarative secrets integration.
The project declares &lt;code dir=&quot;auto&quot;&gt;CACHIX_AUTH_TOKEN&lt;/code&gt;, devenv resolves it from the configured
provider, and the value is passed to Cachix without being added to devenv’s
configuration.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/cachix/cachix/pull/737&quot;&gt;Cachix PR #737&lt;/a&gt; brings the same
boundary into the client through the Monosecret Haskell SDK. It resolves
&lt;code dir=&quot;auto&quot;&gt;CACHIX_AUTH_TOKEN&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;CACHIX_SIGNING_KEY&lt;/code&gt; from Monosecret and can store them
in the user’s chosen provider instead of &lt;code dir=&quot;auto&quot;&gt;cachix.dhall&lt;/code&gt;. Existing environment
variables and config files remain higher-priority fallbacks for compatibility.
The PR is still open.&lt;/p&gt;
&lt;p&gt;That is the problem Monosecret is designed to solve: configuration declares the
requirement, while each environment chooses where the value lives.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;declare-once-resolve-anywhere&quot;&gt;Declare once, resolve anywhere&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Monosecret applies that separation by making &lt;code dir=&quot;auto&quot;&gt;monosecret.toml&lt;/code&gt; a declaration of
what an application needs, without storing the values:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;monosecret.toml&lt;/span&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;myapp&lt;/span&gt;&lt;span&gt;&quot;&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;[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 connection string&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;STRIPE_API_KEY&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;Stripe secret key&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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://ifiokjr.github.io/concepts/providers/&quot;&gt;Providers&lt;/a&gt; decide where the values live. A developer can
use the system keyring, CI can use environment variables, and production can use
1Password, Vault/OpenBao, or a cloud secret manager without changing the
declaration.&lt;/p&gt;
&lt;p&gt;An existing application can receive the resolved values at startup:&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;monosecret&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;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;./myapp&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Applications can also resolve them directly through the
&lt;a href=&quot;https://ifiokjr.github.io/sdk/overview/&quot;&gt;Monosecret SDKs&lt;/a&gt; for Rust, Python, Go, Ruby,
Node.js/TypeScript, Haskell, PHP, C#, and Swift (0.2+), all sharing the same
resolver so behavior stays consistent across languages.&lt;/p&gt;
&lt;p&gt;Providers own where secret values come from. SDKs give applications an
idiomatic way to consume them. Configuration remains a shareable declaration of
what is required.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;making-the-boundary-practical&quot;&gt;Making the boundary practical&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you maintain an application, stop adding passwords and tokens to ordinary
configuration schemas. Accept a file reference, credential, environment
variable, or provider instead.&lt;/p&gt;
&lt;p&gt;For NixOS, &lt;a href=&quot;https://github.com/ifiokjr/monosecret/issues/65&quot;&gt;Monosecret issue #65&lt;/a&gt;
tracks how an official integration could declare and resolve secrets without
per-module substitution glue.&lt;/p&gt;
&lt;p&gt;Consistent secret handling across developer machines, CI, and production used
to require infrastructure that only dedicated platform teams could build. A
project of any size should be able to separate secrets from configuration
without building its own secrets platform first.&lt;/p&gt;</content:encoded></item><item><title>Monosecret 0.12: audit logs and coding agents</title><link>https://ifiokjr.github.io/monosecret/blog/secretspec-0-12-audit-logs-and-coding-agents/</link><guid isPermaLink="true">https://ifiokjr.github.io/monosecret/blog/secretspec-0-12-audit-logs-and-coding-agents/</guid><pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;aside aria-label=&quot;Historical upstream article&quot;&gt;&lt;p aria-hidden=&quot;true&quot;&gt;Historical upstream article&lt;/p&gt;&lt;div&gt;&lt;p&gt;This article is preserved from the upstream Monosecret 0.12 release. For current
usage, use &lt;code dir=&quot;auto&quot;&gt;monosecret&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;monosecret.toml&lt;/code&gt;, and &lt;code dir=&quot;auto&quot;&gt;MONOSECRET_*&lt;/code&gt;; see the
&lt;a href=&quot;https://ifiokjr.github.io/concepts/audit/&quot;&gt;Monosecret audit documentation&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;&lt;/aside&gt;
&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/monosecret/releases/tag/v0.12.0&quot; title=&quot;Monosecret 0.12 release&quot;&gt;Monosecret 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;monosecret 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 Monosecret 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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&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/monosecret/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;monosecret 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;monosecret&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;monosecret&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;monosecret&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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&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/monosecret/config.toml&lt;/code&gt;), not the project’s &lt;code dir=&quot;auto&quot;&gt;monosecret.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/monosecret/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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;See &lt;a href=&quot;https://ifiokjr.github.io/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;$ monosecret run -- npm test&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&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 MONOSECRET_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 monosecret.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;monosecret&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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&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;MONOSECRET_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;monosecret&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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&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;MONOSECRET_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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;MONOSECRET_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;monosecret_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;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&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;monosecret.toml&lt;/code&gt; and is enforced by Monosecret, 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;MONOSECRET_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;monosecret&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&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>