How "Don't ask again" in Claude Code saves your database password where git can publish it

When Claude Code asks before running a shell command and you pick "Yes, and don't ask again", it saves a permission rule to .claude/settings.local.json in…

Loading
apiiro··PT2M3.2S

video loads only when you press play

When Claude Code asks before running a shell command and you pick "Yes, and don't ask again", it saves a permission rule to .claude/settings.local.json in…

When Claude Code asks before running a shell command and you pick "Yes, and don't ask again", it saves a permission rule to .claude/settings.local.json in the project folder. The rule is the exact command string, so a command like PGPASSWORD=... psql -h ... is saved with the password in it.

  • Picking "Yes, and don't ask again" in Claude Code saves the exact approved command as an allow rule in .claude/settings.local.json, so a command like PGPASSWORD=... psql is stored with the password in it.
  • Claude Code keeps that file out of git by adding it to the developer's global ignore list (core.excludesFile), which lives on one machine. A clone elsewhere, a folder that became a repo later, or a copy already committed is not covered, and new approvals keep being appended to a tracked file.
  • Apiiro found 104,185 public copies of the file on GitHub; 1,122 hold a real secret, 734 more sit only in git history, and 215 got past both gitleaks and TruffleHog. Check with git ls-files | grep settings.local.json, untrack it, add it to the repo's .gitignore and rotate what the rules held.

What happens when you pick "Yes, and don't ask again"

Claude Code asks before it runs a shell command, often several times a session. Ask it to check why a login fails on staging or to run a migration, and the command it proposes usually needs a credential, so it carries one: a connection string from a config file, a PGPASSWORD= in front of psql, an sshpass -p in front of ssh, a bearer header in a curl call.

The prompt offers three answers. "Yes, and don't ask again" runs the command and also adds a permission rule to .claude/settings.local.json in the project folder so the same command won't prompt again. Apiiro's finding is that the rule is the command string itself, as it ran, so the secret is written into the file in plain JSON. They reproduced it with current models (Opus 5.5 and Fable 5.1) and note that it is Claude Code's behaviour, not the model's.

Why the global git ignore doesn't protect the repo

settings.local.json is meant to stay on the developer's machine. When Claude Code creates it inside a git repository, it adds the file to the developer's global git ignore list: the per-user excludes file git reads on that machine (core.excludesFile, by default ~/.config/git/ignore). It does not add a line to the repository's own .gitignore.

That difference is the whole story. A global excludes file belongs to one user account on one computer. It covers the common case, the machine that created the file, and nothing else:

  • A folder that became a repo later. If the file existed before git init, it was never added to the global list.
  • A clone on another machine. That machine has its own excludes file, and nothing in the project tells it this path is private.
  • A file that was already committed. Ignore rules never apply to tracked files. Claude Code notices this (its debug log records that the file "is tracked in the index; gitignore rules do not apply to tracked files", and its permissions docs treat a tracked copy as repository-supplied), but the user is not told, and the next approval is appended to the tracked copy.

Apiiro traced 1,105 repos with a real secret in this file; in 539 of them, a commit co-authored by Claude changed it. Claude Code's commit instructions tell the model not to commit .env or credentials.json; its own settings file is not on that list. A commit that changed the file is not proof that the commit first added it, and Anthropic's view (below) is that exposed copies were created by hand.

How many copies are public, and what "real secret" means

Public GitHub held 104,185 copies of .claude/settings.local.json in 103,617 repositories when Apiiro searched. 1,122 files (1.1%) hold a real secret, with 1,379 distinct secrets between them: 636 provider keys and 743 passwords or tokens for public hosts. More than a third open a database directly: 379 database passwords and 122 Supabase service-role keys. Another 103 are Supabase account tokens. The rest include 43 SSH logins, 42 GitHub, GitLab and npm tokens, 23 AWS keys and 6 private keys.

"Real secret" has a specific meaning in this research. A value counts when it matches a known provider key format, or when it is a password or token with a public host on the same line (a connection string, or PGPASSWORD= before psql -h). Placeholders, default passwords, local and private hosts, keys that are public by design (Supabase anon keys, Firebase web config), expired tokens and checksum-failing GitHub and npm tokens were removed, and each remaining value was reviewed in masked context. No credential was used or tested, so "real" means credible, not confirmed live.

Bigger allowlists leak more: about one in ten files over 8 KB holds a real secret, against fewer than one in 500 under 500 bytes. The longer someone has been clicking "don't ask again", the more the file holds.

Why deleting the file isn't enough

Git keeps every committed version. Earlier versions of the file hold 734 real secrets, in 509 repos, that appear in no current copy, usually because the file was later deleted. Forks copy history too: 211 forks of 74 repos carry the same secrets. Counting history, the shared .claude/settings.json that teams commit on purpose, and agent chat transcripts saved in repos, the total is 2,539 distinct real secrets in 1,994 repos. A secret that was ever pushed has to be rotated; removing the file only stops new copies.

Why secret scanners miss a password inside a command

Apiiro ran gitleaks and TruffleHog (both without verification) over all 104,185 files. Of the 1,122 with a real secret, gitleaks flagged 564 and TruffleHog 643; 215 were flagged by neither.

Scanners are tuned to key formats: a provider prefix, a long random string. A password typed after PGPASSWORD=, sshpass -p or mysql -p is an ordinary word on an ordinary command line, so both tools mostly let it through. The same pattern shows up wherever an agent records what it ran: transcripts, memory files and handoff notes.

What Anthropic said

Apiiro shared the research with Anthropic before publishing. On Hacker News on 7 November, Boris Cherny, who leads Claude Code, wrote that Claude Code has not historically done secret scrubbing, "but we are looking into what we can do to prevent this", naming approaches the team plans to prototype: making sure auto mode catches it, and building in basic secret scanning. He also said Claude Code gitignores the file when it creates it, so in his view the exposed copies were created manually and checked in; he recommended auto mode over permission rules, and turning on the secret scanning GitHub and other hosts offer.

How to check and fix your own repos

1. In each repo, run git ls-files | grep settings.local.json. Anything returned is tracked. 2. Untrack it with git rm --cached .claude/settings.local.json and add .claude/settings.local.json to the repo's own .gitignore, so the rule travels with the project. 3. Rotate every credential the file has ever held. History, forks and clones keep it after you delete it. 4. Even if it was never committed, open the file and delete every allow rule that contains a secret. 5. Stop typing secrets into commands. Reference them as variables ($DB_URL, $DB_PASSWORD) from your environment or secrets manager, so the saved rule records the name, not the value. 6. Approve patterns such as Bash(npm run:*) instead of literal commands.

For teams, Apiiro also suggests treating agent folders (.claude/, .cursor/, .gemini/, .specstory/, memory files) as secret-bearing like .env, adding them to pre-commit and CI secret scanning including history, making sure the scanner catches passwords inside commands, and adding .claude/settings.local.json to your organisation's .gitignore templates.

Terms

Permission rule (allow rule)
An entry in Claude Code's settings that lets a tool call run without asking. A literal rule matches one exact command; a pattern rule like Bash(npm run:*) matches a family of them.
.claude/settings.local.json
Claude Code's per-project, per-user settings file, meant to stay out of version control. .claude/settings.json is the shared one teams commit.
Global git ignore
A per-user excludes file (core.excludesFile) that git applies to every repo on one machine. It is not part of any repository.
Tracked file
A file git already has in its index. Ignore rules, global or local, do not apply to it.
gitleaks / TruffleHog
Open-source secret scanners that search files and git history for credentials, mostly by known key formats and entropy.
Generic secret
A password or token with no recognisable format. Apiiro only counted one when a public host sat on the same line.
Rotation
Replacing a credential with a new one and revoking the old, the only fix once a secret has been pushed.

Questions

Does Claude Code commit my passwords to GitHub?

Not by itself in every case. "Yes, and don't ask again" saves the approved command, including any password in it, to .claude/settings.local.json. Claude Code adds that file to your global git ignore when it creates it in a repo, but the protection lives on one machine, so a later git init, a clone elsewhere or a manual commit can put it in the repo.

How do I check if my .claude/settings.local.json is in git?

Run git ls-files | grep settings.local.json in the repo. If it prints the path, the file is tracked; untrack it with git rm --cached, add it to the repo's .gitignore, and rotate anything its rules held.

Will GitHub secret scanning catch it?

It catches keys in known formats. Passwords typed inside commands look like ordinary words, which is why 215 of the 1,122 files Apiiro found were missed by both gitleaks and TruffleHog.

Is deleting the file enough?

No. Older commits, forks and clones keep it. Apiiro found 734 secrets that exist only in history. Rotate the credentials.

What has Anthropic said?

Boris Cherny said the team is looking into prevention, including basic secret scanning and making sure auto mode catches it, and that in his view the exposed copies were created manually and checked in.

Clicking "Don't ask again" in Claude Code can publish your database password. Ask it to check a failing login, and the command it proposes carries the password from your config file. Pick "Yes, and don't ask again", and it saves a permission rule so it won't ask twice. That rule isn't something tidy like allow psql, it's the whole command as it ran, password included, saved to .claude/settings.local.json in the project folder. Claude Code tries to keep that file private: when it creates it inside a git repo, it adds it to your global git ignore list. That list lives on your laptop, though, not in the repo, so it only guards the machine that wrote the file. But a folder that became a repo later, a clone on another machine, or a file someone already committed isn't covered, and the rule walks straight into the repo. And in almost half the repos where Apiiro, a security company, traced a leaked secret, a commit co-authored by Claude had changed that file. Apiiro found over a hundred thousand copies of it on public GitHub, and 1,122 of them hold a real secret, including 379 database passwords. Deleting the file doesn't help much, because git keeps every version, and older ones hold 734 more secrets that no current copy shows. Secret scanners miss plenty of them too, because they look for key formats, and a password typed after PGPASSWORD= is just one more word in a command line. Of the files with a real secret, 215 got past both gitleaks and TruffleHog. Anthropic's Boris Cherny, who leads Claude Code, says the team is looking at basic secret scanning, though in his view these copies were created by hand and checked in. Until then, check whether the file is tracked, untrack it, rotate anything a rule has held, and keep secrets in environment variables, so a rule saves only the variable's name. "Don't ask again" only ever meant it wouldn't ask you; it never meant it wouldn't tell anyone.

This explainer is based on Coding Agents Are Leaking Your Secrets to GitHub by Apiiro ↗. The original reporting and technical work belong to its publisher.