API Keys Don't Belong in Your Shell Config Files

The risk of putting API keys in .bashrc / .zshrc has long been underestimated. This article lays out safer secret-storage approaches and a migration path.

API keys should not live in your shell config files. It’s a habit many developers have, chosen for convenience, but the risk has been underestimated for a long time.

A recent operational mistake brought this home for me: a command couldn’t find its target file, so the fallback ran env, dumping the entire terminal environment variables out. A dozen API keys went straight into an AI conversation’s context, and I had to rotate all of them.

The incident was small, but it revealed a structural problem: keeping keys in shell config files means they are always exposed to every child process and every tool’s view.

Why shell config files aren’t a good place for API keys

Different shells have different config files, but they all face the same problem:

  • zsh: ~/.zshrc, ~/.zprofile
  • bash: ~/.bashrc, ~/.bash_profile, ~/.profile
  • fish: ~/.config/fish/config.fish

Writing export API_KEY=xxx in these files has these consequences:

  • Every terminal session loads them automatically at startup, so any child process can read them
  • Commands like env and printenv can reveal the plaintext at any time
  • AI tools, log collectors, and debug output may accidentally capture them
  • If the config file gets synced to a dotfiles repo, they become public directly

The root problem is: the key’s lifetime is far longer than the time it’s actually used.

Better approaches

Option one: project-level .env + precise injection

The lightest improvement. Each project only holds the keys it uses, extracted precisely via a script without polluting the global environment.

run.sh example:

Pros: simple, no dependencies, works on every operating system.

Cons: the key still sits on disk in plaintext.

Option two: macOS Keychain

System-level encrypted storage. The key is stored encrypted in the Keychain and only decrypted into memory at the moment it’s read; cat, grep, and env can’t see it.

Storing:

Reading in a script:

Experience: When you log into your Mac, the system Keychain unlocks automatically, so scripts can read values directly for the whole session without asking for a password. The first time you access an item, the system shows a single authorization dialog; click “Always Allow” and it never appears again.

This is the most direct replacement for storing keys in shell config files, and it has almost no impact on your workflow. It’s macOS-only; Linux and Windows have their own equivalents (secret-tool, Windows Credential Manager).

Option three: Bitwarden CLI (self-hosted)

If you have a Bitwarden instance (including a self-hosted Vaultwarden), you can use the official CLI, bw, to pull keys dynamically within scripts.

Configuring the self-hosted address:

Logging in and getting a session:

The session stays valid within the current terminal session and expires when you close the terminal, so it isn’t persistently exposed.

Reading a specific entry in a script:

bw get password looks up an entry by name and returns its password field. You can also use bw get notes to fetch the notes field, which is handy for keys with complex stored formats.

Best fit: scenarios where multiple devices share the same set of keys, or where you need cross-platform support (macOS/Linux/Windows are all supported). Keys are centralized in Bitwarden, so switching devices only requires logging in again rather than manually migrating .env files.

Migration suggestions

If your shell config files currently hold lots of export KEY=xxx:

  1. Organize by project; give each project its own .env with only the keys that project uses
  2. Migrate high-value credentials (database passwords, payment-related) to Keychain or Bitwarden
  3. Remove all the export KEY= lines from your shell config files
  4. Make sure .env files are added to .gitignore

You don’t have to do everything at once. Migrate the most sensitive ones first, and move the rest as you touch each project.

Summary

Approach Plaintext on disk Cross-platform Cross-device Dependency
Shell config files ✓ (dangerous) Manual sync needed None
Project .env Manual sync needed None
macOS Keychain Built into the system
Bitwarden CLI bw CLI

There’s no perfect solution, but any of these is better than shell config files. The most pragmatic starting point is: clear the keys from your config files and switch to project-level .env, then gradually move high-value credentials to Keychain or Bitwarden.