claude-code

Title

Claude News

claude-code

permcheck, a PreToolUse plugin for Claude Code, ranks a narrow allow above a broad deny whenever the exception's match set is provably contained inside it: across seven commands from one incident session, it returns three allows and four denies where an unlisted native policy fires seven prompts. The engine, its policy model and its limits are set out by its creator, Saleem Mirza, on Zethian.

At a glance

  • An allow or ask carves out a matching deny only when its match set is a strict subset of it; any uncarved deny still blocks the call, and ties between rules go to ask.
  • Verdicts come back as exit codes 0, 1 and 2, with a corrupt rules file exiting 3; the author measures about 1.7 ms per call, a median of 50 warm runs on an M3 Max.
  • Compound splitting and file operand cross-checks block an allowed cat .env, but tar, git, rsync and PowerShell get no coverage, so the OS sandbox still owns the containment guarantee.

The gap permcheck targets is narrow but common: organizations that have already settled what an agent may do need that decision to survive a rephrased request, a compacted transcript and a changed classifier model. A rule file that returns the same exit code in CI reads as the natural home for settled policy, while judgment calls appear better left to a classifier. Neither layer replaces the sandbox that contains the process.

A Bash(aws:*) deny still blocks aws ec2 describe-instances under native precedence

A common production rule pairs deny Bash(aws:*) with allow Bash(aws * describe-*): inspect AWS, never mutate it. Both rules match aws ec2 describe-instances, and native fixed precedence hands the verdict to the deny, so dropping it is the only way to restore inspection, and dropping it also admits terminate-instances.

The mirror policy fails differently. Issue claude-code#6527, open and labeled bug and area:security, reports that a bare Bash token in allow suppresses the ask list, so rm test.txt runs unprompted. permcheck ranks allow against ask by specificity rather than tier: the same file returns ask for rm test.txt and for touch test.txt && rm test.txt, and allow for ls -la.

Specificity is a score in which literal characters count and an exact specifier earns a fixed bonus. It selects among allow and ask rules only and never rescues an allow that merely overlaps a deny; the CLI lints such shapes at author time, while hook mode stays silent.

A 900-plus rule allow-list still prompted on a chain of git status and gh pr list

One engineer approved more than 700 tool calls over two days, every segment already on the allow-list. Claude Code matches each segment of a compound command independently, which is why an allow-list of that size still produced prompts on ordinary read-only chains.

Denying kubectl, aws and terraform sends all seven incident commands to a terminal; allowing them clears all seven, including kubectl delete pod and kubectl get secret db-creds. One permcheck denial, aws s3 ls s3://audit-logs, is a coverage gap rather than a dangerous call: aws * list-* misses the ls alias, and Bash(aws s3 ls:*) fixes it.

Every verdict is also an exit code, so those rows can be asserted in a loop of two-line checks. Assertions belong in CLI mode, since the hook always exits 0 and carries its verdict in permissionDecision, and they should test for the exact code, because a corrupt rules file exits 3.

Three pre-checks run before permcheck matches a Bash rule

The analyzer splits compounds at &&, ||, pipes, semicolons, backgrounds and newlines, and extracts commands from $(…), backticks, process substitutions and subshells. It re-evaluates commands behind env, sudo, timeout and doas, then tests known readers, writers, transfers and redirections against Read, Write and Edit denies.

On cat ~/.ssh/id_rsa piped into curl -d @-, the pipe splits into two units, the known-reader check extracts the key path, normalization expands the tilde, and the SSH-key Read deny denies the whole pipe. The single-command form with curl --data-binary @~/.ssh/id_rsa hits the same @file cross-check.

The reader list is finite: tar, git and rsync reach the same key with no cross-check, and sh -c or find -exec pass a command as an argument, so the inner call reaches defaultMode unless a rule names the interpreter. The engine returns deny for invalid input, invalid rules and internal panics, while the plugin wrapper falls back to the native flow if its binary is missing.

Where the classifier still wins

Anthropic's auto mode answers the same prompt fatigue with a classifier that judges intent in context, at the cost of a model round trip per classified shell command. Three consecutive blocks, or twenty in total, pause auto mode in an interactive session.

permcheck ships under Apache-2.0 through the Claude Code plugin marketplace and Homebrew. Its builds are not bit-for-bit reproducible: each release publishes SHA256SUMS that pin the installed artifact without tying it to a source tree, so behavior must be checked by running the decision cases.

Comments

No comments yet. Be the first.

Join the conversation

Sign in with Google to leave a comment. Your name and avatar come from your Google profile, and the comment appears after moderation.

We only use your name and avatar from Google. We never store your email address.