The short answer
Store the canonical copy of each key in an encrypted vault that locks itself, on a Mac with FileVault on and a short screen-lock delay, and never let the key reach your Git working tree.
That's four controls, and the order matters, because each one covers a threat the others don't:
| Layer | Stops | Does nothing about |
|---|---|---|
| FileVault | Stolen or resold Mac, powered off | Anything while you're logged in |
| Screen lock | Someone walking up to your desk | Code already running as you |
| Encrypted vault with auto-lock | Casual snooping, file copies, stale plaintext | Malware active while the vault is unlocked |
| Git + backup hygiene | The most common real-world leak | Keys pasted into chat by hand |
"Safest" is not a single app. It's the combination — and knowing which threat each layer actually addresses is what stops you from over-trusting any one of them.
1. FileVault: the floor, not the ceiling
Modern Macs encrypt the internal SSD in hardware whether you ask or not. What FileVault adds is binding the decryption key to your credentials. Without it, the key is available to anything that can boot the machine — which means a stolen laptop is a stolen .env file.
# Should print "FileVault is On."
fdesetup status
If it prints anything else, stop reading and turn it on: System Settings → Privacy & Security → FileVault. (Apple: FileVault)
The critical limitation, and the one people misread: FileVault protects data at rest. Once you log in, the volume is decrypted for the whole session. Every process running as your user reads your files in plaintext. FileVault is why a thief who grabs your closed MacBook at a café gets nothing useful. It is not why a malicious postinstall script in an npm package fails.
2. Screen lock: the layer everyone leaves at 20 minutes
An unlocked, unattended Mac hands over every secret on it, and no encryption helps because the disk is already decrypted.
# 0 = require password immediately after screen saver / sleep
defaults read com.apple.screensaver askForPasswordDelay
Set the grace period to something small — immediately, or a few seconds — in System Settings → Lock Screen. Then make locking a reflex: Control-Command-Q locks instantly, and a Hot Corner set to "Lock Screen" costs nothing.
More detail: how to auto-lock sensitive data on macOS.
3. An encrypted vault that locks itself
Layers 1 and 2 both share a weakness: they protect you when you're away. Most of your day is spent logged in with the screen unlocked, and during those hours a plaintext .env file is simply a plaintext file.
This is the gap an application-level vault closes. Because it holds its own encryption key separately from your login session, it can be locked while you remain logged in.
Why not raw Keychain for everything?
The login keychain is unlocked automatically when you log in and stays unlocked for the session. That's excellent ergonomics and a real security ceiling: any process running as you can ask for an item, and for items your own tools created, it will often get it.
There's also a scaling problem. Ad-hoc security add-generic-password entries are write-once, understand-never:
# Fine for one secret. Unmanageable at forty.
security add-generic-password -a "$USER" -s STRIPE_TEST_KEY -w 'sk_test_...'
security find-generic-password -s STRIPE_TEST_KEY -w
Six months later you cannot tell which of those entries is current, which project it belongs to, or whether it was ever rotated. See macOS Keychain for developers for where Keychain genuinely fits.
What a developer vault adds
PassStore keeps vault data encrypted with AES-256-GCM, wrapping the key with Argon2id so a weak master password costs an attacker real time rather than milliseconds. It unlocks with Touch ID through the Secure Enclave or with your master password, and it re-locks on a timer — so the window where secrets are readable is minutes rather than your whole workday.
Three features matter more than the encryption details, because they change behaviour rather than math:
- Auto-lock shrinks the exposure window without you thinking about it.
- Clipboard auto-clear means a copied key doesn't sit in the pasteboard — readable by any process running as you, and persisted to disk by most clipboard-history apps — until the next thing you copy.
- Vault health audit flags reused and weak values, which is how you discover that the same key is in four projects.
Full detail on the cryptography and threat model: Security. It's open source (MIT), so none of this requires taking our word for it: github.com/ilmakio/PassStore.
4. Keep keys out of Git and out of backups
This is where real leaks come from. Not cryptanalysis — a git add . at 1am.
# Ignore env files but keep a committed template
cat >> .gitignore <<'EOF'
.env
.env.*
!.env.example
EOF
# Stop tracking a file already committed (see the incident guide below)
git rm --cached .env
Add a scanner to your pre-commit hook so the rule is enforced rather than remembered:
brew install gitleaks
gitleaks protect --staged --redact
Full setup: keep secrets out of Git. If a key is already committed, go straight to I accidentally committed an API key — what now?.
Two backup paths people forget
Time Machine keeps deleted files. Deleting a .env from your project does not remove it from snapshots. A secret can live in backups for months after you think it's gone — which is one more reason revocation, not deletion, is what actually ends an exposure.
iCloud Drive syncs Desktop and Documents. If "Desktop & Documents Folders" is enabled and your projects live there, every .env file has been uploaded. Keeping code in ~/Developer or ~/code avoids this entirely. More: why your .env setup is probably leaking.
5. Reduce what a stolen key is worth
Storage is one half. The other half is making the secret on your laptop low-value in the first place:
- Test keys locally, production keys never. A
sk_test_key on a laptop is an inconvenience if it leaks; ask_live_key is an incident. - Scope narrowly. A read-only token scoped to one resource beats an admin token you rotate diligently.
- Prefer short-lived credentials where your stack allows it —
aws sso login, OIDC in CI, GitHub App installation tokens. A credential that expires in an hour barely needs a storage strategy. - One key per project. Sharing one vendor key across five repos means one leak rotates all five.
See how to store API keys safely without committing them.
What local encryption does not promise
Being direct about the limits, because a security tool that oversells is worse than one you understand:
- Malware running as you, while the vault is unlocked. No local vault survives this. It can read your memory, your clipboard, and anything you've decrypted. Auto-lock narrows the window; it doesn't close it.
- You pasting a key into an LLM chat, a ticket, or Slack. The most common exfiltration path in practice, and no encryption addresses it.
- Phishing and malicious dependencies. A postinstall script with your privileges is inside the boundary, not outside it.
- A compromised backup destination. Your vault file is encrypted, so that's fine. The plaintext
.envnext to it is not. - Team distribution. A local-first vault is deliberately not a sharing mechanism. If several people need the same production credential, you want a cloud secret manager with audit logs — see local-first vs cloud secret managers.
The honest framing: local encryption raises the cost of the opportunistic attacks that actually happen — a stolen laptop, a synced folder, a committed file, a colleague at your unlocked desk. It does not defend against an attacker already executing code as you. Anyone claiming otherwise is selling something.
Is a password manager enough?
For passwords, yes. For API keys, it depends what you need beyond storage.
Browser-oriented password managers are built around login forms: a URL, a username, a password, autofill. API keys have none of that shape. They need project grouping, .env export, rotation dates, and fast terminal-adjacent copy. You can force them into a password manager — many people do — and the friction shows up as workarounds: keys copied into notes fields, or left in .env because the vault was too slow to reach.
Comparisons: 1Password vs a local secret manager · Bitwarden for developers · best macOS apps for API keys.
A 15-minute setup
fdesetup status→ turn FileVault on if it's off.- Set screen lock to require a password immediately; bind a Hot Corner.
- Install a vault, set a strong master password, enable Touch ID, set auto-lock to 5–15 minutes.
- Move keys out of
.envfiles into the vault, one project at a time; commit.env.examplewith blank values. brew install gitleaksand wire it into pre-commit.- Check whether your projects sit inside an iCloud-synced Desktop or Documents folder — and move them if so.
- Downgrade every production key on the laptop to a test key.
Step 7 is the highest-leverage item on the list and the one most often skipped.