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 -bc-.claude/-bc- 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 -bc-curl-bc- call with an -bc-Authorization-bc-header. An environment variable like -bc-API_KEY=abc123-bc- 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 -bc-.npmignore-bc- or the -bc-files-bc- field in package.json, but neither has a default entry for -bc-.claude/-bc-. There is no warning during -bc-npm publish-bc- 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.

-bc-.claude/settings.local.json-bc- follows the same convention as -bc-.env-bc-. The -bc-.local-bc- suffix signals that the file is personal and environment-specific. Unlike -bc-.env-bc-, 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 -bc-.claude/settings.local.json-bc- 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 -bc-.claude/settings.local.json-bc-. Of those, 33 files across 30 packages contained credentials. Roughly one in thirteen settings files that shipped contained something sensitive.

What Was Inside

-db1-

  • 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-db1-

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 -bc-.claude/-bc- to your -bc-.npmignore-bc-:

.claude/

If you use the -bc-files-bc- field in -bc-package.json-bc- to control what gets published, -bc-.claude/-bc- will not be included by default, but double-check before you publish.

Add it to -bc-.gitignore-bc- 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

The Same Problem Exists Beyond npm 

npm is the registry we scanned, but the underlying issue applies to any packaging workflow that assembles files from your repository into a publishable archive. 

PyPI 

Python packages are published as source distributions (sdists) and/or wheels. What gets included depends on the build backend and its file-selection rules. With setuptools, -bc-MANIFEST.in-bc- is a standard way to exclude files from sdists; with backends like Hatch and Flit, VCS-tracked files and explicit include/exclude settings also affect what gets packaged. 

If -bc-.claude/-bc- is visible to the backend’s file-selection logic, it can end up in an sdist, and in some configurations in other artifacts as well. 

With setuptools, add: 

prune .claude 

If you use a -bc-pyproject.toml-bc- backend such as Hatch or Flit, prefer explicit include/exclude rules so -bc-.claude/-bc- is never selected in the first place. 

Verify before publishing:

python -m build --sdist 
tar -tzf dist/*.tar.gz | grep -i claude  

If you publish with Twine, remember Twine uploads artifacts; it does not audit them for sensitive files. The check has to happen before upload. 

RubyGems 

A gem’s contents come from -bc-spec.files-bc- in the -bc-.gemspec-bc-. Many gemspecs populate -bc-spec.files-bc- from git-tracked files, so anything tracked in git may be published unless you filter it out. 

Add an explicit exclusion, for example: 

spec.files = Dir.glob("**/*", File::FNM_DOTMATCH)
.reject { |f| f.start_with?(".claude/") }

Then you can build the gem and inspect the result by unpacking it:

gem build your-gem.gemspec 
gem unpack your-gem-*.gem 
find your-gem-* | grep -i claude 

Maven / Gradle (JVM) 

The main compiled JAR usually does not include project-root dotfiles like -bc-.claude/-bc-. The bigger risk is custom source/resource configuration or published source JARs that sweep in more than intended. Maven Source Plugin builds source JARs from project sources, and Gradle’s -bc-withSourcesJar()-bc- publishes a JAR from the -bc-main-bc- source set. 

Verify source artifacts explicitly:

jar tf your-artifact-sources.jar | grep -i claude 

General advice for any package manager 

The pattern is the same everywhere: a build tool selects files and publishes an archive. The -bc-.claude/-bc- directory sits at your project root, just like -bc-.env-bc-. Treat it the same way. 

Before publishing, inspect the exact archive contents and search for sensitive paths. 

If your package manager supports a dry-run or preview mode, make it a standard step before every publish. The npm equivalent is -bc-npm pack --dry-run-bc-; other ecosystems often have an analogous way to inspect the artifact before release.

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 -bc-npmjs.com/settings/~/tokens-bc- and GitHub tokens at -bc-github.com/settings/tokens-bc-. For any other service whose credentials appeared in the file, rotate those too.

Conclusion

-bc-.claude/settings.local.json-bc- 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.