<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Adebisi Ayomide's Blog]]></title><description><![CDATA[Adebisi Ayomide's Blog]]></description><link>https://alsaheem.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 14 Sep 2026 23:48:34 GMT</lastBuildDate><atom:link href="https://alsaheem.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to Store Secrets in the Mac Keychain (and Use Them Like Environment Variables)]]></title><description><![CDATA[I used to keep API keys in .env because it was fast. Then I caught myself grepping my home folder for something unrelated and watching paths scroll past that file, or almost committing a backup copy. ]]></description><link>https://alsaheem.hashnode.dev/how-to-store-secrets-in-the-mac-keychain-and-use-them-like-environment-variables</link><guid isPermaLink="true">https://alsaheem.hashnode.dev/how-to-store-secrets-in-the-mac-keychain-and-use-them-like-environment-variables</guid><category><![CDATA[macOS]]></category><category><![CDATA[Security]]></category><category><![CDATA[keychain]]></category><category><![CDATA[Bash]]></category><category><![CDATA[devtools]]></category><dc:creator><![CDATA[Adebisi Ayomide]]></dc:creator><pubDate>Sat, 28 Mar 2026 17:26:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/60c84e98bbd8c506819aefb8/7b2967ab-3b97-4504-b2ae-a54ed517a48b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I used to keep API keys in <code>.env</code> because it was fast. Then I caught myself grepping my home folder for something unrelated and watching paths scroll past that file, or almost committing a backup copy. None of that is catastrophic every time, but it’s a bad habit. On a Mac you already have a place meant for secrets: <strong>Keychain</strong>.</p>
<p>This isn’t a pitch for a fancy secrets vault. It’s about the <strong>security</strong> tool that ships with macOS—handy for <strong>local dev</strong> tokens, DB URLs, signing keys, that sort of thing.</p>
<p>One thing to get straight up front: Keychain doesn’t literally store “environment variables.” It stores <strong>items</strong> (usually generic passwords) keyed by <strong>service name</strong> and <strong>account</strong>. You <strong>pull the value out</strong> with <code>security</code> and <strong><code>export</code></strong> it when you need it. Day to day it behaves like env vars; under the hood it’s a lookup, not a magic <code>.env</code> replacement.</p>
<p>You’ll need a Mac, a terminal, and if you want secrets to load automatically, willingness to touch <code>~/.zshrc</code> or similar.</p>
<h2>Save and read a secret</h2>
<p><strong>Add</strong> a generic password (Terminal may ask for Keychain permission the first time):</p>
<pre><code class="language-bash">security add-generic-password \
  -a "$USER" \
  -s "myapp-dev-api-key" \
  -w "sk_live_xxxxxxxx"
</code></pre>
<ul>
<li><strong><code>-a</code></strong> — account; often your macOS username or an app name.</li>
<li><strong><code>-s</code></strong> — service string; this is the <strong>handle</strong> you’ll use later. Make it unique, e.g. <code>myapp-dev-api-key</code>.</li>
<li><strong><code>-w</code></strong> — the secret. The problem is anything after <code>-w</code> can land in <strong>shell history</strong> (see below).</li>
</ul>
<p><strong>Read</strong> it (prints to stdout):</p>
<pre><code class="language-bash">security find-generic-password -s "myapp-dev-api-key" -w
</code></pre>
<p><strong>Use it as an env var</strong> for this shell only:</p>
<pre><code class="language-bash">export MYAPP_API_KEY="$(security find-generic-password -s "myapp-dev-api-key" -w)"
</code></pre>
<p>After that, anything that reads <code>MYAPP_API_KEY</code> behaves as if you’d sourced a <code>.env</code>—except the value never had to live in a plaintext file on disk.</p>
<h2>Avoiding shell history when you add the secret</h2>
<p>Apple’s own usage text says bluntly that <strong><code>-p</code> / <code>-w</code> on the command line is insecure</strong>. Two patterns that actually work:</p>
<p><strong>Built-in prompt (often the nicest):</strong> put <strong><code>-w</code> last and omit the value</strong>. <code>security</code> will prompt for the password; that path doesn’t shove the secret into your history the way <code>-w "secret"</code> does.</p>
<pre><code class="language-bash">security add-generic-password -a "$USER" -s "myapp-dev-api-key" -w
</code></pre>
<p>(When it asks, type or paste the secret; it’s the usual “no echo” style prompt.)</p>
<p><strong>Shell-side prompt</strong> if you prefer to stay in bash/zsh:</p>
<pre><code class="language-bash">printf 'Paste secret (hidden): '
read -rs SECRET
echo
security add-generic-password -a "\(USER" -s "myapp-dev-api-key" -w "\)SECRET"
unset SECRET
</code></pre>
<p><strong>Keychain Access</strong> still counts: <strong>File → New Password Item…</strong> and line up the fields with whatever you pass as <code>-a</code> / <code>-s</code> so <code>find-generic-password</code> can find the item later.</p>
<h2>Wiring it into how you actually work</h2>
<p><strong>Every new terminal</strong> (Zsh snippet in <code>~/.zshrc</code>):</p>
<pre><code class="language-bash">export MYAPP_API_KEY="$(security find-generic-password -s "myapp-dev-api-key" -w 2&gt;/dev/null)"
</code></pre>
<p><code>2&gt;/dev/null</code> just keeps noise down if the item doesn’t exist yet.</p>
<p><strong>Per project</strong>, a small <code>scripts/load-secrets.sh</code> that exports what that repo needs is reasonable—<strong>don’t commit values</strong>; document the <strong>service names</strong> in the README so someone else can add their own copy to Keychain.</p>
<p><strong>One shot:</strong></p>
<pre><code class="language-bash">MYAPP_API_KEY="$(security find-generic-password -s "myapp-dev-api-key" -w)" uv run python -m myapp
</code></pre>
<h2>What you gain, what you’re signing up for</h2>
<p>You lose the plaintext secret sitting in <code>.env</code> (and in a lot of accidental greps and backups). It’s <strong>built in</strong>, no extra install, and Keychain can <strong>nudge or block</strong> access per app if you tune <strong>Access Control</strong> on the item. For “this laptop only” dev keys, that’s often plenty.</p>
<p>The flip side is boring but real: <strong>Linux and Windows</strong> won’t help you here—those teammates need 1Password CLI, Doppler, SOPS, cloud IAM, whatever your team standard is. There’s <strong>no automatic team sync</strong>; new machine usually means re-adding items (iCloud Keychain exists but that’s a deliberate trust choice). If you <strong>echo</strong> secrets or log them, you’ve undone the point. <strong>CI</strong> wants provider-native secrets, not interactive Keychain. Onboarding is also slightly worse than “copy <code>.env.example</code>”—people need the <strong>exact service strings</strong> and commands written down somewhere.</p>
<p>I still reach for this for <strong>solo Mac dev</strong> and small personal projects where I want fewer sensitive files lying around. For <strong>shared, audited, rotated</strong> secrets at work, use the thing your platform team points you at.</p>
<hr />
<p>So: you store with <code>security</code>, load with <code>export</code> and <code>$(security find-generic-password … -w)</code>, and you get env-var ergonomics without a plaintext <code>.env</code> for that value. The cost is <strong>macOS-only</strong>, a bit more <strong>documentation</strong>, and <strong>discipline</strong> around history and logging.</p>
]]></content:encoded></item></channel></rss>