Apiiro found 1,122 public copies of Claude Code's local settings file holding a real credential. We trace how an approved command becomes a saved rule, why the global gitignore does not travel with your repo, and give three tested checks for your org, your repos and your laptop.
This explains reporting by
Apiiro: Coding Agents Copy Your Secrets to a Hidden File. Thousands Are on GitHub (November 5, 2026, updated November 8). Read the original first:
https://apiiro.com/blog/coding-agents-leaking-secrets-github-dont-ask-again
In one minute #
- When you pick 'Yes, and don't ask again' on a Bash command, Claude Code saves that exact command as an allow rule in .claude/settings.local.json at the repo root. If the command held a password or token, the file now holds it too.
- Claude Code protects the file by adding it to your global git ignore list. That does not travel with the repo, so a clone on another machine, a folder that becomes a repo later, or a file someone force-added gets committed.
- Apiiro counted 104,185 public copies on GitHub, 1,122 with a real credential, and 2,539 distinct credentials in 1,994 repos once history and other agent files are counted. 215 of the 1,122 files were flagged by neither gitleaks nor TruffleHog.
- Anthropic's head of Claude Code said on Hacker News the team is looking at secret scanning and auto mode checks. Nothing has shipped yet.
- Rotate first, clean up second. Then approve patterns like Bash(npm run *) instead of literal commands, and pass credentials as environment variables.
What Apiiro found #
Apiiro searched public GitHub from November 1 to 3, 2026 and downloaded every .claude/settings.local.json it could list: 104,185 files in 103,617 repos. 1,122 of them (1.1%) hold a real credential, meaning a key in a known provider format or a password or token next to a public host. Together they hold 1,379 distinct credentials. More than a third open a database directly: 379 database passwords and 122 Supabase service-role keys. There are also 43 SSH logins, some of them root passwords in an sshpass command.
Adding git history, the shared settings.json teams commit on purpose, agent transcripts, memory files and handoff notes, the total is 2,539 distinct credentials in 1,994 repos. Apiiro says no credential was used or tested, so 'real' means it matches a provider format or points at a public host, not that it is known to be live.
How a command becomes a saved rule #
Claude Code asks before it runs most shell commands. Its permissions docs list what each answer does: for Bash commands, 'Yes, and don't ask again' lasts 'Permanently per repository and command', and the rule is saved to .claude/settings.local.json at the root of the git repository. File edits, by contrast, are approved only until the session ends.
The saved rule is the command string itself. So if Claude proposes PGPASSWORD=... psql -h db.example.com and you approve it for good, that line, password included, is now a rule in a JSON file in your project folder. Apiiro found that large files leak most: one in ten files over 8 KB held a real credential, against fewer than one in 500 under 500 bytes. The longer someone clicks 'don't ask again', the more the file holds.
Apiiro reproduced this with Opus 5.5 and Fable 5.1 and stresses it is Claude Code's behavior, not the model's. It compared 16 coding agents and says Claude Code is the only one confirmed to write the full approved command to a project file by default.
Why the gitignore does not save you #
Claude Code does try to keep the file local. Apiiro says that when it creates the file inside a git repo, it adds the file to your global git ignore list. On a Mac where Claude Code has run, we found that line on November 9: the global ignore file held **/.claude/settings.local.json, git check-ignore named it as the reason the file was ignored, and the test repo's own .gitignore had nothing.
That protects one machine. It does not cover a teammate's machine, a CI runner, a folder that became a repo after the file was made, or a git add -f. Once the file is tracked, the docs say Claude Code treats it as repository-supplied and holds its rules until you trust the folder. Apiiro says the user is not told, and the next approval is appended to the tracked file.
Apiiro also found that in 539 of 1,105 traced repos with a real credential, a commit co-authored by Claude changed the file. Apiiro says Claude Code's commit instructions warn the model about .env and credentials.json, but not about its own settings file.
Why secret scanners miss a third of it #
Scanners look for key formats: a provider prefix and a long random string. A password typed after PGPASSWORD=, sshpass -p or mysql -p, or a bearer token in a curl header, is an ordinary word in an ordinary command line. Apiiro ran both popular scanners over all 104,185 files without verification: gitleaks flagged 564 of the 1,122 files with a real credential, TruffleHog 643, and 215 were flagged by neither.
The same pattern shows up wherever an agent records what it ran: transcripts, memory files, handoff notes. Apiiro found real credentials in 298 shared settings.json files, 120 agent transcripts and 9 repos' agent memory files.
Check your org, your repos and your laptop #
- Public files in your GitHub org. The GitHub CLI's code search takes a filename and an owner. We ran this on November 9 and it listed matching files:
gh search code permissions --filename settings.local.json --owner YOUR-ORG --limit 100 --json repository,path
- History in each repo, even if the file is gone today. The first line shows whether it is tracked now; the second counts commits that ever touched it:
git ls-files | grep -E '(^|/)\.claude/settings(\.local)?\.json$'
git log --all --oneline -- .claude/settings.local.json | wc -l
- Saved rules on your own machine. This script walks a folder, reads every .claude/settings*.json, and prints the file, the rule number and the kind of credential it looks like. It never prints the rule. We tested it on November 9 against a test repo with four planted fake credentials and two clean rules: it flagged all four and neither clean rule.
#!/usr/bin/env python3
import json, re, sys, pathlib
KINDS = [
("password env var", r"PGPASSWORD=|MYSQL_PWD="),
("sshpass password", r"sshpass\s+-p"),
("mysql -p password", r"mysql\s[^)]*-p\S"),
("bearer token", r"Bearer\s+\S"),
("password in URL", r"://[^/\s:@]+:[^@\s]+@"),
("provider key", r"\b(sk|pk|rk)-[A-Za-z0-9_-]{8,}|ghp_[A-Za-z0-9]{20,}|npm_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}"),
("key=value", r"(api[_-]?key|token|password)\s*[=:]\s*\S"),
]
roots = [pathlib.Path(p).expanduser() for p in (sys.argv[1:] or ["~"])]
for root in roots:
for f in root.rglob("settings*.json"):
if f.parent.name != ".claude" or "node_modules" in f.parts:
continue
try:
allow = json.loads(f.read_text()).get("permissions", {}).get("allow", [])
except Exception:
continue
for i, rule in enumerate(allow):
for kind, rx in KINDS:
if re.search(rx, rule, re.I):
print(f"{f} allow[{i}] {kind}")
break
python3 check-allow.py ~/code
It is a pattern match, so it will miss a bare password with no telling prefix. Read any large allow list by eye too.
What to change #
- Rotate any credential a saved rule ever held. Deleting the file does not clear git history, forks or clones; Apiiro found 211 forks carrying the same credentials.
- Untrack the file with git rm --cached .claude/settings.local.json and add it to the repo's own .gitignore, so the protection travels with the project.
- Approve patterns, not literal commands. The docs show Bash(npm run *) matches npm run build and npm run test, so one rule covers a family of commands with nothing secret in it.
- Keep credentials out of command lines. Reference $DATABASE_URL from your environment or secrets manager so the saved rule records the variable name, not the value.
- Add .claude/, .cursor/ and other agent folders to pre-commit and CI secret scanning, with rules for passwords inside commands, not only key formats.
Who is affected #
| Case | Status |
|---|---|
| Claude Code users who approve Bash commands with 'Yes, and don't ask again' | Each approval is saved word for word to .claude/settings.local.json at the repo root. |
| Repos where that file is tracked by git | New approvals are appended to the tracked file. Public repos expose them to anyone. |
| Teams relying on gitleaks or TruffleHog alone | In Apiiro's test, 215 of 1,122 leaking files were flagged by neither. |
| Claude Code 2.1.319 | Ships Bash permission fixes, but no change to how approvals are saved. |
What to do #
- Run the org search and the history check on every repo you own or manage.
- Rotate every credential you find, then untrack the file and add it to the repo's .gitignore.
- Run check-allow.py on developer machines and delete rules that hold credentials.
- Replace literal approvals with pattern rules and pass credentials as environment variables.
What is still unknown #
- How many of the 2,539 credentials were live when found. Apiiro did not test them, and one organization confirmed its key had been live.
- What Anthropic will ship. Boris Cherny named secret scanning and auto mode checks as ideas to prototype, with no date.
- How many private repos carry the same file. Apiiro searched public GitHub only.
- Whether 'real' credentials include some false positives. Apiiro used rules plus an LLM reviewer on masked values, not a human check of every one.
Sources #
- Apiiro: Coding Agents Copy Your Secrets to a Hidden File. Thousands Are on GitHub (November 5, 2026, updated November 8) — the original report
- Claude Code docs: configure permissions
- Claude Code v2.1.319 release notes
AI News Report· every headline, every morning.