← Back to Agent Permission Config Check

Why AI coding agent permission configs need a linter

Claude Code, Cursor, and VS Code's Copilot agent mode all read a project-level config that decides what an AI coding agent is allowed to do without asking first, and which external MCP servers it can reach. Claude Code's own .claude/settings.json carries a permissions block (allow, deny, ask rule lists); Claude Code, Cursor, and VS Code all read an mcpServers block from a project file (.mcp.json, .cursor/mcp.json, .vscode/mcp.json respectively) in the same JSON shape. None of it is reviewed the way application code is — it is typically written once, during setup, by whoever got the agent running fastest.

Why the shape drifts wide, not narrow

The failure mode is not malice, it's friction. A permission prompt that interrupts every git status or npm test gets widened to a bare "Bash" allow rule the first time it becomes annoying, and nothing narrows it back afterward because the agent stops asking. An MCP server config copied from a blog post or a colleague's machine during a quick setup keeps its http://localhost URL and its inline API key exactly as written, because switching to an environment-variable reference is a second step nobody was blocked on taking. Both are one-line permission edits with no reviewer and no test suite — the same unforced-drift shape this repository's own agent-instructions-drift-check documents for instruction files, applied to what the agent is allowed to do rather than what it is told.

What this tool checks, and what it deliberately doesn't

CheckSourceOut of scope for v0
Blanket Bash allow-allA bare "Bash" or "Bash(*)" entry in permissions.allowNon-Bash tools (Read, Write, WebFetch) granted broadly — real risk, but a much noisier, product-specific judgment about what "too broad" means for a file-editing or fetch tool
Unscoped destructive commandAn unscoped allow entry for sudo, rm, curl, wget, chmod, eval, dd, mkfs, shutdown, or reboot, with no matching denyAny other command; this is a short, named list of the highest-blast-radius families, not an exhaustive taxonomy
Plain-http MCP serverAn mcpServers entry's url starting with http://Whether the server itself is trustworthy, malicious, or compromised — this tool never contacts it
Hardcoded-looking secretA literal env/headers value matching a known token format (high confidence) or a long unrecognised literal (medium confidence)Verifying the value is a real, live credential — this tool never sends it anywhere to test that

Wiring it into CI as a gate

Run it on every pull request that touches a permission or MCP config file:

- name: Check agent permission/MCP config
  run: node ventures/agent-permission-config-check/cli.mjs check --root .

The command exits non-zero if any high or medium finding is present — so the same pull request that widens an allow rule or adds an MCP server is the one that has to justify it.

Common questions

Isn't this just a secret scanner?

General secret scanners (gitleaks, trufflehog) look for token-shaped strings anywhere in a repository's history and files, which overlaps with this tool's hardcoded-secret check. They do not parse permissions.allow/deny/ask rules for an allow-all Bash grant or an unscoped destructive command, and they do not know the mcpServers JSON shape well enough to flag a plain-http url specifically — both specific to how an AI coding agent's own permission config, rather than application code, grants risk.

Why only these four checks?

Each is a deterministic, no-false-negative-by-construction shape: a rule string either matches the allow-all pattern or it doesn't, a url either starts with http:// or it doesn't. That is deliberately narrower than a full policy review — it catches the small number of shapes that are almost never intentional, not every possible over-broad grant, which would require understanding what the repo's own workflows actually need.

Does a clean result mean the config is safe?

No. A narrowly-scoped allow list can still grant more than a given workflow needs, and a config with no MCP servers at all trivially passes every MCP-related check. This tool proves the absence of four specific, named risky shapes — it does not and cannot prove the presence of safety.