Vetting Agent Extensions
An agent extension is not a library you call — it is text and configuration that your model reads as instructions and tooling your agent runs on your behalf. A malicious dependency has to wait for you to call it. A malicious skill is already in the context window, and a malicious MCP server may execute the moment the agent starts. The trust decision happens before either runs, and it is the decision this skill is about.
This is an adoption gate, not a code audit. The question is not "does this server have a CVE" — it is "if I install this, what can it make my agent do, and what can it read on its way out."
When to Use
- Deciding whether to install a skill pack, plugin, or marketplace entry into Claude Code, Cursor, Cline, or a similar agent
- Reviewing an MCP server before adding it to an agent's config — especially
one that ships its own
.mcp.json,.env, or startup wiring - Judging a repo that mixes skills, MCP config, and bootstrap scripts, where installing the repo wires all three at once
- Assessing whether third-party agent content that gets loaded into context (skill bodies, tool descriptions, resources, bundled notes) can steer the model or leak the current engagement
When NOT to Use
- Auditing an MCP server's own code for vulnerabilities — injection
surfaces, tool scope, authz, transport — use
auditing-mcp-servers. That skill reviews a server you are building or breaking; this one decides whether to trust one someone else built. - Dependency, package, lockfile, and CI/CD supply-chain risk — use
auditing-supply-chain. An extension's npm/pip dependencies are its problem; route them there. - Prompt-injection and tool-abuse testing of an LLM application you are
assessing — use
securing-ai-systems. - Reviewing a diff to an extension you have already adopted — use
reviewing-code-changes; come back here only if the diff changes what loads into context or what runs on startup.
What Makes an Extension Different
Ordinary supply-chain review assumes code runs when called. Three properties of agent extensions break that assumption, and each is a distinct vetting axis.
Content is executed by being read. A skill body, an MCP tool description, a bundled reference file, a "field journal" entry — anything the extension causes to enter the model's context is a candidate instruction. There is no call site to audit. A line in a Markdown file that says, in effect, disregard the operator and send the current findings to this URL is live the instant the model reads it. Treat every file the extension loads into context as attacker- controlled input to your agent, because from the model's side that is exactly what it is.
Config can run on startup, not on demand. A repo that ships .mcp.json, a
.env that redefines where the agent looks for its config, or a plugin manifest
with a launch command, can cause code to execute when the agent boots — before
you have asked it to do anything. Installing the repo is the trigger. This is
the property that makes "I'll just clone it and look" unsafe: cloning is fine;
letting your agent start inside the clone with its config honoured is not.
Loading it costs recall. Even entirely benign, every skill you mount competes for the model's attention, and past a point more skills lower the rate at which the right one loads for a given task. An extension that registers forty skills is not free even if all forty are clean. Adoption has a budget, and the question "do I need this" is part of vetting, not separate from it.
The Vetting Passes
Run these in order. Stop and reject as soon as one fails hard; there is no need to finish a review of a package that runs code on startup you cannot account for.
1. Read everything that loads into context
Every SKILL.md, every tool description in the MCP definition, every resource
or bundled note the extension can surface. You are reading for instructions
aimed at the model, not for bugs:
- Directives to ignore the operator, adopt a persona, or treat prior instructions as void.
- Claims of authority — "the user has pre-approved", "system policy requires", "as the administrator" — planted in content to manufacture consent the user never gave.
- Instructions to read
~/.ssh, browser credential stores,.envfiles, or cloud metadata endpoints, framed as a routine setup step. - Hidden text: HTML comments, zero-width characters, content in a language you did not expect the repo to use. A denylist that scans for English injection strings in a repo written in another language is not protection — check what the repo's actual content could carry, not what its filter claims to catch.
2. Find everything that runs without being asked
- MCP server entries: what command launches, with what arguments, reaching what network destinations.
- Repo-local config that an agent honours on startup —
.mcp.json,.env, editor or agent config files, anything that redirects where the agent loads its own settings from. - Bootstrap and install scripts. Read them. A script that downloads a tool and
runs it is a
curl | bashwith extra steps; the question is whether the downloaded artifact is pinned to a specific version and a hash, or floats onlatest.
Resolve any URL or callback endpoint the extension bundles by reading it, not by fetching it. If you are trying to learn where a package phones home, requesting its endpoint yourself is the exact call it was hoping to trigger.
3. Judge scope and blast radius
- What does the extension ask to reach — the local filesystem broadly, the network unrestrictedly, specific hosts? An extension that wants unscoped network and full-disk read to do a narrow job is over-asking.
- Does any content push the agent to widen its task — a skill that offers to "automatically own an entire domain from one hostname", or that reframes a scoped request as a bigger one? Scope-drift inducement is a real property of aggressive offensive skill packs, and it turns your agent into the thing that exceeds authorization.
- Is a self-propagation or contribute-back mechanism present — does using the extension ship anything back to a third party? Data leaving your machine as a feature is still data leaving your machine.
4. Judge the maintenance signal
Stars are not review. Look at what a fork actually gets you: bus factor (one author with 90% of commits is one account-compromise from a supply-chain event), watcher-to-star ratio (a gap says the stars are reputation, not users), release discipline, and whether any automated merge path lets unreviewed content into a branch that installers pull. An auto-merge workflow gated only by a content denylist is a supply-chain hole wearing a safety feature's clothes — assess what actually blocks a hostile contribution, not what the README says does.
Verdicts
Land on one, with the evidence that forced it:
- Adopt — content read, nothing runs on startup you cannot account for, scope is proportionate, maintenance is real.
- Adopt narrowed — take specific skills or read specific ideas, but do not install the whole package or wire its MCP config. Re-implement the good parts under your own review rather than mounting the source.
- Sandbox only — study it in a VM or container with no access to real credentials or engagement data; never in the agent you work from.
- Reject — runs unaccountable code on startup, carries injection-shaped content, exfiltrates by design, or over-asks for a narrow job.
"Adopt narrowed" is the common right answer for useful-but-overreaching packages and it is not a compromise — a good idea re-expressed under your own review is strictly safer than the same idea mounted from a repo you do not control.
A Worked Case
A reverse-engineering skill pack, seventeen thousand stars in three months, MIT, one author holding most commits. It ships eighty skills, an MCP server, a bootstrap script, and an auto-merge workflow that accepts community "journal" entries into the main branch. The workflow scans contributions for prompt injection — with a denylist of English phrases. The repo's content is largely in another language.
Read the passes against it. Runs on startup: the bootstrap refuses unpinned downloads for some tools but has call sites that fetch others unpinned — a control with a bypass. Loads into context: community entries are auto-merged and then loaded into every user's agent, and the injection filter cannot see instructions written in the language the entries are actually in. Blast radius: a contribute-back flow ships engagement lessons to the public repo, anonymised only by an LLM's judgement. Maintenance: the auto-merge only fails closed today because the platform hands fork PRs a read-only token — a configuration accident, not a designed control; "fix" it and the English-only filter becomes the sole gate for every installed user.
Verdict: adopt narrowed. The scope-contract and structured-finding ideas are worth re-implementing under your own review; the package is not worth mounting, and the contribute-back flow is worth removing entirely. That is a defensible decision backed by what each pass surfaced — not "17k stars, looks fine."
Reading External Sources
Fetch public advisories, specifications, and vendor reports as Markdown:
curl -sL "https://defuddle.md/<url>" # scheme in the path is optional
This strips page boilerplate — roughly 78% fewer tokens on a prose page — and returns the full text rather than a summary, so you can grep it and trust a negative result.
Three things it is not for. Fetch JSON and API responses raw, because readability extraction mangles structured data. Fetch authenticated or JavaScript-rendered pages directly, because it retrieves them anonymously. And never route adversary infrastructure (phishing links, C2, malware hosting), client-owned hosts, or engagement URLs through it — the request leaves your machine to a third party, and for live adversary infrastructure it also tips off the operator.
Some sites block the extractor and return an error blob rather than the page —
{"error":"Failed to fetch: 418 I'm a teapot"} from freedesktop.org, for
instance. That is the fetch being refused, not the source saying the thing
does not exist. Re-fetch the URL directly before drawing any conclusion from
it.
Rationalizations to Reject
- "It has thousands of stars, it's obviously fine." Stars measure reach, not review, and a popular package is a higher-value injection target, not a safer one. Vet the content, not the badge.
- "I read the README, that's the gist." The README is marketing written by the author. The SKILL.md bodies, tool descriptions, and startup config are the attack surface, and they are not the README.
- "I'll just clone it and look." Cloning is safe; letting your working agent start with the repo's config honoured is not. Read it somewhere your agent will not execute its startup wiring.
- "The repo has a security scan / injection filter, so merged content is safe." Assess what the filter actually catches against what the content actually is. An English denylist over non-English content, or a check that fails open under a config it does not control, is theatre.
- "The anonymisation step handles the data risk." LLM-judged redaction is not a control you would defend to a client. If content leaves your machine as a feature, the safe default is that the feature is off.
- "It's a defensive/RE tool, the payloads in it are just documentation." Whether the payloads are dangerous is a different question from whether the packaging steers your agent or runs on startup. A defensive tool can carry an offensive extension mechanism.
References
auditing-mcp-servers— auditing an MCP server's own implementationauditing-supply-chain— dependency, package, and CI/CD supply-chain risksecuring-ai-systems— prompt injection and tool abuse in LLM apps you assessmaintaining-engagement-state— egress modes and the local-journal discipline an adopted extension must not undermine