Claude Code settings.local.json Secret Leaks: Audit 'Don't Ask Again' Rules and Rotate Exposed Keys
When you choose 'Yes, and don't ask again' in Claude Code, it saves the approved command exactly as typed into settings.local.json inside the project. If the command held a credential, the file now holds it too. Security researchers have found thousands of these secrets in public repositories, Docker images and npm packages. This guide gives engineering leaders a sequence called Find, Kill, Clean, Close: locate every copy, rotate the credentials before cleaning anything, and then shut the paths the file leaks through.
GitGuardian found 5,343 secrets that were still working when detected, all sitting in public copies of a single Claude Code project file, .claude/settings.local.json. Credentials land there because Claude Code asks permission before it runs a shell command, and one of the answers it offers, "Yes, and don't ask again", writes the approved command into that file. GitGuardian and a second research team, Apiiro, have each published counts showing large numbers of credentials, from database passwords to API tokens, in public copies of this one file.
If you are an engineering manager, a platform or DevSecOps lead, or a CTO whose developers use Claude Code, this guide gives you a working sequence: find every exposed copy, kill the credentials, clean the repositories and artefacts, and then close the paths the file uses to escape.
What "Yes, and don't ask again" actually writes to disk
When a developer picks "Yes, and don't ask again" for a Bash command, two things happen. According to GitGuardian, Claude Code runs the command and writes an allow rule into .claude/settings.local.json in the project folder, and the rule is the command string itself, API key included. Think about the commands developers approve on a normal working day: a PGPASSWORD=... psql call to a staging database, an sshpass -p '...' ssh login, a mysql -p... session, or a curl carrying a bearer token. Approve any of those permanently and the credential is saved with the command.
Anthropic's documentation confirms this. The settings docs say that choosing "Yes, and don't ask again" for a Bash command saves that approval as an allow rule in settings.local.json. The permissions docs add that permanent approvals go to .claude/settings.local.json at the root of the git repository, resolved through worktrees to the main checkout.
Anthropic has had a public report of this behaviour since August 2025.
How big the exposure is: two research counts, read side by side
Apiiro published its research on November 6, 2026. It reported that 1,122 of 104,185 public copies of the file on GitHub contained at least one secret. Those files held 1,379 distinct secrets, including 379 database passwords, 43 SSH logins for public servers and 23 AWS keys. When Apiiro widened the search to older versions of the file, the shared settings file teams commit on purpose, and saved agent chat transcripts, the tally reached 2,539 secrets across 1,994 repositories.
GitGuardian's own scan found 31,088 distinct secrets in .claude/settings.local.json on public GitHub, across 23,276 repositories, with 5,343 of them still working when detected. The valid secrets in settings.local.json alone include 1,955 distinct Postgres connection strings, 292 SSH passwords and 941 Google API keys. Our reading of that mix is that much of the live risk sits in databases and servers, with third-party API accounts only one part of the picture.
| Dimension | Apiiro | GitGuardian |
|---|---|---|
| What was scanned | Public settings.local.json copies on GitHub; wider tally adds file history, shared settings and transcripts | settings.local.json on public GitHub, plus separate Docker Hub and CLAUDE.md analysis |
| Files or repositories | 1,122 of 104,185 files held a secret; 1,994 repositories in the wider tally | 23,276 repositories |
| Distinct secrets | 1,379 (2,539 in the wider tally) | 31,088 |
| Valid at detection | Not reported in these sources | 5,343 |
| Notable credential types | 379 database passwords, 43 SSH logins, 23 AWS keys | 1,955 Postgres connection strings, 292 SSH passwords, 941 Google API keys |
The two studies differ in scope, time window and detection method, so read them as separate measurements of one problem. Adding them together would double count, and neither one should be treated as a correction of the other.
GitGuardian also tracked the trend. Monthly leaks in settings.local.json peaked at 12,535 occurrences in April 2026 and fell to 959 by October. GitGuardian says it doesn't yet know what changed.
Why the built-in safeguard and your scanners miss it
Claude Code does ship a safeguard. Per the settings docs, the first time it writes the file in a git repository that doesn't already ignore it, it adds **/.claude/settings.local.json to the user's global git excludes file. That entry lives on the developer's own machine and stays out of the project's .gitignore. The Cloud Security Alliance research note explains why that falls short: a global exclude protects one developer on one workstation, and it doesn't travel with the repository to a teammate's clone, a CI runner, a fresh laptop, a container image or an npm tarball.
Detection is the second gap. In Apiiro's dataset, gitleaks flagged 564 of the 1,122 leaking files and TruffleHog flagged 643, but neither tool flagged 215 of them, mostly because the secrets were passwords embedded in command syntax such as PGPASSWORD=, sshpass -p or mysql -p. GitGuardian's numbers point the same way. Generic detectors (passwords, high-entropy strings, JWTs, bearer tokens, username and password pairs) produced 63% of all occurrences in settings.local.json: 44,755 of 71,525.
Beyond GitHub: Docker images, npm packages and CLAUDE.md
The file doesn't stay in git. GitGuardian examined public Docker Hub images and found 613 distinct secrets in Claude Code settings files, 99 of them valid when detected, spread across 543 image repositories and 7,625 tags.
Those tag counts show how a single mistake multiplies. One valid Cloudflare API token appeared in 559 tags of a single image, and one database password showed up in 643 tags across seven image repositories. According to GitGuardian, deleting the file from the repository does nothing for images that have already been pushed.
Package registries carry the same risk. Check Point research, cited in the CSA note, found 428 public npm packages that shipped a .claude/settings.local.json file, and 33 of those files, across 30 packages, contained credentials. The audit also has to go past settings files. GitGuardian found that CLAUDE.md files held 6,155 distinct secrets, 2,537 of them valid, the highest validity rate of any file it examined.
The Find, Kill, Clean, Close playbook
The order of the steps matters more than any single command. Find covers Steps 1 to 3, Kill is Step 4, Clean covers Steps 5 to 7, and Close is the prevention work in the section after this one. Kill comes before Clean because cleanup can't recall copies that already exist elsewhere: forks and already-pushed images keep the secret however thoroughly you rewrite history, and only rotation makes those copies worthless.
Step 1 (Find): Inventory
List every repository, container registry and published package your organisation controls, including archived repositories, internal mirrors and the personal forks people use for work. Anything missing from the list won't get cleaned.
Step 2 (Find): Search current trees and full git history
Look for .claude/settings.local.json, .claude/settings.json, CLAUDE.md and any exported agent transcripts. Search the full history as well as the current branch, because Apiiro's larger tally came from older file versions, shared settings files and transcripts. A path-filtered search on your code host makes a quick first pass. For a per-repository pass, we suggest a command along these lines, which also looks for .jsonl files in case your team's transcripts were saved in that format:
git log --all --name-only --pretty=format: | grep -E '[.]claude/|CLAUDE[.]md|[.]jsonl$' | sort -uTreat that output as a starting point for transcripts. Any saved outside git, or under other file names, need a separate search.
Step 3 (Find): Read the files, not just scanner output
These files are short enough that someone should open them and read the allow rules directly. For a history-wide search on large repositories, we suggest feeding revisions to git grep one at a time rather than all at once:
git rev-list --all | while read -r rev; do
git grep -nE 'PGPASSWORD=|sshpass -p|mysql[^|]*[[:space:]]-p[^[:space:]]|Authorization: Bearer' "$rev" -- '*.json' '*.jsonl' '*.md'
doneTeams may find a loop like this slow on long histories, and pattern searches of this kind tend to produce false positives. Treat every hit as a real credential until a person confirms it isn't.
Step 4 (Kill): Rotate or revoke every credential first
According to GitHub, if the sensitive data is a secret, the first step is to revoke or rotate it. In practice that means rotating the database password, reissuing the token and replacing the SSH credential, then checking that each old value no longer works. Until you've confirmed a credential is dead, assume it's live.
Step 5 (Clean): Untrack local settings and review shared settings
Stop tracking the local file and add the ignore rule to the project's own .gitignore, so it travels to every clone and CI runner:
git rm --cached .claude/settings.local.json
echo '.claude/settings.local.json' >> .gitignore
git add .gitignore
git commit -m "Stop tracking Claude Code local settings"We also suggest asking each developer to prune any rules in their own local copy of the file that contain credentials.
Step 6 (Clean): Rewrite history, rebuild images, republish packages
Once rotation is confirmed, rewrite history where it helps, rebuild affected images and republish clean package versions, accepting that some exposure will remain. GitHub warns that if the commit that introduced the sensitive data exists in any forks, it will continue to be accessible there. We suggest treating old package versions as still reachable unless you have confirmed otherwise.
Step 7 (Clean): Record what you did
Keep a short incident log. For each credential found, note the owner, the rotation date and any residual exposure you can't remove, such as forks, pulled images and old package versions. The log turns a one-off cleanup into something auditors and future teams can check, and it is worth folding this incident log into your agent incident runbook so the next leak follows a path people already know.
Checklist to hand to teams
- [ ] Find: repositories, registries and packages listed, including archived repos and forks
- [ ] Find: trees and full history searched for settings files, CLAUDE.md and .jsonl files; transcripts outside git searched separately
- [ ] Find: files read by a person, plus custom checks for PGPASSWORD=, sshpass -p and mysql -p
- [ ] Kill: every credential rotated or revoked, old value confirmed dead
- [ ] Clean: settings.local.json untracked and in the project .gitignore; settings.json stripped of credentials and reviewed in pull requests
- [ ] Clean: history rewritten where useful, images rebuilt, packages republished
- [ ] Clean: incident log completed, with forks and old package versions listed as residual exposure
- [ ] Close: the prevention controls below in place
Close: keeping secrets out of approved commands
The allow rule is the literal command string, so prevention comes down to keeping credentials out of that string.
- Use variables instead of values. Pass credentials through environment variables or a secrets manager, so the approved command holds a variable name rather than the secret. Set the variable outside the approved command, in the shell session or a secrets manager wrapper, because an inline
VAR=secret cmdstill writes the secret into the allow rule. Before: approvingPGPASSWORD=s3cret psql -h staging-db -U appsaves the password. After: with PGPASSWORD already set in the environment, approvingpsql -h staging-db -U appsaves no secret. For a stronger pattern, see how a credential proxy keeps API keys out of AI agents. - Read before you click. Before granting a permanent approval, we suggest reading the exact command, and if it contains a credential, approving it once instead. The same read-before-approve habit applies to remote agent approvals too.
- Commit the ignore rule and review the file. Put .claude/settings.local.json in every project .gitignore instead of relying on global excludes, and every so often prune rules developers no longer need.
- Block it in pre-commit and CI. Reject commits that add the local settings file, and flag command syntax carrying credentials that standard scanners may miss.
- Close the packaging paths. Add .claude/ paths to .dockerignore, and use an explicit files allowlist or ignore list for npm packages so the file can't ride along.
What Anthropic has said so far
The original bug report has an uneven history. The CSA note records that it was marked "not planned" on January 3, 2026, with no maintainer response shown on the page. The report was filed in August 2025, and the sources reviewed here don't explain the gap between that filing and the January closing date.
Anthropic has since responded in public, though it hasn't shipped a fix. Claude Code lead Boris Cherny told iTnews: "Historically Claude Code has not done secret scrubbing (there are many good tools for that), but we are looking into what we can do to prevent this." On Hacker News, Cherny listed possible approaches, including making sure auto mode catches this and building in basic secret scanning. He said the team would prototype a few of them to understand the tradeoffs before shipping something.
Caveats and limits of this guidance
- Validity is a snapshot. GitGuardian's 5,343 valid secrets reflect the moment of detection. Some may have been rotated since, and others may not.
- The decline is unexplained. GitGuardian doesn't know why monthly occurrences fell after April 2026, so the drop doesn't prove the problem is solved.
- Private repositories are out of scope. The research covers public repositories, images and packages. Private repositories have the same exposure wherever files get shared or built into artefacts.
- Behaviour may change. Check the official Claude Code settings and permissions documentation again before you finalise internal policy.
- The commands are starting points. The search snippets above are suggestions to adapt and test in your own environment, not complete detection tools.
Key Takeaways
- 1'Yes, and don't ask again' saves the full command, credentials included, into .claude/settings.local.json.
- 2Work in order: Find every copy, Kill the credentials, Clean repos and artefacts, then Close the escape paths.
- 3Rotate or revoke first. Forks and already-pushed images keep copies that cleanup can't reach.
- 4Put the ignore rule in each project's .gitignore. The built-in global exclude only protects one machine.
- 5Standard scanners miss passwords embedded in commands, so add custom checks and have a person read the files.
- 6Audit beyond git: shared settings, CLAUDE.md, transcripts, Docker images and npm packages all belong in scope.
- 7Set credentials outside approved commands. An inline VAR=secret still lands in the allow rule.
Conclusion
The decision in front of a technology leader is whether to treat every credential that ever passed through a permanent approval as exposed. Given the research summarized above and the gaps it shows in both scanners and the built-in global exclude, we suggest the safer default is yes. That means setting aside engineering time for rotation and a full audit now, before any scanner raises an alert. Anthropic has said it's working on prevention, but until a fix ships and is documented, plan as though the behaviour won't change.
We also suggest having someone outside the original project review your teams' Claude Code allow rules, along with the pre-commit and CI checks meant to catch credentials hidden in command syntax.
Frequently Asked Questions
Does choosing 'Yes, and don't ask again' in Claude Code really store my password or API key in plain text?
Yes, if the password or key was part of the command. Claude Code saves the approved command string as an allow rule in .claude/settings.local.json, and Anthropic's settings documentation confirms that permanent Bash approvals are stored there. If the command carried a credential, that credential now sits in the JSON file exactly as you typed it. To keep the actual value out of the saved rule, set secrets as environment variables outside the approved command.
Is settings.local.json ignored by git automatically, and why isn't that enough?
Partly. The first time Claude Code writes the file in a repository that doesn't already ignore it, it adds **/.claude/settings.local.json to the developer's global git excludes file. That covers one developer on one workstation. The entry doesn't travel to a teammate's clone, a CI runner, a fresh laptop, a container image or an npm tarball, so add the rule to each project's own .gitignore as well.
Should I rotate the secret or rewrite git history first?
Rotate first. GitHub's guidance says that when the sensitive data is a secret, the first step is to revoke or rotate it. History rewriting comes later, and it can't reach everything: if the commit exists in any forks, it stays accessible there. We also suggest treating old package versions as still reachable. Confirm the old credential no longer works before you start cleanup.
Will gitleaks or TruffleHog find these leaks?
Some of them. In Apiiro's dataset gitleaks flagged 564 of 1,122 leaking files and TruffleHog flagged 643, but neither flagged 215 files. Most of those were passwords embedded in command syntax such as PGPASSWORD=, sshpass -p or mysql -p. GitGuardian found that generic detectors produced 44,755 of 71,525 occurrences. Add custom checks, and have someone read the files directly.
Which files and artefacts besides settings.local.json should I audit?
Include the shared .claude/settings.json file, CLAUDE.md, exported agent transcripts, container images and published packages. GitGuardian found 613 distinct secrets in Claude Code settings files on public Docker Hub images and 6,155 distinct secrets in CLAUDE.md files. According to research by Check Point, as reported by SecurityBrief and cited by the Cloud Security Alliance, 428 public npm packages shipped a .claude/settings.local.json file, and 33 of those files, across 30 packages, contained credentials.
Has Anthropic fixed the issue?
Not in the sources reviewed here. Claude Code lead Boris Cherny said the tool hasn't historically done secret scrubbing and that the team is looking into prevention. Ideas such as auto mode detection and basic built-in secret scanning are to be prototyped before anything ships.
Sources
- https://apiiro.com/blog/coding-agents-leaking-secrets-github-dont-ask-again
- https://blog.gitguardian.com/claude-code-settings-local-json-secrets-docker-hub/
- https://code.claude.com/docs/en/settings
- https://code.claude.com/docs/en/permissions
- https://thehackernews.com/2026/11/claude-codes-dont-ask-again-file.html
- https://www.csoonline.com/article/4247336/claude-codes-dont-ask-again-button-has-a-secrets-spilling-problem.html
- https://labs.cloudsecurityalliance.org/research/csa-research-note-claude-code-settings-secret-leak-20261108/
- https://github.com/anthropics/claude-code/issues/5544
- https://news.ycombinator.com/item?id=50408176
- https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository
- https://www.itnews.com.au/news/dont-ask-again-in-anthropics-claude-code-leaks-developer-secrets-630203
Written by
Hamza DiazHamza Diaz is the founder of Optijara, where he builds practical AI agents, automation systems, and Copilot workflows for service businesses. He writes about AI operations, agent strategy, and real-world implementation for teams that want usable systems instead of hype.
