Local-first secrets for developers (without another cloud)

If you have ever pasted an API key into Slack “just for a second,” duplicated a .env file across three laptops, or wondered whether your “private” GitHub repo is really a safe place for production credentials, you are not careless — you are normal. The defaults in our tooling nudge us toward speed, not safety.

This article is a grounded overview of local-first secret handling for software developers: what problem it solves, what it does not solve, and how it pairs with macOS platform primitives like Keychain. It ends with a concrete mental model you can reuse across projects — whether or not you use PassStore.


1. Hook: the “works on my machine” problem, but for credentials

Local development is messy:

  • You clone five repositories.
  • Each README says “copy .env.example to .env.”
  • Some teams use Doppler, Vault, or 1Password; others use nothing.
  • CI has its own secrets; production has another layer.

Over time, credential state becomes impossible to reason about. You cannot answer simple questions without guessing:

  • Which machine has the current staging database password?
  • Did anyone commit a token “temporarily” and force-push?
  • Is that Stripe key test mode or live?

Local-first secret management is the bet that, for day-to-day development, your Mac should be the system of record for your dev credentials — not a plaintext file that syncs via Dropbox, not a chat log, and not a spreadsheet.


2. Why this is a real problem (not security theater)

Secrets fail in boring ways:

Failure modeWhy it happens
Git history leaks.env was added before .gitignore, or a tool generated a tracked file.
Forks and templatesA “private” repo becomes public, or a template repo ships with real keys.
Backup sprawlTime Machine, cloud sync folders, or zip exports copy .env blindly.
Clipboard and screenshotsKeys end up in screen shares, ticket attachments, or shared notes.
Dependency confusionBuild scripts echo environment for “debugging” and CI logs retain it.

OWASP’s Secrets Management Cheat Sheet stresses avoiding storage of secrets in source code and rotation when exposure is suspected. The hard part is making those policies easy enough that developers actually follow them.


3. Common mistakes (even on good teams)

Mistake A: treating .env as “secure because it is gitignored”

.gitignore only affects future commits. It does not:

  • Remove history if the file was ever committed.
  • Stop you from copying the repo into a USB stick.
  • Stop macOS Spotlight or backup tools from indexing the folder (unless you exclude it).

See our deeper walkthrough: Why your .env setup is probably leaking.

Mistake B: using the same secrets for dev and prod “to keep things simple”

Convenience creates blast radius. If your dev key is compromised, attackers may hit the same service tier you use in production — or worse, you may think you are on test data when you are not.

Mistake C: putting secrets in the frontend bundle

Anything shipped to the browser is public to a determined user. NEXT_PUBLIC_*, VITE_*, and similar patterns are for non-secret configuration only. For server-side keys, use server runtime environment or a backend proxy.

Mistake D: assuming a password manager alone fixes developer workflow

General password managers are excellent for human passwords. They are often awkward for:

  • Rotating machine-local API keys weekly.
  • Copying fifteen variables into a terminal session.
  • Representing per-repo groupings that mirror how you think about work.

That is why many teams add a developer-specific vault or secret workflow on top.


4. A sane baseline architecture (generic)

Think in layers:

  1. Production and shared environments
    Use your platform’s native secret store (AWS Secrets Manager, GCP Secret Manager, Kubernetes secrets with tight RBAC, etc.). Developers should not need production credentials on laptops unless there is a strong operational reason.

  2. Continuous integration
    Inject secrets via CI provider secret storage (GitHub Actions secrets, GitLab CI variables, etc.). Never echo secret values in logs.

  3. Local development
    Use short-lived credentials where possible (OIDC, scoped tokens). Where you must store long-lived dev keys, prefer OS-backed storage and explicit copy/paste workflows over a pile of .env files on disk.

  4. Documentation
    Commit .env.example with placeholder values only — never a real key, even “expired” ones.

Example .env.example (safe to commit):

# Copy to .env and fill with your own dev credentials (do not commit .env)
DATABASE_URL="postgresql://USER:PASSWORD@localhost:5432/myapp_dev"
STRIPE_SECRET_KEY="sk_test_..."   # use test keys only
API_SIGNING_SECRET="generate-with-openssl-rand"

Generate a strong random secret locally:

openssl rand -base64 32

5. Where local-first tools fit (and how PassStore approaches it)

Local-first means: the primary copy of your dev vault lives on your machine, under your control, without a mandatory cloud sync layer for the core product flow.

PassStore is a native macOS app aimed at developer secrets: API keys, credential bundles, and environment-style groupings. From a security-design perspective (full detail on our Security overview):

  • The vault is encrypted at rest (AES-256-GCM) with password-derived keys (Argon2id for current vaults).
  • Sensitive items can be stored using Apple Keychain Services, aligning with platform conventions for access control and hardware-backed protection where available.
  • The threat model is honest: PassStore does not claim to stop malware with full control of your unlocked session. It does help keep material off Git, chat, and unnecessary network paths.

What to use PassStore for (typical):

  • Organizing per-project secrets the same way you organize repositories.
  • Reducing friction when you need to copy a key into a terminal or config — without leaving secrets in long-lived plain files.
  • Keeping a single place on macOS that you audit, back up, or migrate intentionally.

6. Worked example: structuring secrets per repository

Suppose you maintain api, web, and worker services.

Anti-pattern: one mega .env at ~/secrets/everything.env with hundreds of lines. You will eventually paste the wrong DATABASE_URL into the wrong shell tab.

Better pattern:

  • One logical workspace per repo (or per product), each with:
    • dev / staging / prod-like groups (even if prod keys are rarely on the laptop).
    • Named entries: DATABASE_URL, STRIPE_SECRET_KEY, JWT_SIGNING_KEY, etc.
  • Shell session discipline: export variables only for the terminal tab you are using, or use your stack’s supported loader.

For patterns that involve many .env files and switching contexts, read How to manage multiple .env files across dev, staging, and prod.


7. When a cloud secret manager is still the right tool

Local-first is not a religion. Cloud managers (HashiCorp Vault, cloud vendor secret stores, Doppler, etc.) shine when:

  • Rotation and audit logs are mandatory compliance requirements.
  • Many machines need the same secret material with fine-grained RBAC.
  • Dynamic secrets (short-lived database users, etc.) are in play.

We compare trade-offs plainly in Local-first vs cloud secret managers for developers.


8. Soft CTA

If your goal is fewer leaked .env files, less clipboard anxiety, and a vault that stays on your Mac with Keychain-backed storage options, try PassStore — free download and skim the Security overview so you know exactly what is — and is not — promised.

Related reading