I Went Hunting for .claude Files on GitHub. The Passwords Were Sitting in Plaintext.

Claude Code's 'don't ask again' feature saves complete commands, including passwords typed inline, to a local config file. 1,122 public GitHub repos were found leaking those files with credentials in plaintext.

The command looked harmless: mysql -h prod-db.internal -u admin -pS3cr3t.... A developer approved it once, clicked the convenient little option that means “stop bothering me about this,” and moved on. Claude Code did exactly what it was told. It wrote the full command, host, user, and the password typed inline, into a configuration file under .claude/. Then git add . swept that file up with everything else, and a public push did the rest.

Multiply that by 1,122 public repositories. That’s the count The Hacker News reported for GitHub repos carrying .claude configuration files with plaintext passwords, API keys, database connection strings, and auth tokens baked in. None of this is a vulnerability in the exploit sense. There’s no CVE here, and I’m not going to manufacture one. It’s a design behaviour meeting human habit at the worst possible intersection.

Here’s the mechanism, because it matters for how you hunt. Claude Code’s “Yes, and don’t ask again” doesn’t remember a category of command, it remembers the string. The permission entry it persists is the command as you ran it. If you ran it with a secret in the arguments, the secret is now in the allow-list on disk. Many developers may not realize the file exists or that it quotes their commands verbatim. That’s the part that bites in six months: unlike a .env file you deliberately created and know to guard, .claude/ shows up on its own.

If you run Claude Code anywhere near credentials, work this list in order. The order is the point.

1. Find out whether you’re already leaking, before you touch anything

Don’t delete first. If a file is public, assume it’s already indexed, cloned, and scraped. Your first job is to know the blast radius, and that means searching both your own repos and the wider platform. Start locally and with your org’s own GitHub.

run.shbash, zsh
# Local sweep: find any committed .claude files across repos on this machine
# Report only, nothing is modified.
find ~/code -type d -name '.git' -prune -o -type f -path '*.claude*' -print
​
# Within a single repo: is .claude tracked by git right now?
git ls-files | grep -E '(^|/)\.claude(/|$)'
​
# Grep tracked content for the usual suspects inside approved commands
git grep -nE '(-p[^ ]|password=|PGPASSWORD|api[_-]?key|token=|Authorization: Bearer)' \
  -- '*.claude*' 2>/dev/null || echo "No matches in tracked .claude files"

Then query GitHub itself across your organisation. Use the gh CLI so you’re authenticated and can see private repos you own:

run.shbash, zsh
# Requires: gh auth login  (code search scope)
# Find .claude files across your org (adjust org name)
gh api -X GET search/code \
  -f q='org:YOUR_ORG path:.claude' \
  --jq '.items[] | "\(.repository.full_name): \(.path)"'
​
# Broader: any path containing .claude that mentions a password-ish token
gh search code 'org:YOUR_ORG password path:.claude' --limit 100

GitHub’s code search is picky and rate-limited, and it won’t surface everything, it indexes the default branch and has size limits. Treat a clean result as “nothing obvious,” not “nothing there.” The local find and git grep are your ground truth.

2. Rotate every secret you find, all of it, now, no triage

This is the step people want to negotiate with. Don’t. If a credential appeared in a .claude file that reached a public repo, it is compromised. Not “possibly.” Compromised. Automated scrapers hoover public GitHub for exactly this, and the window between push and harvest can be very short.

Rotate database passwords, API keys, cloud tokens, and bearer tokens, every distinct secret that appears in any exposed file. Changing the repo’s visibility back to private afterwards does nothing; the secret already left the building. Rotation is the only control that actually invalidates what an attacker grabbed. While you’re at it, pull access logs for those credentials and look for use from unfamiliar IPs or geographies around and after the push date. A rotated key with suspicious prior usage is an incident, not a chore.

3. Purge the history, not just the current file

Deleting the .claude configuration file and committing fixes exactly nothing. The secret lives in every historical commit that ever contained it, and git log -p will hand it to anyone who clones. You need to rewrite history and force-push, then get the remote to garbage-collect.

run.shbash, zsh
# Preferred: git-filter-repo (install separately)
# Dry run first, see what would be removed
git filter-repo --path .claude --invert-paths --dry-run
​
# Then for real (this rewrites history, coordinate with your team)
git filter-repo --path .claude --invert-paths
git push origin --force --all
git push origin --force --tags

Two caveats that trip people up. First, forks and existing clones keep the old history regardless of what you do, which is another reason step 2 (rotation) is the real fix and this step is cleanup. Second, on GitHub the old commits can linger in cached views until you open a support request to purge them. Do that if the exposure was serious.

4. Block the file class everywhere, globally

You do not want to rely on remembering this per-repo. Add it to a global gitignore so every repository on the machine inherits the rule, then add it to each project’s .gitignore as belt-and-braces (CI and teammates don’t share your global config).

run.shbash, zsh
# Global ignore, applies to every repo for this user
git config --global core.excludesFile ~/.gitignore_global
printf '%s\n' '.claude/' '.claude' >> ~/.gitignore_global
​
# Per-repo, committed so the whole team benefits
printf '%s\n' '' '# Claude Code local config / approved commands' '.claude/' >> .gitignore
git add .gitignore && git commit -m "Ignore Claude Code local config"

A gotcha worth stating out loud: .gitignore only ignores files that aren’t already tracked. If .claude was committed before you added the rule, Git keeps tracking it. Run git rm -r --cached .claude to untrack it, then commit. Otherwise you’ll add the ignore rule, feel safe, and keep leaking.

5. Put a tripwire in front of the commit

Human discipline fails. The developer who leaked the first one wasn’t careless, they were busy. Add a pre-commit hook or a secret scanner so the machine catches what the human misses. Enable GitHub’s push protection at the org level if you have it; it rejects pushes containing recognised secret patterns before they land.

run.shbash, zsh
# Minimal pre-commit guard: refuse commits that stage a .claude path
cat > .git/hooks/pre-commit <<'EOF'
#!/usr/bin/env bash
if git diff --cached --name-only | grep -qE '(^|/)\.claude(/|$)'; then
  echo "BLOCKED: a .claude file is staged. Unstage it or check it for secrets." >&2
  exit 1
fi
EOF
chmod +x .git/hooks/pre-commit

Hooks living in .git/hooks don’t travel with the repo, so for a team, wire this into a managed framework (pre-commit, Husky) that’s committed and enforced in CI. A scanner that runs server-side is the one that actually saves you, because it’s the one nobody can skip with --no-verify.

6. Stop feeding secrets to commands in the first place

Everything above is damage control. This is the fix. The root cause isn’t the file, it’s secrets typed inline into commands. Pull them out and the persisted approval becomes harmless.

Use environment variables and reference them (PGPASSWORD read from the environment, not pasted into the command). Use credential helpers and secret managers so the plaintext never appears in an argument. And be deliberate with “Yes, and don’t ask again”, it’s fine for npm test, it’s a landmine for anything carrying a password. If a command contains a secret, approve it once and let it ask again next time. The friction is the feature.

7. Make it an org-wide sweep, not a one-machine fix

You are not the only person running this tool. I turned a scan loose across a handful of orgs for an afternoon, and the pattern was depressingly consistent: the leaks clustered in side projects and proof-of-concept repos that someone flipped public and forgot. Run the GitHub API search across every org you own, check historical commits (files that were added then “removed” without a history rewrite), and send a short, non-blamey note to engineering: here’s the file, here’s why it matters, here’s the gitignore rule. Enable repository secret scanning and push protection org-wide so this is caught by policy, not by one person’s vigilance.

If you do only one thing today

Rotate. If you’re confident a .claude file with a real credential ever touched a public repo, rotating that credential is the single action that invalidates what’s already been scraped. Everything else, history rewrites, gitignore, hooks, reduces the next leak. Rotation closes the one you already have. The scrapers don’t wait for you to finish reading your own git log.