DEV Community

Cover image for How to Store Secrets in the Mac Keychain (and Use Them Like Environment Variables)
Ayomide Adebisi
Ayomide Adebisi

Posted on

How to Store Secrets in the Mac Keychain (and Use Them Like Environment Variables)

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. None of that is catastrophic every time, but it’s a bad habit. On a Mac you already have a place meant for secrets: Keychain.

This isn’t a pitch for a fancy secrets vault. It’s about the security tool that ships with macOS—handy for local dev tokens, DB URLs, signing keys, that sort of thing.

One thing to get straight up front: Keychain doesn’t literally store “environment variables.” It stores items (usually generic passwords) keyed by service name and account. You pull the value out with security and export it when you need it. Day to day it behaves like env vars; under the hood it’s a lookup, not a magic .env replacement.

You’ll need a Mac, a terminal, and if you want secrets to load automatically, willingness to touch ~/.zshrc or similar.

Save and read a secret

Add a generic password (Terminal may ask for Keychain permission the first time):

security add-generic-password \
  -a "$USER" \
  -s "myapp-dev-api-key" \
  -w "sk_live_xxxxxxxx"
Enter fullscreen mode Exit fullscreen mode
  • -a — account; often your macOS username or an app name.
  • -s — service string; this is the handle you’ll use later. Make it unique, e.g. myapp-dev-api-key.
  • -w — the secret. The problem is anything after -w can land in shell history (see below).

Read it (prints to stdout):

security find-generic-password -s "myapp-dev-api-key" -w
Enter fullscreen mode Exit fullscreen mode

Use it as an env var for this shell only:

export MYAPP_API_KEY="$(security find-generic-password -s "myapp-dev-api-key" -w)"
Enter fullscreen mode Exit fullscreen mode

After that, anything that reads MYAPP_API_KEY behaves as if you’d sourced a .env—except the value never had to live in a plaintext file on disk.

Avoiding shell history when you add the secret

Apple’s own usage text says bluntly that -p / -w on the command line is insecure. Two patterns that actually work:

Built-in prompt (often the nicest): put -w last and omit the value. security will prompt for the password; that path doesn’t shove the secret into your history the way -w "secret" does.

security add-generic-password -a "$USER" -s "myapp-dev-api-key" -w
Enter fullscreen mode Exit fullscreen mode

(When it asks, type or paste the secret; it’s the usual “no echo” style prompt.)

Shell-side prompt if you prefer to stay in bash/zsh:

printf 'Paste secret (hidden): '
read -rs SECRET
echo
security add-generic-password -a "$USER" -s "myapp-dev-api-key" -w "$SECRET"
unset SECRET
Enter fullscreen mode Exit fullscreen mode

Keychain Access still counts: File → New Password Item… and line up the fields with whatever you pass as -a / -s so find-generic-password can find the item later.

Wiring it into how you actually work

Every new terminal (Zsh snippet in ~/.zshrc):

export MYAPP_API_KEY="$(security find-generic-password -s "myapp-dev-api-key" -w 2>/dev/null)"
Enter fullscreen mode Exit fullscreen mode

2>/dev/null just keeps noise down if the item doesn’t exist yet.

Per project, a small scripts/load-secrets.sh that exports what that repo needs is reasonable—don’t commit values; document the service names in the README so someone else can add their own copy to Keychain.

One shot:

MYAPP_API_KEY="$(security find-generic-password -s "myapp-dev-api-key" -w)" uv run python -m myapp
Enter fullscreen mode Exit fullscreen mode

What you gain, what you’re signing up for

You lose the plaintext secret sitting in .env (and in a lot of accidental greps and backups). It’s built in, no extra install, and Keychain can nudge or block access per app if you tune Access Control on the item. For “this laptop only” dev keys, that’s often plenty.

The flip side is boring but real: Linux and Windows won’t help you here—those teammates need 1Password CLI, Doppler, SOPS, cloud IAM, whatever your team standard is. There’s no automatic team sync; new machine usually means re-adding items (iCloud Keychain exists but that’s a deliberate trust choice). If you echo secrets or log them, you’ve undone the point. CI wants provider-native secrets, not interactive Keychain. Onboarding is also slightly worse than “copy .env.example”—people need the exact service strings and commands written down somewhere.

I still reach for this for solo Mac dev and small personal projects where I want fewer sensitive files lying around. For shared, audited, rotated secrets at work, use the thing your platform team points you at.


So: you store with security, load with export and $(security find-generic-password … -w), and you get env-var ergonomics without a plaintext .env for that value. The cost is macOS-only, a bit more documentation, and discipline around history and logging.

Top comments (0)