Security & Governance

Tool Permission Threat Modeling

What Bash access really means, how namespaced constraints protect you, and the security model behind 1,372 skills.

~16 min read 3,100 words Data-Driven

The Permission Model

Every skill in the Claude Code Plugins catalog declares, upfront, the complete list of tools it is allowed to use. Not a suggestion. Not a hint. An explicit, enumerated surface area that Claude enforces at runtime before executing a single action. If a skill's frontmatter says allowed-tools: Read, Glob, that skill cannot write a file, run a command, or fetch a URL — even if the skill's instructions tell Claude to try.

This is the principle of least privilege expressed as a data structure. And it's worth understanding exactly what that means in practice, because the 13 tools in the specification are not created equal.

1,372 skills across 315 plugins have been validated against the tool permission specification. Each one declares its allowed tools explicitly in YAML frontmatter before any instructions are evaluated.

The 13 Tools and Their Risk Profiles

The complete list of valid tools as of the 2026 spec is: Read, Write, Edit, Bash, Glob, Grep, WebFetch, WebSearch, Task, TodoWrite, NotebookEdit, AskUserQuestion, and Skill. Any tool name outside this list is flagged as invalid during validation and will fail CI.

Each tool has a different blast radius. Some of them can only observe — they read data from the filesystem or search for patterns. Others can modify state. And one of them, Bash, can in principle do anything the operating system allows.

Tool What It Does Risk Level Reversible?
Read Reads file contents from the filesystem Low N/A (read-only)
Glob Matches file paths by pattern Low N/A (read-only)
Grep Searches file contents by regex Low N/A (read-only)
WebSearch Queries a search engine Low N/A (read-only)
AskUserQuestion Prompts the user for input before proceeding Low N/A (interactive)
TodoWrite Manages the structured task list Low–Medium Yes
Write Creates or overwrites files Medium With backups
Edit Makes targeted in-place changes to existing files Medium With version control
WebFetch Fetches content from a URL Medium N/A (network I/O)
NotebookEdit Modifies Jupyter notebook cells Medium With version control
Skill Invokes another skill (chaining) Medium–High Depends on child skill
Task Spawns a subagent with its own execution context High Depends on subagent
Bash Executes shell commands (unrestricted or namespaced) High Depends on command

The risk levels here aren't arbitrary. They map to two things: the ability to modify persistent state, and the ability to interact with systems outside the current project. A skill that only uses Read, Glob, and Grep cannot change anything. A skill that uses Write and Edit can change files, but only files. A skill that uses Bash without constraints can delete files, run network requests, install packages, or fork processes.


Why Bash Gets Special Treatment

There's a reason Bash is the one tool in the spec that supports namespacing. Every other tool in the list has a well-defined scope — Read reads, Write writes, Grep searches. Their names are their contracts. But Bash is different. Bash is a meta-tool. It grants access to everything the shell can reach, which is effectively everything your user account can touch on the operating system.

When a skill declares allowed-tools: Bash with no namespace, it's asking for unrestricted shell access. That skill could, in principle, run rm -rf, curl | bash, or git push --force. Whether it actually does any of those things depends entirely on the skill's instructions — but the permission has been granted, and there's no technical guardrail below it beyond Claude's own judgment.

Unrestricted Bash access is not inherently malicious — many legitimate skills need it. But it requires more scrutiny than any other tool in the spec. When you see it, your first question should be: does this skill actually need that much access?

This is where namespacing comes in. Instead of granting full shell access, a skill author can constrain Bash to a specific command prefix. Bash(npm:*) means the skill can only run commands that start with npm. Bash(terraform:*) means only Terraform commands. The wildcard after the colon means "any arguments to that command," but the command itself is fixed.

This is a meaningful security boundary. A skill that declares Bash(npm:*) cannot run git push, cannot delete files, cannot hit an arbitrary URL with curl. It can run npm install, npm test, npm publish — all the npm surface area — but nothing outside it. The constraint is narrow enough to be meaningful and specific enough to be auditable by a human reviewer in about three seconds.

The key insight: namespaced Bash shifts the review question from "can this skill do anything dangerous?" to "is this specific command family dangerous in this context?" That's a much easier question to answer.

Namespaced Bash in Practice

Across the skills catalog, several Bash namespace patterns appear repeatedly. Each maps to a different category of work — package management, databases, infrastructure, cloud APIs. Here's what each one grants and, equally importantly, what it prevents.

Namespace What It Allows What It Prevents Common Use
Bash(npm:*) All npm subcommands and flags Git, file deletion, curl, any non-npm command Node.js dependency management, package publishing
Bash(python:*) All python invocations System commands, pip (separate binary), file I/O via shell Running scripts, REPL evaluation, test runners
Bash(psql:*) PostgreSQL CLI commands MySQL, file writes, network calls outside psql Database migrations, query execution, schema inspection
Bash(mysql:*) MySQL CLI commands PostgreSQL, other databases, system shell MySQL query execution, dump/restore operations
Bash(ansible:*) Ansible CLI toolchain Direct SSH, file system writes outside ansible Playbook execution, inventory management
Bash(terraform:*) All Terraform subcommands AWS CLI directly, kubectl, file system manipulation Infrastructure plan/apply, state management
Bash(cmd:*) Windows Command Prompt commands PowerShell, bash/sh, Unix utilities Windows-specific automation workflows
Bash(api:*) API-related CLI commands (curl, httpie, etc.) File system, package managers, database CLIs REST API testing, endpoint verification
Bash(aws:s3:*) AWS S3 CLI operations only EC2, IAM, Lambda, all non-S3 AWS services Object storage reads/writes, bucket management
Bash(az:storage:*) Azure Storage CLI operations only Azure Compute, Azure AD, all non-storage Azure services Blob storage operations, file share management

Notice the two-level namespaces at the bottom: Bash(aws:s3:*) and Bash(az:storage:*). This pattern is particularly powerful because it constrains not just which CLI tool is available, but which subcommand group within that tool. A skill with Bash(aws:s3:*) cannot touch IAM policies, cannot modify Lambda functions, cannot list EC2 instances. It is locked to the S3 service surface only.

This layered namespacing is how you express "this skill needs cloud access, but only to one specific service" — a common and legitimate requirement that, without namespacing, would require granting full AWS CLI access or nothing at all.


The Risk Spectrum

Security models that treat all tools as equally risky aren't useful. Real threat modeling requires calibrating attention to the actual blast radius of each tool. Here's a more granular breakdown of how to think about the risk spectrum.

Low Risk: Observation Tools

Read, Glob, Grep, and WebSearch share a defining property: they cannot change anything. They observe and return. A skill that uses only these four tools is, at most, an information leak risk — and even that depends heavily on what the skill does with what it reads. In most contexts, a purely observational skill is safe to run without careful review.

AskUserQuestion also falls here. Its function is to pause execution and wait for human input before proceeding — which is actually a risk-reducing pattern, not a risk-adding one. Skills that ask before acting are generally safer than skills that don't.

Medium Risk: State Modification

Write, Edit, and NotebookEdit can all change files on disk. The risk depends on what files are in scope and whether the project uses version control. In a git repository, most file changes are recoverable — which is why these tools sit in the medium range rather than the high range. Outside version control, file writes are permanent, and the risk profile shifts upward accordingly.

WebFetch introduces a different kind of medium risk: network I/O. It can retrieve data from external URLs, which means it can be used to exfiltrate data via URL parameters, or to pull in content from untrusted sources. This risk is theoretical in most skill contexts — legitimate skills use WebFetch to read documentation or fetch API specs — but it's worth noting that the tool has an outbound vector that the pure filesystem tools don't.

High Risk: Execution and Delegation

Task and Bash are the two tools where reviewer attention should concentrate. Task spawns a subagent — an entirely separate execution context that can have its own tool permissions, its own instructions, and its own chain of actions. The parent skill's constraints don't automatically propagate to subagents. A skill that uses Task needs to be reviewed not just for its own permissions, but for what it might spawn.

Bash, especially unrestricted, sits at the top of the risk spectrum for the reasons described earlier. But even namespaced Bash deserves scrutiny. Bash(terraform:*) with instructions to run terraform apply -auto-approve on production infrastructure is high risk regardless of the namespace constraint. The namespace limits the command family; it doesn't limit what that command family can do to your infrastructure.

Reviewer heuristic: if a skill declares Bash (unrestricted) or Task, read the skill instructions carefully before the description. The permissions are the lead signal.

What the CI Pipeline Catches

The validation pipeline that runs on every pull request and commit to the plugins repository is a multi-layered automated review. Understanding what it catches — and what it doesn't — is useful context for anyone thinking about the actual security posture of the catalog.

Secret Scanning

Every plugin file is scanned for patterns that match common secret formats: API keys, tokens, private key headers, connection strings with embedded credentials. This scan runs at multiple points — on plugin source files during the validate CI job, and again on the generated zip archives during the validate-cowork-security.mjs post-build step.

The post-build scan on zip files is particularly important because it catches cases where secrets might be embedded in generated artifacts even when the source files looked clean. A build step that inlines configuration values could theoretically produce a zip containing credentials that weren't in any source file the pre-build scan examined.

Dangerous Pattern Detection

Beyond credential patterns, the pipeline scans for shell patterns that are frequently used in supply chain attacks: piping curl output to bash, downloading and executing remote scripts, suspicious use of eval, and similar constructs. These patterns don't automatically fail a plugin, but they flag it for closer human review.

Tool Name Validation

The validate-skills-schema.py script enforces that every tool name in every allowed-tools declaration matches the known valid set. An unrecognized tool name — whether from a typo or an attempt to declare a tool that doesn't exist in the spec — fails validation outright. This prevents a class of confusion attacks where a skill might try to claim permissions that look legitimate but aren't actually valid.

Structural Completeness Checks

Required fields — name, description, version, author, and allowed-tools — must all be present. Missing frontmatter fields fail validation before the skill can be published. This isn't just housekeeping: an incomplete permissions declaration is semantically ambiguous, and the pipeline refuses to let ambiguous skill definitions reach the catalog.


The 8-Field Constraint as Security

One of the less obvious security properties of the catalog is the strict constraint on plugin.json structure. The spec allows exactly eight fields: name, version, description, author, repository, homepage, license, and keywords. Any additional field causes CI to fail.

This might look like arbitrary strictness, but it's actually preventing a specific class of attack.

Configuration Injection

Package manifests that accept arbitrary fields are a vector for configuration injection. An attacker who can add arbitrary fields to a package manifest can potentially influence how that manifest is interpreted by downstream tooling — install hooks, resolution strategies, registry behavior. The npm ecosystem has seen this attack surface exploited in practice.

By enforcing a strict eight-field allowlist, the catalog eliminates this entire class of risk. There is no way to embed a scripts.postinstall field, a dependencies object, or any other mechanism that downstream tooling might act on. The manifest is inert beyond the declared fields.

Dependency Confusion Prevention

Dependency confusion attacks work by publishing a package to a public registry with the same name as a private internal package, relying on the package manager preferring the public version. The eight-field constraint doesn't prevent this directly, but it removes the dependencies and devDependencies fields from the manifest entirely — plugins cannot declare npm dependencies in their plugin.json at all. MCP plugins (TypeScript) have their own package.json that's evaluated separately, but AI instruction plugins have no dependency surface to exploit.

315 plugins pass through the eight-field constraint validation before reaching the catalog. The constraint has caught several submitted plugins with extra fields, including a handful attempting to add scripts blocks.

Practical Guidance for Reviewers

If you're reviewing a plugin before adding it to your own setup, or contributing to the catalog's review process, here's what to look for — and what distinguishes a reasonable permission request from a red flag.

Normal Patterns

A skill that reads code and writes tests probably needs Read, Glob, Grep, and Write — that's a coherent, minimal permission set for that task. A skill that manages npm dependencies needs Bash(npm:*). A skill that inspects and repairs database schemas needs Bash(psql:*) or Bash(mysql:*) plus Read. The permissions should tell a story that matches the skill's stated purpose.

It's also normal for complex, multi-purpose skills to request broader permissions. A "full-stack development workflow" skill that orchestrates an entire feature branch — writing code, running tests, managing git — legitimately needs a broader tool surface than a focused single-purpose skill. The question isn't just "are these permissions wide?" but "do these permissions match what the skill actually does?"

Red Flags

  • Unrestricted Bash with vague instructions. If a skill declares Bash without namespacing and the description is generic or the instructions are opaque, that's worth extra scrutiny. Legitimate skills that need full shell access typically have obvious reasons — deployment scripts, system configuration, complex build orchestration.
  • Task with no explanation of what's being spawned. Skills that use Task to spawn subagents should describe what those subagents do. A skill that says "uses Task to coordinate work" without any specifics about what that coordination entails is giving you incomplete information about its actual blast radius.
  • WebFetch with Bash. The combination of "can fetch arbitrary URLs" and "can run shell commands" is the core pattern in "curl | bash" style attacks. Skills that combine these two tools should have a clear, benign explanation — fetching an API spec to generate code, for instance — and instructions that obviously don't connect the fetch output to the shell execution.
  • Permissions that don't match the description. A "code formatting" skill that requests database access. A "documentation generator" that requests Terraform permissions. When the permissions and the stated purpose are misaligned, either the description is incomplete or something else is going on.
  • Skill chaining with Skill across trust boundaries. A skill in a low-trust plugin that uses Skill to invoke a skill from a different, more permissive plugin is attempting to access elevated permissions through delegation. This pattern needs explicit review.

Practical Review Checklist

Check What to Look For Pass Condition
Permission coherence Do the declared tools match what the skill claims to do? Every permission has an obvious use in the instructions
Bash namespace Is Bash namespaced? If not, why not? Namespaced, or has an explicit reason for full access
Task delegation Does the skill use Task? What do subagents do? Subagent purpose is described and benign
WebFetch + Bash Does the skill combine network fetch with shell execution? The two are clearly not connected in the instructions
Secret patterns Are any credentials or tokens embedded in skill instructions? No static credentials anywhere in the file
Minimal access Could the skill work with fewer permissions? No obviously unnecessary tools are declared

What Remains Open

The permission model described here is a real security improvement over "Claude can use any tool it decides to use." But intellectual honesty requires being clear about what it doesn't protect against. No permission model is a complete security solution, and this one has known boundaries.

Prompt Injection via Fetched Content

A skill that uses WebFetch to retrieve a URL, then passes that content to Claude as context, is exposed to prompt injection in the fetched content. The fetched page could contain instructions designed to manipulate Claude's behavior — to expand its effective permissions, to take actions outside the skill's stated scope, or to exfiltrate data through legitimate channels. The tool permission model cannot fully protect against this because the attack vector is in the content, not the tool invocation.

The same risk applies, to a lesser degree, to Read — a file that a skill reads might contain adversarial content if the file was created by an untrusted source. Skills that read then act on content from untrusted sources carry an inherent injection risk that permission declarations alone cannot neutralize.

Social Engineering Through Skill Instructions

The validation pipeline checks that skill instructions don't contain embedded credentials or obviously dangerous patterns. But it can't evaluate whether a skill's instructions, taken as a whole, constitute social engineering. A skill that's technically well-formed but designed to manipulate Claude into behavior that serves the skill author's interests over the user's would pass most automated checks. This is a fundamentally human review problem, and automated tooling can only help at the margins.

Namespace Escape Edge Cases

The Bash(npm:*) namespace is enforced by checking the command prefix. But npm itself has features that can invoke other programs — npm run executes scripts defined in package.json, and those scripts can be arbitrary shell commands. A Bash(npm:*) constraint allows npm run deploy, which might invoke terraform apply under the hood. The namespace constrains what Claude can directly invoke; it doesn't constrain what those invocations do transitively.

This is a known limitation and not unique to this permission model — it's a fundamental property of how shell namespacing works. Mitigating it fully would require sandboxing at the OS level, which is beyond what the catalog can enforce for every runtime environment.

The Underlying Trust Model

The permission system works within a trust model: Claude is assumed to follow its declared permissions honestly, and skill authors are assumed to be trying to solve legitimate user problems. Both assumptions are reasonable in most cases, and the catalog's review process and CI pipeline are designed to catch deviations from the second assumption before they reach users.

But the model is not adversarial-proof. A determined, technically sophisticated actor who wanted to sneak a malicious skill through review would face real obstacles — secret scanning, dangerous pattern detection, structural validation, human review for featured skills. Those obstacles are meaningful. They're just not a guarantee.

The honest summary: the permission model makes malicious skills harder to write, easier to detect, and limited in blast radius when they slip through. It does not make them impossible. The appropriate response to that is vigilance and process, not paralysis.

The current architecture — explicit permission declarations, namespaced Bash constraints, eight-field plugin manifests, automated secret scanning, and post-build security validation across 315 plugins — represents a deliberate and documented approach to shifting the security baseline. Understanding where the boundaries are is part of using the system responsibly.

Cite This Research

Tool Permission Threat Modeling. Claude Code Plugins Research, 2026. https://tonsofskills.com/research/tool-permission-threat-modeling/