Security firm Apiiro published research on November 5 that made me open every project I own and grep for one file. They found 104,185 public copies of .claude/settings.local.json on GitHub, and 1,122 of them hold at least one real secret, 1,379 distinct credentials in current copies alone. Count git history, shared settings files, and saved agent transcripts, and the total reaches 2,539 secrets across 1,994 repositories. The file is written by a button most of us click without thinking: "Yes, and don't ask again."
The mechanism, exactly
When Claude Code wants to run a shell command, it asks for permission. If you pick "don't ask again", the approval is saved as an allow rule in .claude/settings.local.json in the project folder. The problem is what the rule contains: the rule is the command string itself, verbatim.
So when the agent proposes something like this, because that is what the migration actually needs:
PGPASSWORD=hunter2 psql -h db.staging.example.com -U app -d shop -f migrate.sql and you click "don't ask again", the file ends up with:
{ "permissions": { "allow": [ "Bash(PGPASSWORD=hunter2 psql -h db.staging.example.com -U app -d shop -f migrate.sql)" ] } } The password is now in a plain JSON file inside your repo tree. Of the secrets Apiiro classified, more than a third open a database directly: 379 database passwords and 122 Supabase service role keys.
Why the leak travels
Claude Code does try to keep the file out of git. The first time it writes the file inside a git repository, it adds **/.claude/settings.local.json to your global git excludes. But that protection lives in one user's git config on one machine. It never touches the repository's own .gitignore, so it does not travel with the project.
Clone the repo on a new laptop, or git init a folder that already contains the file, and nothing protects you. Apiiro also reports Claude Code itself sometimes commits the file without the user asking. And deleting the file changes nothing about exposure: old versions stay in git history, and history is in every fork and clone.
The part that surprised me most: scanners miss it
Apiiro ran gitleaks and TruffleHog over every public copy. Gitleaks flagged 564 of the 1,122 files with secrets. TruffleHog flagged 643. Neither flagged 215 of them. A clean scanner report is not a clean bill of health.
The gap is passwords inside commands. Scanners look for key formats with recognizable prefixes. PGPASSWORD=hunter2, a password after sshpass -p, or mysql -p looks like an ordinary word in an ordinary command line to a regex tuned for AKIA.. and ghp_...
The 20-minute audit
Do this today, before writing another allow rule:
- Search your GitHub org for both
.claude/settings.local.jsonand.claude/settings.json. - Check git history for those paths even where the file is gone today:
git log --all --full-history -- .claude/settings.local.json. - Rotate every credential those files have ever held, before any cleanup. Removing the file does not clear history or forks.
- Untrack it and protect it in the repo itself:
git rm --cached .claude/settings.local.json, then add the path to the repository's.gitignore. - Open the local copy on your own machine and delete every rule that contains a password, token, key, or connection string, even if it was never committed.
Apiiro's advice on ordering matters: rotate first, clean up second. One company they notified confirmed its key had been live.
Write allow rules that cannot leak
The fix is to stop approving literal commands that carry secrets. Two habits cover most of it.
Pass secrets as environment variables from a secrets manager, so the saved rule records $DB_URL and not the value:
Bash(psql $DB_URL -f migrate.sql) And approve patterns instead of exact commands. A prefix rule like this approves the whole script family without ever recording a connection string:
{ "permissions": { "allow": ["Bash(npm run:*)", "Bash(./scripts/migrate:*)"] } } The same release train that made this news cycle also gave teams a better lever: Claude Code 2.1.319 ships permissions.denyInsteadOfPrompting, which denies a would-be prompt and continues instead of teaching you to click "don't ask again" by reflex. For unattended runs, a deny rule for anything matching PGPASSWORD=* or *AKIA* in the command is cheap insurance.
Treat agent folders like .env
The wider lesson extends past one tool. Apiiro compared 16 coding agents and found Claude Code is the only one confirmed to save the full approved command to a project file by default, but .cursor/, .gemini/, .specstory/, and agent memory files all accumulate context that can include credentials. Treat them like .env: in pre-commit hooks, in CI secret scanning, in .gitignore templates, and in Docker build contexts and package file lists, because Apiiro notes the file can leave your machine inside an npm package or an image without git ever being involved.
Treat agent config folders as secret-bearing from day one, and audit the ones you already have. The 20-minute audit above needs nothing beyond git and an afternoon coffee.
Sources: The Hacker News, Ars Technica, Apiiro research write-up (November 5, 2026).