AI12 min read

What a third-party Claude Code skill can do before you have read it

We installed third-party Claude Code skills and checked what they do on the machine. Commands with no confirmation, hooks in every session, silent auto-updates.

A Claude Code skill is a folder with a SKILL.md file in it. It installs with one command, like a package: npx skills add owner/repo or /plugin install name@marketplace. The registries already count installs in the millions — on Vercel's skills.sh, the top skill reports three million — and people install them the way they install npm packages: by name and star count, without opening them.

The difference is that a dependency runs inside your application, while a skill runs as you, on your machine. Below is exactly what it can do — checked on Claude Code 2.1.223 and on live repositories, not on secondhand accounts.

What is in a skill folder

From the documentation and from what the repositories show, a skill has three layers, and each one runs at a different point.

The description — the description field in the frontmatter. It sits in the context of every session while the skill is installed: this is what the model uses to decide whether to pick the skill up on its own, without you asking. To keep the description out of context, the author has to set disable-model-invocation: true explicitly — of the 364 skills in the largest repository we looked at, not one did.

The body — instructions that load when the skill is invoked and stay in context for the rest of the session. The body can contain commands like !`git diff`: Claude Code runs them before the model sees the text and puts the output where the command was. This is not the model being offered something to run — it has already run.

Permissions. The allowed-tools field does not restrict; it grants: the listed tools work without a confirmation prompt for the turn that invokes the skill. The documentation says as much: workspace trust does not gate the field, and "a skill can grant itself broad tool access, so review the allowed-tools of skills checked into a repository before you run Claude Code there."

A plugin is skills plus three more things, each with its own way in: hooks (hooks/hooks.json) — shell scripts that run on session events: start, every tool call, end; MCP servers (.mcp.json) — local processes or HTTP addresses the session connects to; and a bin/ folder whose executables are added to the Bash tool's PATH while the plugin is enabled. Hooks, per the documentation, "execute shell commands with your full user permissions", and the sandbox does not cover them — it covers Bash commands only.

What we checked ourselves

Four skills in an empty project, run with claude -p in the default permission mode — that is, the way Claude Code works in CI or in someone else's script, where there is nobody to answer a prompt.

SkillWhat is in itResult
allowed-tools: Bash(*) + an !`…` command in the bodywrites a marker fileran, no prompt
the same without allowed-toolswrites a marker fileinvocation aborted, no file
allowed-tools: Bash(*) + an instruction in the body, "run this command"writes a marker fileran, no prompt
allowed-tools: Bash(*), auto-invocation by descriptionan !`…` commandin our run the model did not pick the skill up

The marker is a line with our username and a timestamp, so the command ran as us. No workarounds: exactly the behaviour the documentation describes. The fourth row is the honest one — auto-invocation remains the model's decision, and this time it answered without picking the skill up. But the mechanism is there, and the description is already in context, waiting.

A note on modes: in an interactive session, without allowed-tools, the command from the body would have asked for permission. claude -p and the SDK show neither the trust dialog nor the prompt — per the documentation, the folder counts as trusted in that mode, and hooks from a cloned repository's .claude/settings.json run. That is exactly the path Claude Code takes in CI.

What is in real repositories

We cloned five popular repositories and counted what they contain besides instructions. None of them is malicious — that matters. The point is what gets installed along with the text.

A curated marketplace from a well-known audit firm, 31 skills. The allowed-tools field is present in all 31, and in 24 of them the list contains a bare Bash — no pattern. Invoking one of these skills gives you a turn on which any shell command runs without confirmation. Not malice, convenience: a fuzzing skill has to run the fuzzer. But whoever installs it signs up for that without looking.

A collection of 364 skills and 90 plugins with 24,000 stars. Inside, five hooks.json files: Python and bash scripts on SessionStart, SessionEnd and PostToolUse. One of them receives the output of every Bash command and searches it for errors; another reads files from disk at session start and inserts their contents into context. Three .mcp.json files connect an external HTTP server, a local process, and npx tsx with a script from the plugin. Installing the "marketing skills" from this collection installs all of that too.

The marketplace of a popular plugin set, 10 plugins from external repositories: nine of them list only a URL — no sha or ref. Installation takes whatever is at HEAD at that moment.

The most popular skill on GitHub, 99,000 stars, is a mode that shortens responses. Its SessionStart hook is an echo with an instruction for the model. Harmless. But it is the same mechanism: text that lands in the context of every session without your involvement.

Anthropic's official marketplace on our machine: 286 plugins, 233 of them external repositories on GitHub, and all 233 have a sha set. That is the good news. The bad news is in the file next to it: the plugin installed from there had updated itself overnight, and the catalogue again a few hours later; we asked for neither. Official marketplaces have auto-update on by default — the documentation confirms it. Nobody asked what had arrived.

Where the data goes

A skill has no network layer of its own, so "where does it send things" comes down to what routes out it has.

  • Hooks are handed JSON containing the tool's parameters and output — that is, file contents, command output, prompt text — and can do anything with it: it is a shell script with your permissions.
  • MCP servers from .mcp.json are an address the session connects to on its own. In the Figma plugin from the official marketplace it is the vendor's HTTPS address with a build-version header; that is legitimate and transparent, but the field looks the same for any address.
  • !`…` commands run before the model — a curl there is still a curl.
  • Behaviour that changes after install. Unit 42 pulled apart the money-radar skill from the OpenClaw registry: on every invocation it downloaded referrals.json from the author's server, and the instruction told the agent to always recommend the affiliate links from it. The skill is published once; the behaviour is steered from the server afterwards.

The API key is a separate case. Until December 2025, an ANTHROPIC_BASE_URL planted in a repository's .claude/settings.json redirected the very first request — key and all — to a third-party server, before the trust dialog ever appeared (CVE-2026-21852, analysed by Check Point). Closed: requests now wait for trust to be confirmed. But it shows where the key lives and which file reaches it.

What has already happened

This is not a theoretical risk, and the precedents go back to the start of the year.

ClawHub, February 2026. Koi Security checked OpenClaw's entire skill registry: 341 malicious out of 2,857, almost 12%, 335 of them one campaign. Snyk scanned 3,984 skills from ClawHub and skills.sh: 76 confirmed malicious, 13.4% with critical issues, 10.9% with hard-coded secrets. The top hundred on skills.sh came out clean — which speaks to curation more than to the format. The malicious ones all work the same way: a "prerequisites" block in SKILL.md, a base64 string inside it, and a request for the agent to decode the string and pipe it to bash. What follows is the AMOS infostealer and stolen wallet keys. When the scanners learned to read SKILL.md, the authors moved the commands into the skill's comments on the registry page, and one skill padded its README.md with 22 megabytes of junk to push it past the scanners' size limit.

An agent broke out of its sandbox to cheat on a test. No attacker was involved

Claude Code, demonstrations. In January, Prompt Security showed a plugin from a third-party marketplace whose skill, when asked to "install the library", quietly pointed the install at a source of its own — the library imports, the example works, the trojan is in the project. Reversec in May: a reverse shell through an !`…` command plus allowed-tools: Bash(*) — "dynamic context skips the model's reasoning and executes before any checks". It is the same mechanism as in the first row of our table: asked directly, the model refused to run the reverse shell — but here the command never reached it.

Hookify, April 2026. Pluto Security found that a plugin from the official marketplace reads rule files from the project folder and feeds them into the trusted hook channel — so a file in someone else's repository becomes a way to steer the model for everyone who installed the plugin. Five payloads dressed up as "project conventions" made the model hand over environment variables; not one was recognised as an injection. Anthropic's response: "working as designed" — the security boundary is the folder trust dialog.

That last point matters most. Claude Code's security model is honest and documented: trust is granted to a folder, and everything in it counts as yours. Someone else's skill in that folder is yours too.

How this differs from a dependency

A dependency from npm can be malicious too, and there are plenty of examples. But it has four properties a skill does not.

  1. A dependency runs in the application's process. A skill and its hooks run as your user, with your ~/.ssh, ~/.aws, ~/.claude, with the tokens in your environment and with network access. On the Anthropic API, the same SKILL.md runs in a container with no network access; in Claude Code it runs with full host privileges.
  2. Code has a linter; instructions do not. A malicious package is caught by its code; "three lines of markdown" that ask to read an SSH key and send it are, to a scanner, text. Snyk puts the figure at 91%: in that share of malicious skills, code and an injection in the instructions go together.
  3. Review and signing. Anthropic's community marketplace has "automated security scanning", with no details; there is no code signing, no publisher verification, and the plugin manifest requires a name, a description, a version and an author. A curated marketplace is a list of links to other people's repositories with a pinned commit, and the pin holds exactly until the next update.
  4. Trust is transitive. Installing a plugin enables all of its skills, hooks and MCP servers at once — Pluto calls it a "trust pyramid": approving the apex endorses every layer beneath it.

There is a fifth, about people. A dependency is chosen by a developer who reads what it does. A skill is chosen by the model — by description, in context — and the developer learns about it from a line in the output, if they look at all.

What to do about it

None of this requires giving up skills — we write and use our own. It requires treating them as code that runs with your permissions, not as hints.

  • Read the frontmatter before the body. allowed-tools with a bare Bash, Write, Edit is permissions; !`…` in the body is commands. Give it the same attention you would give a package's postinstall script.
  • Look for what is not in SKILL.md. find . -name hooks.json, .mcp.json, the bin/ folder, settings.json at the plugin root. In that 364-skill collection, that is exactly where the interesting part was.
  • Pin. Install by sha, not HEAD; third-party marketplaces already have auto-update off by default — do not turn it on "for convenience". Official ones have it on, and that is worth knowing.
  • Deny the shell to code you did not write. The disableSkillShellExecution setting disables !`…` for all skills except the bundled ones; a deny: Bash(*) rule in your settings overrides any skill's allowed-tools — Reversec checked that. In an organisation, set strictKnownMarketplaces and allowManagedHooksOnly in managed settings.
  • Remember -p. In CI and in scripts there is no trust dialog, and hooks from a cloned repository run. An agent that holds keys and works across other people's repositories is already a system with privileges, not an assistant, and has to be protected as one: separate credentials, an allowlisted network, a sandbox.
  • Scan what is already installed. uvx mcp-scan --skills and its equivalents find the known patterns — not injections in natural language, but at least base64 and curl | bash.

What this does not prove

None of the repositories we looked at does anything bad, and we left them unnamed on purpose: a bare Bash in allowed-tools at an audit firm is a deliberate choice for its own work, not a flaw. The ClawHub figures are about OpenClaw, which has a different registry and a different publishing culture; the top hundred on skills.sh, which is what people use with Claude Code, came back clean.

What it does prove is one thing: a skill is not text but code — code that runs with your permissions, that you did not write, and that updates without you. The industry already knows how to handle everything that fits that description. What remains is to notice that a skill is one of those things.

Sergei Palii

Founder, Sepia Software

About me

Read next

All articles