All posts 🗑️

Why We Deleted Every .env File — And What Replaced Them

It started at 1am on a Tuesday. We were rotating a Cloudflare API token — routine stuff, the old one was about to expire. We updated the .env in the active project, ran the deploy, everything worked. Then the staging environment for a different project broke. Same token, different .env, still pointing to the old value.

Fixed it. Then a third project broke the next morning.

That is when we ran the command that changed everything:

$ find ~/dev -name ".env" -not -path "*/node_modules/*" -not -path "*/.git/*"
./boundless-learning/.env
./gitpulse/.env
./gitpulse/api/.env
./noxterm/website/.env
./blindspot/.env
./blindspot/api/.env
./112schade/.env
./bitz-snoek/.env
./playnist/.env
...

$ find ~/dev -name ".env" -not -path "*/node_modules/*" -not -path "*/.git/*" | wc -l
47

Forty-seven .env files. On one machine.

47
.env files on one machine
6
duplicated keys across projects
8 months
forgotten with live credentials

The .env file audit that started it all

We spent the next hour opening every single one.

The duplicates. The Cloudflare API token — the one that just broke three projects — appeared in 6 different files. An OpenAI API key was in 8. The same Postmark server token sat in 4 projects, two of which had not been touched in over a year.

The expired ones. A Stripe test key that had been rotated months ago was still sitting in three .env files. It no longer worked, but nobody cleaned it up. If it had been accidentally used in production, the result would have been silent auth failures — mysterious 401s at 2am with no explanation.

Critical
The dangerous ones. A healthcare API project had a .env with a production database connection string. Full admin credentials. The project was archived — untouched for 8 months. But the credentials were still valid. Anyone with access to the machine could have connected to a production database with patient-adjacent data.

The forgotten ones. A side project from early 2024 — a weekend experiment, completely forgotten — still had a live Stripe secret key in its .env. Not a test key. The real one. Connected to a real account with a real credit card.

Forty-seven plaintext files with zero authentication, scattered across the filesystem, containing credentials we could not even remember storing. Some valid, some expired, no way to tell which without checking each one manually.

Migrating from .env files to macOS Keychain

We decided to move everything to the macOS Keychain using NoxKey and delete every .env file on the machine. The whole process took one afternoon.

The workflow for each project:

.env workflow
# Plaintext file, no auth
$ cat .env
DATABASE_URL=postgresql://...
OAUTH_CLIENT_SECRET=abc...
OPENAI_API_KEY=sk-proj-...

# Hope you .gitignored it
# Hope no agent reads it
# Hope you remember to update it
NoxKey workflow
# Step 1: Drag the .env file onto the NoxKey
#         menu bar icon. A native review sheet
#         opens, listing every key (values masked).

# Step 2: Pick the org/project (noboxdev/gitpulse),
#         confirm the keys you want to keep, hit Import.
#         One Touch ID writes the whole batch.

# Step 3: The same sheet shows what landed.
#         Click any key to peek the first 8 chars
#         and verify it matches the .env value.

# Step 4: Delete the liability
$ rm .env
Drag .env onto menu bar Review sheet (values masked) One Touch ID writes batch Peek to confirm Delete .env

For projects that shared secrets — like the Cloudflare token that lived in 6 places — we stored it once under a shared prefix. Copy the value to the clipboard, click "Add Secret" in the menu bar app, pick the shared org, and paste:

# Same thing the menu bar "Add Secret" button does:
#   noxkey_set(account: "shared/general/CLOUDFLARE_API_TOKEN",
#              clipboard: true,
#              field_type: "api_key")
# → Approval sheet appears in the menu bar app, one Touch ID writes it.

One token. One location. Accessible from any project. When we rotate it next time, we update it once. Not six times. Not three-out-of-six times.

The healthcare API credentials got an extra layer. Open the secret in the menu bar app and toggle Always require Touch ID:

# noboxdev/healthcare-api/DATABASE_URL → strict
# Excluded from any session unlock. Touch ID every single read.

Strict mode means that secret always requires Touch ID, even during a session unlock. No shortcuts for credentials that could expose patient data.

The first week without .env files

The first two days were friction city.

Every time we opened a terminal, muscle memory reached for the .env that was not there anymore. Instead of source .env or letting dotenv auto-load, the workflow was asking the agent to fetch each secret from NoxKey — and seeing a Touch ID prompt every time.

Day two almost broke us. Debugging a webhook integration, restarting the server about fifteen times in an hour. Touch ID fifteen times. It felt excessive.

Tip
Then we discovered prefix loads:
# Agent calls:
#   noxkey_get(account: "noboxdev/blindspot", session: "4h")
# → Touch ID once, every key under noboxdev/blindspot loaded
#   into the shell through an encrypted handoff script.
One Touch ID authenticates the whole prefix and loads every key under it into the shell environment in a single handoff. Subsequent reads under the same prefix skip the prompt for the rest of the session window. Run it at the start of a work session and forget about it.

By day four, the new workflow felt natural. By end of week, it was invisible.

How AI agent security became the bigger win

Two weeks after the migration, we were pair-programming with Claude Code on the Blindspot project. The agent needed the Postmark token to test an email integration. Old workflow: it would have read the .env and the raw token would be sitting in the conversation context. Logged. Visible. Potentially leaked in an error message.

Instead, the agent called noxkey_get through NoxKey's bundled MCP server. NoxKey detected the agent by walking the process tree, encrypted the value, wrote a self-deleting temp script, and returned a source command. The agent ran that one command in Bash. The secret reached the shell environment, but the raw value never appeared in the conversation.

We had not even been thinking about AI agent security during the migration. We deleted our .env files because of the duplication and rotation mess. The agent safety was a side effect — and turned out to be the more important benefit. We use AI agents every day now. Every single session would have been reading plaintext secrets if those .env files still existed.

For more on this attack surface, we wrote about six specific ways agents can leak your secrets.

Six months without a single .env file

Key rotation is a non-event. When the Cloudflare token expires, we update it in one place — open the secret in the menu bar app, paste the new value, done. Every project picks up the new value next time the agent fetches it. No hunting through directories. No grepping for old values. No "which three of the six copies did we forget to update?"

We know exactly what we have. The menu bar app's tree view shows every secret on the machine, organized by org/project. No more discovering a forgotten Stripe key in an archived repo. If it is in the Keychain, we can see it. If it is not, it does not exist on this machine.

# Menu bar tree (same data agents see via noxkey_show):
noboxdev/blindspot/POSTMARK_SERVER_TOKEN
noboxdev/blindspot/DATABASE_URL
noboxdev/gitpulse/DATABASE_URL
noboxdev/gitpulse/OAUTH_CLIENT_SECRET
noboxdev/gitpulse/OPENAI_API_KEY
noboxdev/healthcare-api/DATABASE_URL          [strict]
shared/general/CLOUDFLARE_API_TOKEN
shared/general/CLOUDFLARE_ACCOUNT_ID
...

New projects start clean. No .env.example to copy and fill in. Add the secrets once through the menu bar app and let the agent fetch them on demand. The project directory has zero credential files. Nothing to accidentally commit, nothing for an agent to read, nothing to forget about when the project gets archived.

The background anxiety about plaintext credentials is gone.

The anxiety is gone. This one was unexpected. We did not realize how much low-grade background worry those plaintext files caused. "Did we .gitignore that correctly?" "Is that old project's .env still sitting there with live keys?" "Did the AI just read our database credentials?" Those questions do not exist anymore. The secrets are in the Keychain, behind Touch ID.

The honest downsides of leaving .env behind

It is macOS only. We work exclusively on macOS, so this is not a limitation for us. If you are on a mixed team, your Linux colleagues need a different solution. The principle is the same — use your OS credential store — but NoxKey will not help them.

Onboarding new team members takes an extra step. Instead of "here is the .env.example, fill in your keys," it is "install NoxKey from the App Store, click Add Secret, paste each value." More steps the first time. Simpler every time after.

Some tools expect .env files. Docker Compose, certain Node.js frameworks, Vercel's local dev server. For those, we let the agent load the prefix into the shell, then have it write a temporary .env from those env vars, run the tool, and delete the file. Not perfect. But the alternative is 47 plaintext files with no authentication.

Run this command right now

find ~/dev -name ".env" -not -path "*/node_modules/*" -not -path "*/.git/*" | wc -l

Whatever number you see — that is how many plaintext files with zero authentication are on your machine right now, containing credentials that any process, any agent, any accidental git push can expose.

NoxKey is not the only answer. But .env files are the wrong answer. Use your Keychain. Use 1Password CLI. Use something with actual authentication. Stop treating plaintext files as secret storage.

Key Takeaway
47 .env files with zero authentication, scattered across one machine, containing credentials we could not even remember storing. The fix: drag each .env onto the NoxKey menu bar app, confirm the keys in the native review sheet, write them all under one Touch ID, then delete every .env file. One afternoon of work eliminates plaintext secrets, duplicate key rot, and AI agent exposure — permanently.

If NoxKey is the route you want to take:

Download on the Mac App Store

Free, no account, no cloud, your secrets never leave your machine. The migration took one afternoon. Six months later, there is no going back.

Frequently asked questions

How many .env files does the average developer have?
We found 47 across one machine — spanning active projects, archived repos, and forgotten prototypes. Many contained duplicate or expired keys. Ask your AI agent to scan your dev folder (it will use NoxKey's noxkey_scan MCP tool, which is read-only and just lists keys — no values) and you will find yours.
What replaces .env files?
The macOS Keychain. It's hardware-encrypted, protected by Touch ID, and already on your Mac. NoxKey wraps it with a developer-friendly menu bar app and a bundled MCP server for AI agents: drag a .env onto the app to import it, then your agent retrieves secrets through Touch ID-gated MCP calls that load them into your shell as env vars.
Is it safe to delete .env files after migrating?
Yes, once you've reviewed the import sheet (which lists every key as it lands) and tapped any key in the menu bar app to peek the first 8 characters and confirm the value matches the original. We recommend keeping a backup for 48 hours, then deleting permanently.
Does this work with Docker and CI/CD?
NoxKey is for local development. For Docker and CI/CD, use your provider's secret management (GitHub Actions secrets, AWS Secrets Manager, etc.). NoxKey replaces the plaintext .env files on your development machine.