Apiiro found 2,539 secrets in 1,994 GitHub repos from Claude Code's "don't ask again" feature. Here's how to check your repo and rotate exposed keys.
Vamsi Tallapudi
Manager, Architect Technology at Cognizant
Claude Code's "Yes, and don't ask again" button has been quietly saving your passwords to a file that ends up on public GitHub. Security firm Apiiro scanned 104,185 public copies of .claude/settings.local.json and found 1,122 of them held at least one real secret, 1,379 credentials in that one file alone. Widen the search to git history, shared settings, and leftover agent transcripts, and the number jumps to 2,539 secrets across 1,994 repositories and 63 organizations.
That's not a hypothetical. Those are live database passwords, Supabase service keys, and SSH logins sitting on the public internet right now, found by Apiiro's research covered on The Hacker News and picked apart on Hacker News this week. If you use Claude Code and have ever clicked "don't ask again" on a Bash command, go check your repo. Seriously, before you finish this article.
Claude Code asks permission before running most shell commands. Every time it does, you get three choices: approve once, deny, or "Yes, and don't ask again." That third option is the one that bites you.
When you pick it, Claude Code doesn't save a general rule like "allow npm commands." It saves the exact command you just ran, character for character, as an allow rule in .claude/settings.local.json. If that command happened to include a database password, an AWS key, or a raw connection string, the credential goes into the file in plain text. Forever, until someone notices.
Apiiro's writeup, summarized by dev.to, put it bluntly: of 16 coding agents they checked, Claude Code was the only one confirmed to save the full approved command into a project file by default. Other tools either don't persist approvals the same way or store them as abstract patterns instead of literal strings.
Claude Code isn't completely careless here. The first time it writes .claude/settings.local.json inside a git repo, it adds the file to your global git excludes (not a repo .gitignore), so new commits shouldn't pick it up. That's documented behavior, confirmed in Anthropic's own settings docs.
The problem is everything that protection doesn't cover:
git init after Claude Code had already written the file, the exclude may never get applied correctly..claude/settings.local.json yourself instead of letting Claude Code write it, nothing ignores it automatically. You have to add it yourself.So a developer commits once, maybe before they even know the file exists, and that one commit lives in history forever. Even if you delete the file next week, the old version is still sitting in git log, in every fork, in every clone someone made before the cleanup.
Here's what Apiiro actually found hiding in those 1,122 files:
| Secret type | Count |
|---|---|
| Database passwords | 379 |
| Supabase service-role keys | 122 |
| Supabase account tokens | 103 |
| SSH logins (some with root access) | 43 |
| GitHub/GitLab/npm tokens | 42 |
| AWS keys | 23 |
| Private keys | 6 |
Database passwords alone make up a third of the haul. That's not an edge case, that's the default outcome of pasting a psql connection string or a migration command into Claude Code and clicking the lazy button.
You'd think secret scanners would mop this up before it ever reached a public repo. They caught a lot, just not everything. Across the 1,122 affected files, Gitleaks flagged 564 and TruffleHog flagged 643. Neither tool caught 215 of them.
Why? Most secret scanners look for patterns: an AWS key has a recognizable prefix, a JWT has a recognizable shape. A plain password sitting inside a shell command like psql postgres://admin:[email protected]/prod doesn't look like a secret to a regex. It looks like a normal connection string. That's exactly the kind of thing a human reviewing a diff would also skim past, which is sort of the whole point of "don't ask again" in the first place — it exists so you stop reviewing.
The Claude Code team responded in the Hacker News thread tied to the Apiiro report. The gist of it: .claude/settings.local.json is supposed to stay out of git by default through the global-excludes mechanism, and the team's recommendation is to lean on Auto Mode instead of manual "don't ask again" approvals, plus turn on GitHub's native secret scanning as a backstop.
Commenters weren't fully convinced. Auto Mode changes how permissions get granted, it doesn't change what gets written to disk when a command runs. Several people pointed out the real failure mode here is approval fatigue: after the tenth prompt in a session, people stop reading the command and just click yes. That's a human problem a settings file can't fix on its own.
Don't wait for a scanner to tell you. Run these yourself.
Check if the file is tracked at all:
git ls-files | grep settings.local.json
Anything printed here means the file is in your repo, which means it's in your history too.
Check what's actually in it, right now:
cat .claude/settings.local.json
Look at every entry under permissions.allow. If any of them contain a password, token, or connection string instead of a clean command pattern, you have a leak.
Check history, not just the current state:
git log --all --full-history -- "**/.claude/settings.local.json"
Even if the current file is clean, an old commit might not be. Deleting the file today doesn't erase yesterday's commit.
Check your whole org on GitHub, if you have admin access, with GitHub's code search:
gh search code "filename:settings.local.json" --owner your-org
Rotate first, clean up second. That order matters, because scrubbing git history takes time and the credential is compromised the moment it's public, not the moment you get around to fixing it.
git rm --cached .claude/settings.local.json
.gitignore so it doesn't come back:
echo ".claude/settings.local.json" >> .gitignore
git filter-repo or BFG Repo-Cleaner if the secret matters enough to justify rewriting history. Remember this also means coordinating a force-push and telling every collaborator to re-clone.psql $DATABASE_URL approved forever is harmless. psql postgres://user:pass@host/db approved forever is a leak waiting to happen.Honestly, both. Claude Code's default behavior of saving the literal command string is a design choice that makes this kind of leak easy, and Anthropic should probably default to pattern-based approvals or at least warn when a command looks like it contains a credential. That's a reasonable product fix and I'd be surprised if it doesn't ship eventually.
But also: nobody forced anyone to paste a plaintext database password into a terminal command and then commit the resulting settings file to a public repo. The "don't ask again" checkbox is doing exactly what it says on the box. The mistake is pasting secrets into commands at all, something teams have been telling each other not to do since long before AI coding agents existed.
This isn't the first time an AI coding tool's local config files have leaked something they shouldn't. We covered a similar story when Claude Code's user-agent string leaked email addresses, and the rotate-your-keys playbook here is basically identical to what we wrote up after the OpenCode breach and the LiteLLM backdoor. The pattern keeps repeating: a tool trusted with your credentials has a quiet config file, that file gets committed by accident, and the fix is always the same three steps — rotate, scrub, gitignore.
If you're running Claude Code on any repo with real credentials in it, go run that git ls-files check right now. It takes ten seconds. The alternative is finding out from a security researcher's spreadsheet.
New AI tools, automation workflows, and course drops — straight to your inbox. Join 2,400+ builders.
Libghostty 1.0 freezes a stable C ABI for Ghostty's terminal engine, already used by JetBrains IDEs (12.5M users), Neovim, KDE, and Turborepo.
MaxxBench mutates 89 DeepSWE tasks and finds Claude Opus 5.5 and Gemini 3.8 Flash, tied at 74%, split by 23 points, exposing benchmaxxing in coding models.
Gemini Omni 1.2 Flash cut per-second pricing 30% to $0.07/s and stretched clips to 15 seconds, but a full-length clip now costs about the same as the old one did.
Comments