<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Secrets Management on Matt Goodrich</title><link>https://mattgoodrich.com/tags/secrets-management/</link><description>Recent content in Secrets Management on Matt Goodrich</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Wed, 13 May 2026 12:00:00 -0700</lastBuildDate><atom:link href="https://mattgoodrich.com/tags/secrets-management/index.xml" rel="self" type="application/rss+xml"/><item><title>Stop Putting Secrets in .env Files: A 1Password Service Account for Claude</title><link>https://mattgoodrich.com/posts/1password-service-account-claude-secrets/</link><pubDate>Wed, 13 May 2026 12:00:00 -0700</pubDate><guid>https://mattgoodrich.com/posts/1password-service-account-claude-secrets/</guid><description>&lt;img src="https://mattgoodrich.com/posts/1password-service-account-claude-secrets/header.png" alt="Featured image of post Stop Putting Secrets in .env Files: A 1Password Service Account for Claude" />&lt;p>&lt;strong>&lt;code>.env&lt;/code> was already a compromise. AI agents make it a bad one.&lt;/strong>&lt;/p>
&lt;p>For human developers, a &lt;code>.env&lt;/code> file is a small, stable risk. It lives on one machine, it&amp;rsquo;s gitignored, the developer who wrote it is the only one reading it, and most of the time nothing ever goes wrong. The control isn&amp;rsquo;t great, but the blast radius is small.&lt;/p>
&lt;p>AI agents change that calculation. Now the file isn&amp;rsquo;t being read by the human who wrote it. It&amp;rsquo;s being read by a process that can shell out, edit code, paste into a transcript, run network calls, and occasionally do things you didn&amp;rsquo;t quite ask for. The &amp;ldquo;small, stable risk&amp;rdquo; assumption isn&amp;rsquo;t true anymore. The same plaintext file is now an input to a system whose outputs you don&amp;rsquo;t fully control.&lt;/p>
&lt;p>It&amp;rsquo;s already happening in the open.&lt;/p>
&lt;p>&lt;img src="https://mattgoodrich.com/posts/1password-service-account-claude-secrets/env-secrets.png"
width="1200"
height="1612"
srcset="https://mattgoodrich.com/posts/1password-service-account-claude-secrets/env-secrets_hu4157887489347537657.png 480w, https://mattgoodrich.com/posts/1password-service-account-claude-secrets/env-secrets_hu18090805344684618849.png 1024w"
loading="lazy"
alt="Twitter Users Baiting AI Agents to Reply with Their .env Contents"
class="gallery-image"
data-flex-grow="74"
data-flex-basis="178px"
>&lt;/p>
&lt;p>I&amp;rsquo;m not going to pretend I have a perfect answer. But I&amp;rsquo;ve moved away from &lt;code>.env&lt;/code> for anything an AI agent touches, and the pattern that&amp;rsquo;s working for me is a 1Password service account scoped to a single dedicated vault.&lt;/p>
&lt;h2 id="the-problem-with-env">The Problem with &lt;code>.env&lt;/code>
&lt;/h2>&lt;p>Three things have always been wrong with &lt;code>.env&lt;/code> files. They got worse the moment an AI agent showed up.&lt;/p>
&lt;p>&lt;strong>They&amp;rsquo;re plaintext on disk.&lt;/strong> Anything with file system access can read them. That used to mean &amp;ldquo;another process running as your user.&amp;rdquo; Now it also means &amp;ldquo;an AI agent that decided to grep your home directory for context.&amp;rdquo;&lt;/p>
&lt;p>&lt;strong>They&amp;rsquo;re easy to commit by accident.&lt;/strong> I&amp;rsquo;ve done it. You&amp;rsquo;ve probably done it. The &lt;code>.gitignore&lt;/code> catches most cases, but not all. &lt;code>git add -A&lt;/code> after a refactor that moved &lt;code>.env&lt;/code> to a slightly different path is the canonical way to leak credentials into history.&lt;/p>
&lt;p>&lt;strong>There&amp;rsquo;s no audit trail.&lt;/strong> A &lt;code>.env&lt;/code> file doesn&amp;rsquo;t know who read it, when, or why. If a credential leaks, you have no way to scope what got exposed.&lt;/p>
&lt;p>For human-only workflows, those tradeoffs were tolerable. For agents, the second and third problems compound. An agent that decides to commit a file isn&amp;rsquo;t going to second-guess itself the way a human reviewing a &lt;code>git diff&lt;/code> might. And if an agent does leak a secret (into a transcript, a comment, a paste-buffer) there&amp;rsquo;s no log of which secret, from where, used by what.&lt;/p>
&lt;h2 id="what-a-service-account-buys-you">What a Service Account Buys You
&lt;/h2>&lt;p>1Password&amp;rsquo;s service account model is built for non-human identities. The shape of it:&lt;/p>
&lt;ul>
&lt;li>A service account is a separate identity with its own token&lt;/li>
&lt;li>The account has access &lt;em>only to vaults you explicitly grant it&lt;/em>&lt;/li>
&lt;li>The token is revocable in one click, with no impact on the rest of your 1Password setup&lt;/li>
&lt;li>Every secret read is logged with the service account&amp;rsquo;s identity&lt;/li>
&lt;li>A given credential lives in exactly one place, so rotating or revoking it is one update, not a sweep across every &lt;code>.env&lt;/code> file that copied the value&lt;/li>
&lt;li>Secrets can be fetched at process-start via &lt;code>op&lt;/code> and never touch disk&lt;/li>
&lt;/ul>
&lt;p>That last point is the one that matters most. With a service account and the &lt;code>op&lt;/code> CLI, secrets exist in environment variables for the lifetime of the process and nowhere else. There&amp;rsquo;s no file to commit. There&amp;rsquo;s no plaintext at rest. The agent reads the secret when it needs it and the secret evaporates when the process exits.&lt;/p>
&lt;p>The single-location property is the one I keep noticing in everyday use. The same API key used to end up duplicated across however many project directories needed it, and rotating it meant hunting down each copy and hoping I didn&amp;rsquo;t miss one. With a vault entry, the value changes once and every script that resolves &lt;code>op://Claude/Anthropic/credential&lt;/code> picks up the new value on its next run. Revoking is the same shape: one delete, and the old credential is unreachable from everywhere at once.&lt;/p>
&lt;h2 id="my-setup-a-dedicated-claude-vault">My Setup: A Dedicated &amp;ldquo;Claude&amp;rdquo; Vault
&lt;/h2>&lt;p>The piece that actually scopes this for AI work is a dedicated vault.&lt;/p>
&lt;p>I have a 1Password vault called &lt;code>Claude&lt;/code>. It contains only the secrets I have explicitly decided that an AI agent on my machine can use: a handful of API keys, a couple of tokens, nothing else. My personal logins, my family&amp;rsquo;s shared vault, my work credentials, any secret I haven&amp;rsquo;t deliberately whitelisted for AI access: none of it is in this vault, and none of it is reachable by the service account.&lt;/p>
&lt;p>The service account is scoped to exactly one vault: &lt;code>Claude&lt;/code>. That&amp;rsquo;s the entire access surface. Even if the token leaked tomorrow, the blast radius is whatever&amp;rsquo;s in that one vault, which is also the set of credentials I already accepted some risk on by allowing AI to use them.&lt;/p>
&lt;p>There&amp;rsquo;s a side benefit I didn&amp;rsquo;t expect when I set this up: adding a new secret to the &lt;code>Claude&lt;/code> vault is now a deliberate act. It&amp;rsquo;s not a copy-paste into a half-edited &lt;code>.env&lt;/code>. It&amp;rsquo;s a moment of intentionality where I decide whether this credential is one I&amp;rsquo;m comfortable handing to an AI process. That friction is small, but it&amp;rsquo;s the right friction.&lt;/p>
&lt;h2 id="how-it-works-in-practice">How It Works in Practice
&lt;/h2>&lt;p>The flow is a service account token, the &lt;code>op&lt;/code> CLI, and &lt;code>op run&lt;/code> (or &lt;code>op read&lt;/code>) to inject secrets at the moment they&amp;rsquo;re needed.&lt;/p>
&lt;p>The token itself lives in the macOS keychain rather than in a shell init file. My shell exports it from the keychain at session start:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># In ~/.zshrc or equivalent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">export&lt;/span> &lt;span class="nv">OP_SERVICE_ACCOUNT_TOKEN&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>security find-generic-password &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> -s &lt;span class="s2">&amp;#34;op-service-account-claude&amp;#34;&lt;/span> -w 2&amp;gt;/dev/null&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Once that&amp;rsquo;s in the environment, scripts and tools fetch secrets via &lt;code>op&lt;/code>. The two patterns I use most:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Inline read — for one-off scripts&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">ANTHROPIC_API_KEY&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>op &lt;span class="nb">read&lt;/span> &lt;span class="s2">&amp;#34;op://Claude/Anthropic/credential&amp;#34;&lt;/span>&lt;span class="k">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># op run — for an entire process&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">op run --env-file&lt;span class="o">=&lt;/span>.env.template -- bun run my-script.ts
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>.env.template&lt;/code> is the file I&amp;rsquo;d normally call &lt;code>.env&lt;/code>, except instead of literal values it has &lt;code>op://&lt;/code> references:&lt;/p>
&lt;pre tabindex="0">&lt;code>ANTHROPIC_API_KEY=op://Claude/Anthropic/credential
OPENAI_API_KEY=op://Claude/OpenAI/credential
GROQ_API_KEY=op://Claude/Groq/credential
&lt;/code>&lt;/pre>&lt;p>This file &lt;em>is&lt;/em> safe to commit. There&amp;rsquo;s nothing sensitive in it, just a manifest of which secrets the script needs and where to fetch them. A human (or an agent) reading the template knows exactly what credentials are in play without ever seeing a value.&lt;/p>
&lt;p>When &lt;code>op run&lt;/code> executes the command, it resolves each &lt;code>op://&lt;/code> reference, exports the resolved value into the process environment for the duration of the run, and exits with the process. No file gets written. No value lives on disk. The agent gets the credential it needs and nothing more.&lt;/p>
&lt;h2 id="what-this-doesnt-solve">What This Doesn&amp;rsquo;t Solve
&lt;/h2>&lt;p>I want to be honest about the gaps, because nothing about this setup is bulletproof.&lt;/p>
&lt;p>&lt;strong>Once a secret is in the agent&amp;rsquo;s process memory, it&amp;rsquo;s a secret in memory.&lt;/strong> A prompt injection that exfiltrates an environment variable still works. &lt;code>op run&lt;/code> reduces the &lt;em>at-rest&lt;/em> surface; it doesn&amp;rsquo;t reduce the &lt;em>in-process&lt;/em> surface.&lt;/p>
&lt;p>&lt;strong>The service account token is itself a secret.&lt;/strong> I store it in the macOS keychain rather than in a file, but it&amp;rsquo;s still a credential I have to protect. Lose the keychain and you&amp;rsquo;ve lost the keys to the &lt;code>Claude&lt;/code> vault.&lt;/p>
&lt;p>&lt;strong>The agent can still print the secret if it wants to.&lt;/strong> Nothing stops Claude from echoing &lt;code>$ANTHROPIC_API_KEY&lt;/code> into a transcript or a commit. The vault scoping limits what&amp;rsquo;s available to leak; it doesn&amp;rsquo;t prevent the leak itself. That&amp;rsquo;s a process and prompt-design problem, not a credential storage problem.&lt;/p>
&lt;p>&lt;strong>Vault scoping is only as good as your discipline.&lt;/strong> If I get lazy and start dropping production database credentials into the &lt;code>Claude&lt;/code> vault because it&amp;rsquo;s convenient, I&amp;rsquo;ve defeated the whole point of the dedicated vault.&lt;/p>
&lt;h2 id="why-i-still-prefer-it">Why I Still Prefer It
&lt;/h2>&lt;p>The mental model that makes this click for me: secrets management for AI agents is the same problem as secrets management for CI/CD, scheduled jobs, or any other non-human identity. We&amp;rsquo;ve spent years getting the non-human-identity story right in production environments: short-lived tokens, scoped service accounts, audit logs, no static credentials in config files. The only reason &lt;code>.env&lt;/code> ever felt acceptable for local development is that the threat model was so small.&lt;/p>
&lt;p>AI agents make the local threat model bigger. The fix is to apply the production-grade pattern locally. A service account scoped to a single vault, secrets fetched at process-start via &lt;code>op&lt;/code>, no plaintext at rest. None of that is a new idea. We just keep forgetting to apply it the moment a new tool shows up that wants credentials.&lt;/p>
&lt;p>The &lt;code>.env&lt;/code> file was a compromise we made when the only thing reading it was us. It doesn&amp;rsquo;t have to be the compromise we keep making.&lt;/p></description></item></channel></rss>