Adaptive Defenses for Large Language Models | Lakera – Protecting AI teams that disrupt the world.
Adaptive Defenses for Large Language Models
Research
July 23, 2026
Elliot W.
Claude Code stores approved shell commands in a local settings file inside your project directory. If that project is an npm package, there is a good chance that file is shipping to the public registry with your credentials inside it.
We built a scanner to measure how common this is and to examine what ends up in those files. This post covers how the exposure occurs, what we found, and how to prevent it.
This research was inspired by a LinkedIn post from Kirill Efimov , who first spotted .claude/settings.local.json files appearing inside published npm packages. We wanted to expand on this to understand the true scale of the issue and raise awareness of it.
How Claude Code's Permission Model Works
Claude Code operates with a permission system for shell commands. When Claude wants to run a command it has not been permitted to run before, it presents you with options, one of which is "allow always". Choosing this writes the exact command string to .claude/settings.local.json as a permanent allowlist entry. Claude will not ask about that command again.
The file lives in .claude/ at the root of your project directory. It looks something like this:
{
"permissions": {
"allow": [\
"Bash(npm run build)",\
"Bash(curl -s -H \"Authorization: Bearer npm_abc123...\" https://registry.npmjs.org/-/whoami)"\
]
}
}
Every command you permanently approve gets recorded there, including any credentials that were inline at the time. A curl call with an Authorization header. An environment variable like API_KEY=abc123 prepended to a command. All of it ends up in the file, and the file ends up in your project directory.
The npm Publishing Gap
npm packages are built from the contents of your project directory. Files are excluded via .npmignore or the files field in package.json, but neither has a default entry for .claude/. There is no warning during npm publish if the directory is present. The settings file is a hidden dotfile that does not stand out in any part of the normal publish workflow.
.claude/settings.local.json follows the same convention as .env. The .local suffix signals that the file is personal and environment-specific. Unlike .env, it does not benefit from widespread awareness or tooling that flags it before it ships.
What We Built
We wrote a TypeScript service that monitors the npm registry's CouchDB changes feed. For every new or updated package, it fetches the tarball and inspects its contents. When a .claude/settings.local.json is present, the file is extracted and saved for analysis.
The Findings
Across approximately 46,500 packages monitored over our scan window, 428 contained a .claude/settings.local.json. Of those, 33 files across 30 packages contained credentials. Roughly one in thirteen settings files that shipped contained something sensitive.
What Was Inside
- npm authentication tokens used to publish packages to the npm registry
- npm login credentials in plaintext: username, password, and email address concatenated into a single allowlist entry
- GitHub personal access tokens, including both fine-grained PATs and classic PATs
- Telegram Bot API tokens, which grant full control of the associated bot: reading messages, sending messages, and all other bot API operations
- Production bearer tokens for third-party services
- Hugging Face API tokens
- Plaintext test credentials: email and password pairs embedded in curl commands used to provision local development accounts
Why This Is Easy to Miss
The file is not obviously sensitive: it reads as a list of shell commands, and nothing in the publish workflow surfaces it. Most developers never open it.
Credentials end up in allowlist entries naturally. During active development with Claude Code, you approve a lot of commands. Most are harmless. But some involve authenticated API calls, deployment scripts, or service logins. Hit "allow always" instead of "allow once" on any of those, and they go into the file permanently.
Prevention
Add .claude/ to your .npmignore:
.claude/
If you use the files field in package.json to control what gets published, .claude/ will not be included by default, but double-check before you publish.
Add it to .gitignore as well. The file has no place in version control:
.claude/settings.local.json
Check what your next publish will include:
npm pack --dry-run
Check versions you have already published:
npm pack <your-package>@<version>
tar -tzf <your-package>-<version>.tgz | grep claude
If The File Has Already Shipped
npm tarballs are permanent. Deprecating a version removes it from default install resolution but does not delete it and does not invalidate cached copies. Any credential that appeared in a published tarball should be considered compromised from the moment of publication, regardless of when it is discovered.
Rotate npm tokens at npmjs.com/settings/~/tokens and GitHub tokens at github.com/settings/tokens. For any other service whose credentials appeared in the file, rotate those too.
Conclusion
.claude/settings.local.json is not inherently dangerous, but it accumulates credentials as a side effect of normal use, and the current defaults do nothing to keep those credentials out of published packages. The fix is a single line in .npmignore. Add it before your next release.