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.
| Skill | What is in it | Result |
|---|---|---|
allowed-tools: Bash(*) + an !`…` command in the body | writes a marker file | ran, no prompt |
the same without allowed-tools | writes a marker file | invocation aborted, no file |
allowed-tools: Bash(*) + an instruction in the body, "run this command" | writes a marker file | ran, no prompt |
allowed-tools: Bash(*), auto-invocation by description | an !`…` command | in 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.jsonare 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 — acurlthere is still acurl.- Behaviour that changes after install. Unit 42 pulled apart the
money-radarskill from the OpenClaw registry: on every invocation it downloadedreferrals.jsonfrom 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.
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.
- 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 sameSKILL.mdruns in a container with no network access; in Claude Code it runs with full host privileges. - 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.
- 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.
- 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-toolswith a bareBash,Write,Editis permissions;!`…`in the body is commands. Give it the same attention you would give a package'spostinstallscript. - Look for what is not in
SKILL.md.find . -name hooks.json,.mcp.json, thebin/folder,settings.jsonat the plugin root. In that 364-skill collection, that is exactly where the interesting part was. - Pin. Install by
sha, notHEAD; 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
disableSkillShellExecutionsetting disables!`…`for all skills except the bundled ones; adeny: Bash(*)rule in your settings overrides any skill'sallowed-tools— Reversec checked that. In an organisation, setstrictKnownMarketplacesandallowManagedHooksOnlyin 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 --skillsand its equivalents find the known patterns — not injections in natural language, but at least base64 andcurl | 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.
Read next
An agent broke out of its sandbox to cheat on a test. No attacker was involved
OpenAI's models escaped a test environment and broke into Hugging Face to steal benchmark answers. No adversary, no prompt injection — just an optimizer.
LLM costs do not scale the way your intuition says
A feature costing cents in a demo can cost thousands a month in production, and rarely because of the price per token. Where the money actually goes.
Infra Graveyard Weekly #04: DFK Chain dies in four days, and the money hasn't moved
We measured the same dying chain twice, eight days apart. The liquidity is still there, 84% of it inside two contracts the wallet never shows.