VAULT
브라우저에서 비밀번호로 파일을 암호화·복호화하며 데이터는 절대 업로드되지 않습니다
Claude Code 권한 규칙 시뮬레이터
Claude Code permission rule simulator: load the merged allow, ask and deny settings, show which rule decides any tool call, and find the bypasses: commands that slip past a Bash pattern, paths that es
보안TypeScript · DockerfileGitHub @basitalisandhu온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Claude Code permission rule simulator: load the merged allow, ask and deny settings, show which rule decides any tool call, and find the bypasses: commands that slip past a Bash pattern, paths that escape an Edit scope, domain rules that admit subdomains, and MCP tools nothing covers. Table, JSON and SARIF. TypeScript CLI.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
claude-perm-sim loads your merged Claude Code permission settings, tells you which rule decides any given tool call, and finds the bypasses: commands that slip past a Bash pattern, paths that escape an Edit scope, and MCP tools nothing covers.
What it does, who it is for, and whyREADME 전문 보기 ↗claude-perm-sim loads your merged Claude Code permission settings, tells you which rule decides any given tool call, and finds the bypasses: commands that slip past a Bash pattern, paths that escape an Edit scope, and MCP tools nothing covers.
For developers and teams who hand-write Claude Code permission rules. Read-only, no runtime dependencies, Node 20 or newer. Part of a small set of Claude Code security tools listed at basitalisandhu.github.io, alongside agent-config-audit and cc-plugin-lock.
No install and no token needed; run it from a project that has .claude/settings.json:
npx github:basitalisandhu/claude-perm-sim load # effective rules, with the file each came from
npx github:basitalisandhu/claude-perm-sim explain "Bash(git push origin main)" # which rule decides one call
npx github:basitalisandhu/claude-perm-sim bypass # where an allow rule admits more than it names
Real output of explain --from calls.txt, which decides one call per line, for a project that allows Bash(npm run *), Bash(git commit *) and Edit(src/**), asks for Bash(git push *) and denies Bash(rm *) and Read(.env):
line decision call decided by
1 allow Bash(npm run test) Bash(npm run *)
2 deny Bash(cd /tmp && rm -rf build) Bash(rm *)
3 ask Bash(git push origin main) Bash(git push *)
4 default ask Bash(npm run build && curl https://example.com) no rule matched every part of the command
5 deny Read(.env) Read(.env)
6 allow Edit(src/app.ts) Edit(src/**)
Line 2 is denied because Claude Code splits compound commands and checks every part; line 4 prompts because curl has no allow rule of its own.
Claude Code decides every tool call against permission rules spread across managed, command-line, local, project, and user settings, evaluated deny first, then ask, then allow. The rules match the text the model writes, with wildcards, wrappers, compound-command splitting, and gitignore-style path anchoring, and the docs say plainly a Bash rule "isn't a security boundary around the program". That leaves two everyday questions hard to answer by reading the files: which rule actually decides a call, and where a rule you wrote admits more than you meant.
claude-perm-sim answers both. It is for developers and teams who hand-write Claude Code permission rules and want to see the effective rule set, trace one decision, and catch a rule that is broader than it looks before it ships.
load merges the settings files in the documented precedence and prints the effective allow, ask, and deny lists with the source file of each rule.explain "<tool call>" prints the decision and the exact rule and file that decided it, plus the rules that also matched but lost. explain --from <file> decides a whole file of calls, one per line.bypass reads your allow rules and reports, structurally, where one admits more than it names: a Bash prefix that runs an arbitrary inner command, an Edit or Read scope that resolves outside its directory, a WebFetch rule that admits subdomains or lookalikes, and MCP tools no rule covers. It does not generate attacks (see What this is not).lint runs static checks on the rule set: duplicates, allow rules shadowed by deny, overly broad patterns, rules that can never match, mixed slash styles, and allow rules that belong in ask.diff shows what changed between two settings files in decision terms: which sample calls flip.Output is table (default), json, or sarif; --format hook prints a PreToolUse hook snippet that runs explain for logging. TypeScript, Node 20 or newer, no runtime dependencies.
Requires Node 20 or newer.
The quickest route needs nothing but Node: npx github:basitalisandhu/claude-perm-sim <command> fetches the source from GitHub and builds it on first use (pin a release with #v0.3.1). The container image below also works without any login.
Every release is published by publish-github-packages.yml in two places on GitHub Packages: the npm package @basitalisandhu/claude-perm-sim and the container image ghcr.io/basitalisandhu/claude-perm-sim. The package is not on npmjs.com yet; when it is, it will use the same scoped name.
Point the @basitalisandhu scope at GitHub Packages in ~/.npmrc (GitHub's npm registry asks for a token with the read:packages scope even for public packages, exported as GITHUB_TOKEN):
@basitalisandhu:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
npx @basitalisandhu/claude-perm-sim load # run without installing
npm install -g @basitalisandhu/[email protected] # or install the claude-perm-sim command
The image runs as the non-root node user with /work as the working directory; mount your project there:
docker run --rm -v "$PWD:/work" ghcr.io/basitalisandhu/claude-perm-sim:0.3.1 load
# From the root of a project that has .claude/settings.json:
claude-perm-sim load # the effective rule set, with sources
claude-perm-sim explain "Bash(cd /tmp && git clean -f)" # which rule decides it
claude-perm-sim explain "Edit(src/app.ts)"
claude-perm-sim explain "WebFetch(domain:evil.example)"
claude-perm-sim bypass # where allow rules are too permissive
claude-perm-sim lint # static problems in the rule set
claude-perm-sim diff before.json after.json # which decisions change
explain also takes the JSON a PreToolUse hook receives, so you can pass a real tool call:
claude-perm-sim explain '{"tool_name":"Bash","tool_input":{"command":"npm run build"}}'
To review a list of calls, put one per line in a file (rule form or hook JSON; blank lines and # comments are skipped) and pass --from <file>, or --from - for stdin. The table shows each line's decision and deciding rule, --format json prints an array, and a line that is not a tool call is reported with its line number (exit code 2) while the other lines are still decided.
By default the tool reads the same files Claude Code does. Override any source with --project <dir>, --user <file|none>, --local <file|none>, --managed <file|none>, add one with --settings <file>, or inject command-line rules with --allow-tool and --deny-tool. Use --untrusted to model a folder whose workspace trust dialog was not accepted, where project allow rules are held back.
explain "<call>" prints the deciding rule, its file, and the rules that also matched but lost to deny-then-ask-then-allow order.bypass reports each allow rule that admits more than it names, with the weakness class and a tighter rule. It exits 1 on a HIGH finding, so CI can gate on it.diff old.json new.json lists the sample calls whose decision flips.lint finds duplicates, shadowed allows, broad patterns, and rules Claude Code accepts but never applies.--format hook prints a PreToolUse hook that runs explain for logging only; it never changes a decision.bypass reads MCP server names from <project>/.mcp.json; use --mcp-from other.json to point at a different file, and repeat --mcp-tool mcp__server__tool for a known inventory. Discovery only reads names: it never expands environment variables, starts commands or contacts servers. A wildcard allow rule for a server the file does not configure is reported as a stale grant. Missing default configuration is allowed, a malformed project file warns on stderr and continues without server names, and a malformed or missing explicit --mcp-from exits 2.
Each finding names the rule, the weakness class, and a tighter rule, and uses an inert placeholder for illustration. It never prints a runnable command.
| Family | What it flags | Tighter rule it suggests |
|---|---|---|
bash-wildcard | Bash(*) or a pattern whose wildcard stands in for the program | one rule per command family |
bash-exec-runner | an allow prefix (sh, node, npx, git -c, docker run, ...) that runs whatever follows it | one exact rule per inner command |
bash-trailing-wildcard | a trailing * that also covers long, unintended argument tails | narrow the wildcard; add deny rules for risky programs |
path-unbounded | a Read or Edit scope that covers an entire tree (**, /**, ~/**) | scope to the directories you use |
path-escape | a scope with a .. segment that climbs out of its directory | anchor with / or //, remove the .. |
domain-subdomain | *.example.com, which admits any subdomain an attacker could register | name each subdomain you trust |
domain-wildcard | domain:* or a mid-label wildcard that admits lookalikes | list exact hostnames |
mcp-wildcard | mcp__server__*, which also allows tools added in a future version | allow one tool per rule |
mcp-stale-server | a wildcard allow for a server the .mcp.json does not configure, dormant until the name returns | remove or rename the rule |
mcp-uncovered | an MCP tool no rule covers, so Claude Code prompts (default ask) | decide the tool explicitly |
Use --mode dontAsk to see which calls a non-interactive run would refuse: anything that would prompt is denied, while allow rules and read-only commands still run. --mode manual is an alias for the default. Use --mode acceptEdits to simulate in-directory edits and a conservative filesystem-command subset, excluding static protected paths from implicit approval; explicit deny and ask rules still win. The default remains --mode default; see acceptEdits semantics and limitations for the inferred subset, protected-path exceptions and symlink boundaries.
The matching semantics come from the Claude Code permissions, settings, managed settings, and hooks pages. Where the docs state a rule exactly, the tool follows it; where behaviour is described only in prose, the tool labels its reading as inferred in docs/semantics.md. Because the docs can change between Claude Code versions, treat a decision from this tool as a close model, not a guarantee, and confirm a specific case with /permissions in Claude Code.
Does this change my settings? No. Every command is read-only. It reads settings files and prints results; it never writes a settings file and never runs any command it analyses.
Is the output exactly what Claude Code will do?
It models the documented rules closely and is tested against them, but Claude Code's parser, version, and undocumented built-ins can differ. Use it to understand and tighten a rule set, then verify a specific decision in Claude Code with /permissions.
Does bypass produce exploits I can run?
No. It is structural: it reads your allow rules and reports where one is more permissive than it looks, using inert placeholders such as <command-after-separator> and sub.<domain>. It never emits a runnable command or URL. See What this is not.
How does this relate to agent-config-audit? agent-config-audit flags risky rules, secrets, and unpinned servers across a project's whole agent configuration; claude-perm-sim simulates the permission engine itself: which rule decides a call, and where a rule admits more than it names. Use both.
Why does Claude Code still prompt for a call this tool says is allowed?
Several documented reasons: an ask rule from any settings file prompts even when an allow rule also matches; a project's allow rules apply only after you accept the workspace trust dialog; a tool marked requiresUserInteraction prompts on every call; and a connector tool your organisation set to ask is never auto-approved. load shows every rule and its source so you can see which one applies.
Each message below is real output. Bad input exits with code 2.
| You see | What it means | Fix |
|---|---|---|
npm error code E401 or a login prompt from npm install @basitalisandhu/claude-perm-sim | GitHub Packages needs a token with read:packages even for public packages. | Use npx github:basitalisandhu/claude-perm-sim <command>, which needs no token, or the container image. |
explain needs a tool call, for example explain "Edit(src/app.ts)" | explain was given nothing to decide. | Quote the call in rule form: explain "Bash(git push origin main)", or pass --from calls.txt. |
error: not a tool name: Bash(ls | The call is not in rule form; here the closing bracket is missing, often because the shell ate it. | Quote the whole call in single or double quotes. |
error: <file> is not valid JSON: Unexpected end of JSON input | A settings file is empty in part, cut off or has a trailing comma. | Fix the file; Claude Code would not read it either. |
error: user settings file not found: /nope.json | A path given with --user, --local or --managed does not exist. | Check the path, or pass none to leave that source out. |
<file>: permissions.allow is not an array; ignored | A list was written as a string. | Write "allow": ["Bash(npm run *)"]. |
mode plan is not modelled; use default (or manual), acceptEdits or dontAsk | Plan, auto and bypass modes are not simulated. | Pick one of the modes it names. |
Rules from your own ~/.claude/settings.json or managed settings show up in load | The tool reads every source Claude Code reads, in the same order. | That is the effective rule set; pass --user none or --managed none to look at the project alone. |
bypass shows where a rule set is permissive; it does not generate attacks. Findings are structural and use inert placeholders.npm install
npm test # builds, then runs vitest against the fixture rule sets
npm run typecheck
Fixtures under test/fixtures/ hold tight and permissive settings files with no real paths and no secrets. See CONTRIBUTING.md, docs/good-first-issues.md, docs/semantics.md, and SECURITY.md.
More tools by the same author: https://github.com/basitalisandhu
MIT, see LICENSE. Copyright 2026 Muhammad Basit Ali.