Sessionboxer

Claude Code's 'don't ask again' saved your password in .claude/settings.local.json. What to do now

Apiiro found 1,122 public copies of Claude Code's settings.local.json holding a secret. How to check yours, what to rotate, and how to stop it happening again.

An opened turn in the Sessionboxer chat with numbered model calls and completed Read and Execute tool rows, none of which waited for an approval

You asked Claude Code to check the staging database. It proposed a psql command with the password in it, you picked "Yes, and don't ask again", and the query worked. That password is now in a JSON file in your project folder, and it stays there until someone deletes the rule. If the file was ever committed to a public repository, anyone can read it.

On November 5, 2026, Apiiro published research on this file, and The Hacker News and iTWire covered it on November 7. This post explains how the secret gets there, how to check your machines and repositories, and what to change so it does not happen again. We build Sessionboxer, which runs coding agents in containers, and the last part says what it changes here and what it does not.

How a password ends up in the file

Claude Code's permissions page says it plainly. For a Bash command, "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. The same goes for a WebFetch domain.

The rule is whatever the prompt offered. Sometimes that is a pattern like npm test *. Sometimes it is the exact command you approved, and then everything you typed into it is saved too: PGPASSWORD=..., mysql -p..., a bearer header in a curl call. Apiiro reproduced this with current models and says it is Claude Code's behavior, not the model's.

Claude Code does try to keep the file out of git. Its settings page says that the first time it writes the file in a repository that does not already ignore it, it adds **/.claude/settings.local.json to your global git excludes. That protects one user on one computer. Apiiro lists what it does not cover: a folder that becomes a repository later, a clone on another machine, and a file someone already committed. The rule never goes into the project's own .gitignore.

People have reported this before. Issue #5544, filed in August 2025, describes approved commands stored "verbatim" with their keys. Issue #24994 from February 2026 describes a file that grew to 311 entries, Stripe live keys among them. Both are closed.

What Apiiro found

Apiiro counted 104,185 copies of .claude/settings.local.json on public GitHub. Of those, 1,122 hold at least one secret, 1,379 distinct values in total. More than a third open a database directly, including 379 database passwords. There are also 43 SSH logins, 42 GitHub, GitLab and npm tokens, and 23 AWS keys.

A few findings matter for what you do next:

  • Bigger files leak more. One in ten files over 8 KB held a secret. The longer someone has been clicking "don't ask again", the more the file holds.
  • Deleting the file did not help. Older versions held 734 secrets that are in no current copy, and 211 forks carry the same secrets as the originals.
  • In 539 of the 1,105 repositories they traced, a commit co-authored by Claude changed the file. Often the agent commits it.
  • Scanners miss many of them. Of the 1,122 files, gitleaks flagged 564 and TruffleHog 643, and 215 were flagged by neither. A password after sshpass -p looks like an ordinary word.

Apiiro did not test whether the secrets still work. One company it contacted confirmed its key had been live. The same pattern showed up in other files agents write: 298 shared .claude/settings.json files, 120 committed chat transcripts, and 33 handoff and deploy notes.

Check your machine first

Most copies of the file never reach GitHub, and a copy on your laptop is still a plain text list of passwords. Find every one:

find ~ -path '*/.claude/settings.local.json' 2>/dev/null

Open each file and read the allow list. Any entry with a password, token, key or connection string in it is a secret you have to treat as exposed if the file ever left the machine.

Then check whether git would pick it up in each project, and why:

git check-ignore -v .claude/settings.local.json

If the output points to your global excludes file, the project is protected on this machine only. If it prints nothing, the file is not ignored at all.

The file can also leave without git. Apiiro mentions npm packages, Docker images built with a copy step, zip archives and synced folders. Before you publish a package, npm pack --dry-run lists what goes into it. A .dockerignore line keeps the folder out of images.

Then check your repositories

On GitHub, search your organization's code with path:.claude/settings.local.json org:your-org, and do the same for .claude/settings.json, which teams commit on purpose. For each repository, look at the history too, since a file that is gone today can still be in an old commit:

git log --all --oneline -- .claude/settings.local.json .claude/settings.json

If you find a secret, the order is the one Apiiro gives: rotate first, clean up second. Rewriting history does not reach forks or clones people already have. Change the password or revoke the token, then stop tracking the file and ignore it in the project itself:

git rm --cached .claude/settings.local.json
echo '.claude/settings.local.json' >> .gitignore

The .gitignore line travels with the repository, so a teammate's clone is covered too.

Stop it happening again

Keep secrets out of the commands you approve. If the password lives in an environment variable or comes from a secrets manager, the command reads psql "$DATABASE_URL", and the saved rule holds the variable's name instead of its value.

Approve patterns, not one-off commands. In /permissions you can write a rule like Bash(npm run test *) yourself, and a short list of patterns is easier to read than hundreds of exact command lines. Go through the file every so often and delete what you no longer need.

If you run a security team, Apiiro's advice is to treat agent folders like .claude/, .cursor/ and .gemini/ the way you treat .env files, and to add .claude/settings.local.json to your organization's .gitignore templates. Make sure your scanner catches passwords inside command lines, not only known key formats. Our earlier post on GitHub push protection for Anthropic keys covers what GitHub blocks on its own and where it does not look.

Where Sessionboxer fits

In Sessionboxer, each session runs in its own Docker container, and Claude Code runs there through claude-agent-acp in bypassPermissions mode, so it never shows a permission prompt. With no prompt, there is no "don't ask again" to answer, and that path does not add rules to the file. Claude Code's docs say to use that mode only in isolated environments like containers or VMs, which is what the box is.

For the systems an agent has to log in to, Utilities keep the credentials on tmpfs inside the box, never in the workspace or a snapshot. The agent runs sb-util env <name> -- <command>, which sets UTIL_USER and UTIL_PASSWORD for that command, so what it types and what the chat records is the variable name. The GitHub login the box pushes with lives on tmpfs too. When you fork a session with a handoff, the agent is told to write the note with no secret values. The chat history lives in ~/.sessionboxer/db.sqlite on the host, outside your repositories.

What it does not do:

  • It has no secret scanner and does not inspect commits before git push.
  • A repository that already has a settings.local.json committed brings it into the box when you start a session, and the agent can read it.
  • If you paste a password into a prompt, it is in the chat history. If the agent copies a secret it can read into a file it commits, nothing in Sessionboxer stops the push.

For the rest of that setup, see how to keep your secrets away from a coding agent.

A short checklist

  1. Find every .claude/settings.local.json on your machines and read its allow list.
  2. Search your GitHub organization and each repository's history for the file and for .claude/settings.json.
  3. Rotate every secret you find before you clean anything up.
  4. Run git rm --cached on the file and add it to the project's .gitignore.
  5. Put secrets in variables, approve patterns, and prune the file now and then.

Features mentioned

Keep reading