1. Paste your config
2. What was found
| Severity | Check | Detail |
|---|
Prefer a CLI, or want this as a CI gate?
Same logic, zero dependencies, one file — and it also flags whether a config carrying a hardcoded-looking secret is tracked by git:
node cli.mjs check # auto-discovers config files under . node cli.mjs check --root# auto-discovers under node cli.mjs check --file [--file ...] # check specific file(s) node cli.mjs check --json
Get cli.mjs — Node 18+, nothing to install. It reads only the config file(s) it finds or is told about, plus a best-effort local git ls-files check on the same file — no network call, ever. Exits 1 if any high/medium finding is present, so it works as a CI gate on the same pull request that adds or edits the config.
What's checked
| Check | How |
|---|---|
| Blanket Bash allow-all | A permissions.allow entry of bare "Bash" or "Bash(*)" — every shell command runs with no prompt |
| Unscoped destructive command | An unscoped allow entry for a command family whose blast radius is large (sudo, rm, curl, wget, chmod, eval, dd, mkfs, shutdown, reboot), with no matching deny rule for the same command |
| Plain-http MCP server | An mcpServers entry whose url starts with http:// instead of https:// |
| Hardcoded-looking secret | An mcpServers entry's env or headers value that is a literal matching a known API-token format (high confidence) or a long, space-free, unrecognised literal (medium confidence), instead of an ${VAR}-style reference |
| No deny rules at all | Informational only: allow is non-empty and deny is completely empty, so nothing blocks a future edit from widening allow |
This is a static shape-matcher, not a runtime guard and not a secret-verification service — it never launches a server's command, never contacts a server's url, and never checks whether a flagged value is a real, live credential. A clean result is not a certification that your config is safe; a flagged one is not proof a value is a secret. See why these configs are worth linting, and where this checker's own edges are, or this checker vs. reading the JSON by eye.
Common questions
Why does a Bash allow-all rule matter?
Claude Code's permission rules scope a tool by an optional pattern, e.g. Bash(git diff:*). A bare "Bash", or a pattern whose scope is itself a wildcard, grants every shell command with no prompt at all — functionally the same as disabling permission prompts for the one tool an agent can use to do almost anything else on the machine, including reading other credentials, modifying git history, or making arbitrary network requests via curl.
Why flag a plain http:// MCP server URL?
An MCP server reached over http:// sends every request, including any bearer token or API key in its headers, unencrypted on the wire. Anyone on the same network path can read or tamper with it. This is worth catching mechanically because a config that started as a local http://localhost dev server during setup is easy to copy into a shared or committed file without ever switching to https://.
Does this tool run, connect to, or attack anything the config describes?
No. It only reads the JSON text you give it (pasted, or a local file when using the CLI) and looks for known-risky shapes in it — it never launches an MCP server's command, never makes a network request to a server's url, and never sends a value anywhere to test whether it is a real, live credential.
How confident is the hardcoded-secret check?
A value matching a known token format (an Anthropic, OpenAI, GitHub, Slack, AWS, Google, GitLab, or npm token prefix) is reported at high confidence. A long, space-free, unrecognised literal is reported at medium confidence — flagged because it looks more like a token than a normal setting, not because it is proven to be a secret. Either way, verify by reading the value yourself before rotating anything.