Shape the shell
Separate environment from secrets
Use environment variables for configuration without turning shell startup files, process lists, logs, or repositories into secret storage.
10 minute lesson
Environment variables are useful process input: they configure tools without editing code, and every language can read them. They are also the most common place developers accidentally store secrets.
The distinction to hold onto: environment variables are a delivery mechanism, not a secure vault. Child processes inherit them, and diagnostic output can expose them. Every subprocess your shell spawns — build scripts, npm packages, editor plugins — sees the full environment of its parent.
Audit what you already export
Check names without printing values:
env | cut -d= -f1 | sort
EDITOR
HOME
HOMEBREW_PREFIX
PATH
STRIPE_SECRET_KEY
...
Scan the list for anything named like a credential. A STRIPE_SECRET_KEY sitting there means some line in your startup files exports it to every process you will ever run. Piping through cut keeps the values off your screen and out of your terminal scrollback while you audit.
Where each kind belongs
Keep non-secret defaults in project documentation and in startup files: EDITOR, feature flags, local port numbers. These are safe to commit, safe to print, safe to inherit.
Load secrets from a password manager, Keychain, or a protected local file excluded from Git. If you use a .env file per project, exclude it before creating it:
echo ".env" >> .gitignore
chmod 600 .env
And scope it: a secret loaded by one project’s tooling, in that project’s directory, is far smaller a target than a secret exported globally in .zshrc.
How the leak actually happens
The realistic failure chain: a token goes into .zshrc “temporarily”. Months later you publish your dotfiles repository, or you paste env output into a GitHub issue to debug something unrelated. The token was inherited, printed, and shipped.
Never add env output to a bug report without review. And when a secret does leak, deleting the line or the comment is not enough — the value lives on in Git history and in whoever’s cache saw the issue. Revoke the credential itself and issue a new one. That is why the storage decision matters up front: the cheapest leak is the one that cannot happen.
Lesson completed