All articles
Claude CodeClaudeFixes

Claude Code settings.local.json Leaked Secrets: Rotate Keys First, Then Untrack and Add .gitignore (2026)

Choosing "Yes, and don't ask again" can save a command with its password into .claude/settings.local.json. Apiiro found 1,122 public copies holding secrets. Fix: rotate keys first, then untrack and add .gitignore; rewriting history is a separate high-risk step.

Claude Code settings.local.json secrets: clay document with a gear next to a clay key and padlock, soft light
Also available in中文Español

Short answer: if you ever committed .claude/settings.local.json, do not start by deleting the file. Step one is to rotate every secret that file has held. Step two is to stop tracking it and add it to the repository’s own .gitignore. Why: when you pick “Yes, and don’t ask again”, Claude Code saves the approved command as an allow rule word for word, and any password or token typed into that command goes into the file with it. Security firm Apiiro reported in early November 2026 that it found 104,185 copies of this file on public GitHub, and 1,122 of them held at least one secret. This article relies only on Apiiro’s research and the official Anthropic and GitHub docs. Every key in the examples is made up, and scan output shows file paths and hit types only.

What the file is and how secrets get in

.claude/settings.local.json is Claude Code’s “project local” settings file: yours alone, for this one project. The official settings docs say that when you approve a Bash command with “Yes, and don’t ask again”, Claude Code saves the approval as an allow rule in this file at the root of the git repository.

A rule can be a pattern (such as npm test *) or one full command. The official permissions docs say a rule with no * matches exactly one command. If the command you approved had a password in it, the password now sits in the file in plain text. The shape looks like this (placeholders are made up):

{"permissions":{"allow":["Bash(PGPASSWORD=<plain-text password> psql -h <host> -U <user> -c '...')"]}}

Why it ends up on GitHub

The docs say that the first time Claude Code writes this file inside a git repository, it adds **/.claude/settings.local.json to your global git excludes file, so no repository on that computer will commit it. The docs also say that if you created the file by hand, you should add it to .gitignore yourself.

The catch is that this protection lives in one git config on one computer. According to Apiiro, Claude Code never adds the file to the project’s own .gitignore, so the protection does not travel with the project: a clone on another machine, a container, a folder that becomes a repository later, or a file that was already tracked all lose it. Apiiro traced 1,105 repositories and found that in 539 of them a commit listing Claude as co-author had changed the file; they take that to mean Claude often commits it. That is their inference, not Anthropic’s statement.

What the research found

The numbers come from Apiiro’s research, reported by The Hacker News. When we checked, Apiiro’s own write-up sat behind the site’s verification wall and would not open. Every figure in the list below, including 63 organizations and 298 copies of the shared settings.json, is quoted in that Hacker News article. Ars Technica also covered the research (it rounds some totals).

  • 104,185 copies of .claude/settings.local.json on public GitHub, across 103,617 repositories.
  • 1,122 of them (about 1.1%) hold at least one secret, 1,379 distinct secrets in total, across 63 organizations.
  • Another 734 secrets exist only in older versions of the file, usually after it was deleted, across 509 repositories. 211 forks carry the same secrets as the originals.
  • Apiiro did not test whether the secrets still work, so this is exposure, not a confirmed breach. One company the researchers notified confirmed its key had been live.
  • Two common scanners, gitleaks and TruffleHog, both missed 215 of the files, mostly passwords typed straight into commands.
  • You may also see “2,539 secrets” in coverage. That figure adds git history, the shared settings.json and chat logs, so it counts something different from the 1,379 above.

The fix, in this order

1. Rotate first, then do anything else

GitHub’s own documentation says that when the sensitive data is a secret, the first step is to revoke or rotate it; once rotated it can no longer be used for access, and sometimes that is enough. Replace every password, token and key that file has held, including ones that live only in old commits. Priorities: passwords that open a database directly, cloud keys, SSH logins, GitHub / GitLab / npm tokens. For deleting and replacing Anthropic, OpenAI and DeepSeek keys, see Leaked API key: what to do.

2. Find where it is tracked and where it ever was

In each repository, list tracked files and every path the history ever touched (these print paths and commit summaries, not file contents):

git ls-files '*settings.local.json'
git log --all --name-only --pretty=format: -- '*settings.local.json' | sort -u

Then use this small loop, which prints only “path + hit type”, for a rough scan of tracked copies. It is a demo with simple rules and does not replace a proper secret scanner:

for f in $(git ls-files '*settings.local.json'); do
  grep -qE 'PGPASSWORD=|sshpass -p|mysql .* -p' "$f" && echo "$f  hit: password-in-command"
  grep -qiE '(token|secret|api[_-]?key|password)=' "$f" && echo "$f  hit: key-or-token-assignment"
done

Output looks like this (paths are made up):

.claude/settings.local.json             hit: password-in-command
apps/api/.claude/settings.local.json    hit: key-or-token-assignment

We ran these commands in a throwaway repository with fake passwords (git 2.47.3); the output was only paths, hit types and commit summaries. On a team, also search your GitHub organization for .claude/settings.local.json and .claude/settings.json; the researchers suggest checking both because the shared settings.json held secrets too (298 copies, Apiiro research, reported by The Hacker News).

3. Stop tracking it and add it to the repository’s own .gitignore

Adding it to .gitignore is not enough: a file that is already tracked is not ignored. Make git stop tracking it first (--cached removes it from git only and keeps your local file):

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 checked the first two steps in the throwaway repository: afterwards git ls-files no longer listed the file and git check-ignore -v showed the .gitignore rule applying. Note that this does not remove old versions from history.

4. Clean the rules that contain secrets on your own machine

Open the file on your own computer, even if it was never committed, and delete every rule that contains a password or token. From now on approve patterns rather than full commands with passwords, and keep secrets in environment variables or a secrets manager so the saved rule holds only a variable name. In the official permissions docs, Bash(npm run build) matches that one command, while a rule with * matches a family of commands; the second is the safer choice at the prompt.

A separate, high-risk step: rewriting Git history

This step is optional and risky, so treat it apart from the rest. GitHub’s documentation is blunt: once a secret is rotated it can no longer be used for access, and going through the extra steps of rewriting history “may not be warranted”. The side effects include:

  • Easy recontamination: a teammate with a clone from before the rewrite who simply pulls and pushes can bring the old data back; they must re-clone.
  • Possible loss of other people’s work: commits on old branches conflict with the rewritten history.
  • Forks, local clones and cached views on GitHub are not cleaned, and copies others already took cannot be recalled.

So the order is: rotate, confirm the old secrets are dead in the provider’s console, and only consider a rewrite if you have a clear reason (for example, the file also holds data you cannot change). Tell every collaborator first and rehearse on a backup mirror. This article gives no copyable rewrite or force-push commands. Follow only GitHub’s official Removing sensitive data from a repository (it recommends git-filter-repo).

How to prevent it next time

  • Put .claude/settings.local.json in your team’s .gitignore template. The researchers also suggest treating agent folders such as .claude/, .cursor/ and .gemini/ as secret-bearing, like .env files.
  • Add secret scanning to pre-commit and CI, and make sure it catches plain-text passwords inside commands, not only known key formats; gitleaks and TruffleHog missed those 215 files.
  • Keep passwords out of commands, use environment variables, and approve patterns.
  • On Anthropic’s side, Claude Code’s Boris Cherny said on Hacker News that the team is looking into preventing this and suggested auto mode and secret scanners; a user pointed out sensitive commands can still be saved to settings files in auto mode, and he called it a good catch that the team is still thinking about. We have not seen an official announcement of a change, so rely on the official docs from here on.

FAQ

Is deleting the file enough?

No. Older versions stay in git history, and forks may hold them too. Rotate the secrets first.

My repository is private. Does it matter?

The research only counted public repositories, so there is no data on private ones. But a secret written in a file is readable by anyone who can read the repository, so if the file ever held one, follow the same order.

Is it the same as settings.json?

No. settings.json holds shared or personal settings; settings.local.json is the part of a project that belongs only to you. For paths and precedence see Claude Code settings.json location; for the permission prompt, see Claude Code /permissions.

Other Claude Code errors to look up?

See Claude Code errors: the fix list.

Leave a thought

Your email address will not be published.