Guide

API key manager for macOS developers

Store, rotate, and copy API keys locally: threat model, why browser password managers fall short, Git safety, and how PassStore keeps tokens off Slack and out of Git history.

API keys are not passwords. They're machine credentials: long-lived, frequently over-scoped, used by code rather than typed by humans, and toxic the moment they appear in Git history, a screenshot, or a support ticket.

Most developers manage them by accident. A .env file per project, a few values in a password manager, a couple in Notes, one still in a Slack DM from onboarding. It works until a key leaks, at which point nobody can answer the only questions that matter: where else does this value live, and what does it have access to?

This page is the complete picture: why keys are structurally harder than passwords, what to require from a tool, how the pieces fit together across laptop, CI, and production, and where PassStore fits — including where it doesn't.


1. Why API keys are harder than passwords

Password managers were designed around a specific shape: a login form with a URL, a username, and a password, filled by a human in a browser. Almost none of that describes an API key.

PasswordAPI key
Used byA human, in a browserCode, in a terminal or a server
Has a login URLYes — drives autofillNo
Natural groupingBy siteBy project and environment
Typical countOne per serviceFive to fifteen per project
LifetimeUntil you change itOften years, unrotated
ScopeThe accountAnywhere from read-only to full admin
When leakedChange it, mostly doneRotate across every consumer without downtime
Fails howLogin refusedSilent — until the bill arrives

Four consequences follow, and they're what a good workflow has to address:

High entropy makes mis-copying easy. A truncated key produces an authentication error that looks identical to a permissions problem. People debug the wrong thing for an hour, then paste the key somewhere visible to compare it by eye. See how to copy environment variables without mistakes.

Scope is invisible at the point of use. Nothing about the string sk_live_51H8x… tells you whether it can read one customer or refund every charge. Two keys that look identical can differ by orders of magnitude in blast radius.

Sprawl is the default. One key, copied for convenience, ends up in four projects. Rotating it now breaks three things you forgot about, so nobody rotates it. Reuse is the mechanism by which a small leak becomes a large incident.

Failure is silent. A leaked key doesn't lock the account or alert anyone. Automated scrapers watch public commit feeds and act in seconds. The first signal is usually a bill, a rate-limit alert, or a vendor email.


2. What to require from an API key workflow

RequirementWhy it matters
Encrypted at rest, algorithm stated"Bank-grade security" means nothing. AES-256-GCM means something
Auto-lock while you stay logged inFileVault stops protecting you the moment you log in
Fast copy, no browserFriction decides behaviour: slow tools push keys back into .env
Clipboard auto-clearThe pasteboard is readable by any process running as you
Project and environment groupingPrevents pointing staging at the live payments key
Rotation metadata"When did this last change?" must be answerable
Reuse detectionFinds the key you copied into four projects
.env import and exportYou have existing files; migration must be possible
A working export path outNo lock-in. Test it before you commit
An honest threat modelA tool that oversells is one you'll misuse

Background reading: OWASP Secrets Management Cheat Sheet.

The two most commonly ignored rows are clipboard auto-clear and a working export. The clipboard is the actual transport for most secrets on a developer machine, and it's readable by everything running as you — most clipboard-history apps also persist it to disk. And any secret manager you can't leave has you hostage; verify the export works on day one, not the day you want to switch.


3. The architecture: three stores, not one

Most confusion about secret management comes from trying to make one tool cover every environment. It doesn't work, because the environments have genuinely different requirements.

EnvironmentStoreWhy
Developer laptopLocal encrypted vaultFast access, offline, personal, high churn
CICI provider's secret store, or OIDCNo Keychain on a runner; ephemeral by design
ProductionCloud secret manager, KMS, platform configNeeds rotation, audit, and RBAC

Two rules make this coherent:

.env files are transport, not storage. They're a format for getting values into a process, not a place for values to live. Generated, used, deleted.

Production credentials do not belong on a laptop. This is the single highest-leverage rule in secret management, and the most frequently broken one. If production keys are on developer machines "for convenience", no vault choice will save you — you've already multiplied your attack surface by the number of laptops. Use a break-glass process instead: a documented, audited path to get temporary production access when genuinely needed.

More: local-first vs cloud secret managers · a practical developer secret management setup for 2026.


4. The options, compared

OptionBest forReal limitation
.env filesNon-secret config; local test keysPlaintext; one git add . from a leak
Keychain AccessAd-hoc items, certificates, Wi-FiNo project model, no rotation data, no auto-lock
Password managers (1Password, Bitwarden)Teams, sharing, human logins, SSOBuilt around login forms; needs the CLI to be ergonomic
Developer vault (PassStore)Solo macOS dev, project-shaped keys, offlineNo sharing, no audit log, no password recovery
pass / sopsScripting, encrypted config in GitGPG key management; pass leaks secret names
Cloud secret managers (Vault, Doppler, AWS/GCP)Production, teams, audit, rotationOperational cost; overkill for one laptop

Detailed comparisons: best macOS apps for managing API keys · 1Password vs a local secret manager · Bitwarden for developers · open source secret managers compared.

Why raw Keychain isn't enough on its own

Keychain Services is solid, hardware-backed storage, and it's the right foundation. It isn't a developer secret manager, for structural reasons:

# Works. Doesn't scale, and leaks into shell history and ps output.
security add-generic-password -a "$USER" -s STRIPE_KEY -w 'sk_test_...'
security find-generic-password -s STRIPE_KEY -w
  • Your login keychain unlocks at login and stays unlocked all session. Great ergonomics, and it means it cannot offer auto-lock.
  • No project grouping. Item names become a convention you invented and will forget.
  • No rotation metadata, so nothing records when a value changed or who issued it.
  • Bulk UX is poor — Keychain Access.app is not built for forty developer secrets.

The right move is to use Keychain as the unlock mechanism beneath a tool that adds the missing layer. macOS Keychain for developers covers where it fits.


5. Naming and organisation

This is the part people skip, and it's what determines whether rotation is a ten-minute task or a two-day archaeology project. A key you can't identify is a key you'll never rotate.

Use a consistent, self-describing scheme:

<project>/<environment>/<vendor>_<purpose>
acme-api/dev/stripe_secret_key
acme-api/dev/stripe_webhook_signing_secret
acme-api/staging/postgres_url
acme-web/dev/sentry_dsn
infra/ci/github_pat_read_packages

Not:

key1
token
stripe
new_key_FINAL
STRIPE_KEY_2 (copy)

The naming rules that pay off:

  • Environment always in the path. The most expensive class of mistake is pointing staging at a production credential. Making the environment structural rather than remembered prevents it.
  • Vendor plus purpose, not just vendor. Stripe alone gives you a secret key, a publishable key, restricted keys, and per-endpoint webhook secrets. stripe is ambiguous; stripe_webhook_signing_secret isn't.
  • One entry per credential. Not one entry holding a pasted .env blob. Rotation targets individual values.
  • Match your repository names, so vault structure and Git remotes correspond without translation.
  • Never encode the value in the name. No stripe_sk_live_51H8.
  • Record what it can do. Note the scope in a field — read-only, refunds allowed, admin. Six months later this is the only way to assess a leak quickly.

Deeper: how to organize secrets across multiple projects · organize API keys without slowing down.


6. Rotation

Rotation is where key management is won or lost, and the mechanics are counter-intuitive enough that people avoid them.

The core rule: the new credential must be live everywhere before the old one dies. That overlap window is the entire safety mechanism.

  1. Inventory every consumer — CI, prod, staging, laptops, serverless, partner configs, container images.
  2. Issue the replacement with narrower scope than the original.
  3. Deploy everywhere while the old key still works.
  4. Verify with real traffic, not a green build.
  5. Revoke the old credential.
  6. Document the date and owner.

Full playbook with AWS, Stripe, GitHub, and Kubernetes specifics: how to rotate API keys safely.

Calendar-driven rotation is weaker than most policies imply — rotating every 90 days does nothing about a compromise on day 4. Better triggers: someone with access leaves, the key appears somewhere it shouldn't, a vendor reports suspicious activity, or the key can't be scoped down. Fixed intervals still earn their place as a backstop, because they force you to prove the rotation path still works.

The strongest move is to eliminate rotation where you can: aws sso login, OIDC in CI, GitHub App installation tokens. A credential that expires in an hour barely needs a storage strategy.


7. Git hygiene, which no vault replaces

A vault removes the reason for a plaintext file to exist. It doesn't stop you creating one.

# Ignore env files, keep a committed template
cat >> .gitignore <<'EOF'
.env
.env.*
!.env.example
EOF

# Verify — assumption is how leaks happen
git check-ignore -v .env

# Catch a file already tracked despite .gitignore (very common)
git ls-files --error-unmatch .env 2>/dev/null && echo "TRACKED — fix this"

# Enforce with a scanner rather than memory
brew install gitleaks
gitleaks protect --staged --redact

Also enable GitHub push protection, which blocks known secret patterns at push time — free for public repositories, and strictly better than finding out afterwards.

Step by step: keep secrets out of Git. If a key is already committed: I accidentally committed an API key.

The build-time trap

The highest-severity API key mistake isn't a commit — it's a build. Prefixed variables are inlined into JavaScript sent to browsers: NEXT_PUBLIC_* in Next.js, VITE_* in Vite. The key ends up on a CDN, cached, and possibly archived.

# Check what actually shipped
rg -o 'sk_live_[A-Za-z0-9]+' .next/static && echo "LIVE KEY IN THE BUNDLE"

No storage choice prevents this; it's an architecture question. How to prevent leaking API keys in frontend apps.


8. Threat model: what a local vault does and doesn't do

Being explicit, because a security tool you misunderstand is worse than one you don't have.

What it meaningfully protects against

  • A lost or stolen Mac. With FileVault plus vault encryption, the data is unreadable.
  • Folder copies, zips, and USB sticks. The secret isn't in your project tree, so it doesn't travel with it.
  • Accidental cloud sync. No plaintext file in Documents to be uploaded.
  • Git commits. Nothing to commit.
  • Casual observation. Auto-lock and one-value-at-a-time reveal beat cat .env during a screen share.
  • Stale copies. One canonical entry per secret instead of eight drifting files.

What it does not protect against

  • Malware running as you while the vault is unlocked. It can read decrypted memory and your clipboard. Auto-lock narrows the window; nothing closes it.
  • A malicious dependency. A postinstall script with your privileges is inside the boundary.
  • You pasting a key into an LLM chat, a ticket, or Slack. The most common exfiltration path in practice.
  • Phishing.
  • Team distribution. Deliberately not a sharing tool.
  • Forgetting your master password. No account means no recovery. Take encrypted backups.

The honest summary: local encryption raises the cost of the opportunistic leaks that actually happen — stolen laptops, synced folders, committed files, unlocked desks. It does not defend against an attacker already executing code as your user. Anyone claiming otherwise is selling something.

PassStore's full model: Security. It's MIT-licensed, so you can verify the claims rather than trust them: github.com/ilmakio/PassStore.


9. How PassStore implements this

  • AES-256-GCM vault encryption; Argon2id for password-based key derivation, so a weak master password costs an attacker real time
  • Touch ID / Secure Enclave unlock, or master password
  • No cloud sync, no account server — no backend exists for data to reach
  • Workspace grouping per project, with .env groups, database URLs, and SSH credentials as first-class types
  • Vault health audit for reused and weak secrets
  • Password and credential generator
  • Command palette and menu bar access, so copying a key doesn't mean leaving the keyboard
  • Clipboard auto-clear and auto-lock
  • Encrypted backup export and import
  • Native Swift/SwiftUI, macOS 26.0+, free and MIT

Download for macOS · Security overview · Source


10. A first-hour setup

  1. Turn on FileVaultfdesetup status should say On.
  2. Set screen lock to require a password immediately.
  3. Install a vault, set a strong master password, enable Touch ID, set auto-lock to 5–15 minutes.
  4. Inventory what you have:
    fd -H -t f '^\.env' ~/Developer
    
  5. Migrate one project, verify it still runs, then continue. Not all at once.
  6. Commit .env.example with blank values.
  7. Install gitleaks and wire it into pre-commit.
  8. Replace every production key on the laptop with a test key. Highest-value step here, and the one most often skipped.
  9. Take an encrypted backup and confirm you can restore it.

Do I need an API key manager if I use 1Password?

Probably not a second tool, if you use 1Password's CLI properly. Secret references plus op run inject values at process start with no plaintext on disk:

op run --env-file=.env -- npm run dev

That covers most of what a local vault offers, and adds sync and sharing.

Add a local vault if you want a free, open-source, offline option with no account; if you prefer not to keep every machine credential in a synced vault; or if you want project-shaped organisation built for keys rather than logins. What we'd argue against is using the browser extension as your API key workflow — that's slow enough that people leave values in .env "just for now", and the friction is what causes the leak.

Full comparison: 1Password vs a local secret manager.


What about teams?

A local-first vault is the wrong tool for shared credentials, and this is a design decision rather than a missing feature. Sharing requires a server: access control, revocation when someone leaves, and an audit trail. There's no backend here, so there's nothing to share through and nothing to log.

For teams:

  • Shared credentials → a manager with sharing and audit (1Password, Bitwarden, Doppler, Vault).
  • Production secrets → a cloud secret manager, injected by your platform. Never distributed to laptops.
  • Per-developer credentials → issue individual keys rather than sharing one. Then a leak is traceable to a person, and revoking it affects one developer.
  • .env files over Slack → never. How to share env files safely with your team.

The pattern that works: the team's cloud manager owns shared and production secrets; each developer's local vault owns their own dev keys. One owner per secret, and everything else holds a pointer, not a copy.


PassStore app iconDownload PassStore — local macOS vault for developer secrets.