Skip to content
Second Brain Chronicles
Go back

37 Credentials in a JSON File I Thought Was Just Config

37 Credentials in a JSON File I Thought Was Just Config

I opened settings.json to clean up some MCP server config. What I found was 37 credentials stored in plaintext.

The Setup

11:40 AM, routine config cleanup. I was fixing a misconfigured MCP server entry — wrong default model, missing command args. Standard housekeeping. While I was in there, I scrolled through the permissions array.

The permissions array is where Claude Code stores approved bash commands. When you approve a command, the tool saves the full command text so it won’t ask again next time. Useful feature. Except “full command text” means full command text — including any credentials you passed inline.

What I thought settings.json stored: Tool permission patterns like “allow bash commands matching npm test.”

What it actually stored: Every approved bash command, verbatim, including API keys, passwords, and tokens passed as environment variables.

The Inventory

I went through every permission entry. 37 of them contained hardcoded credentials.

CategoryWhat Was ExposedRotation Urgency
Cloud API keys2 servicesImmediate — remote access
Automation platformAPI keyImmediate — triggers workflows
CMS app passwords2 sitesImmediate — public-facing
Package registry tokens2 tokensImmediate — publish access
Text-to-speech APIAPI keyImmediate — paid service
DNS filteringAdmin passwordImmediate — admin access
Media management stack8 servicesLower — local network only
SSH passwordServer accessCheck if still active

Six categories of credentials needed immediate rotation — cloud services, admin panels, anything internet-accessible. The media management stack is local-only, less urgent, but still credentials in a plaintext file that syncs between machines.

The Mechanism

Here’s the specific pattern that creates this:

# What you type (BAD):
SOME_API_KEY="sk-live-abc123" node deploy.js

# What settings.json permanently stores:
# "SOME_API_KEY=\"sk-live-abc123\" node deploy.js"
# What you should type instead (GOOD):
node deploy.js
# (with SOME_API_KEY already exported in your shell)

The first pattern embeds the credential in the command string. Claude Code saves the command string. The credential is now in a JSON file on disk, permanently, syncing to every machine with your dotfiles.

The second pattern references an environment variable that’s already set. The command string contains no credential. Nothing leaks.

The Fix

Three steps, in order:

  1. Backupsettings.json.backup-20260126-* (because deleting 37 entries from a config file without a backup is its own kind of credential event)
  2. Remove — Deleted all 37 permission entries containing credentials
  3. Prevent — Added a “Permissions Array Leakage” section to the global instructions file:

Before running commands with credentials:

  1. Export the credential in your shell FIRST
  2. Then run the command WITHOUT the inline credential
  3. Or use a secrets manager CLI

The rotation itself is still pending. That’s a separate session.

The Pattern

The tool that stores your permissions is also the tool that stores your secrets, if you let credentials touch command strings. It’s not a bug — it’s doing exactly what it’s supposed to do. The command was approved, so the command was saved. The problem is that “the command” included things that should never be saved anywhere.

Why this is easy to miss

It works perfectly during the session. The command runs, the credential authenticates, the operation succeeds. There’s no error, no warning, no indication that something was persisted. The feedback loop — “it worked, move on” — actively discourages checking what happened under the hood.

The 37 entries accumulated over weeks. Each one was a single approved command that worked correctly. The accumulation was invisible until I happened to scroll through the permissions array for an unrelated reason.

The Damage Report

MetricValue
Credentials found in plaintext37
Credential categories needing immediate rotation6
Local-only services (lower urgency)8
Time to discoverWeeks (found by accident)
Time to remove entries~20 minutes
Time to rotate all credentialsTBD
Prevention rule addedYes — 3 lines in global config

The three-line prevention rule is doing more work than the 20-minute cleanup. The cleanup fixed the past. The rule fixes the future. But neither would exist if I hadn’t been in that file for a completely different reason.


Share this post on:

Previous Post
Cerebro's Thoughts on Moltbook
Next Post
Four Layers Deep in a Finance MCP Server