Dev.to Security 🔐 Cybersecurity 👁 0 📖 5 min read

Before you install that agent skill, vet it: building a checker that only blocks on proof

Installing an agent skill is an act of trust. A pack — a SKILL.md plus whatever scripts and references its author bundled — runs with your agent's permissions the moment it is installed. The agent reads the instructions;

Installing an agent skill is an act of trust. A pack — a SKILL.md plus whatever scripts and references its author bundled — runs with your agent's permissions the moment it is installed. The agent reads the instructions; the scripts run as you.

The marketplace numbers suggest that trust is being abused at scale. Snyk's ToxicSkills audit (February 2026) found security flaws in 1,467 of 3,984 marketplace skills, with 76 confirmed malicious after manual review. VirusTotal analysed 35,878 skills and found 6,637 malicious — most hiding the payload in plain sentences or concealment mechanics rather than anything a virus scanner would recognise.

The tooling gap bothered me. Per-file linters check that a SKILL.md's frontmatter parses. Nobody was checking the pack: do the referenced files exist? What's in the scripts? Does anything read your keys, watch your screen, or send your files somewhere the docs don't mention?

So I built skill-vet: a static, read-only audit of a whole skill pack, in dependency-free Python.

pipx install skill-vet
skill-vet vet ~/Downloads/some-skill

The rule that shaped everything

Most scanners work on pattern hits: see ~/.ssh in a script, raise an alarm. That model produces blocks nobody trusts — plenty of legitimate packs touch sensitive-looking paths, and a gate that cries wolf gets bypassed by Friday.

skill-vet inverts it. A pack is dangerous only if it does one of three things, each defined as a combination of evidence in the same file:

  1. Stealing — reads credential stores, browser profiles, SSH keys, or environment secrets and sends or copies them out.
  2. Spying — captures screen, keyboard, microphone, camera, or browser history and persists or sends it, without the docs disclosing the capture.
  3. Hidden sending — network calls whose payload or destination is concealed: an encoded payload to an undisclosed endpoint, local file contents sent somewhere the docs never mention, or any send at all from a pack whose docs claim it works offline.

One ingredient without its pair is a warning with the evidence stated: a credential read with no egress is SECRET_ACCESS; an undisclosed endpoint on its own is UNDISCLOSED_NETWORK; a capture API with nowhere for the capture to go is CAPTURE_CAPABILITY. BLOCKED is reserved for the proven combination, and the finding cites both halves — file and line for each.

One judgement call worth stating openly: stealing needs no concealment. Sending a credential to a documented endpoint is still stealing — the docs admitting it doesn't make it safe. Disclosure is what separates spying and hidden sending from their honest counterparts, not stealing from anything.

Read-only, and provably so

A vetting tool you have to trust is a poor joke, so skill-vet is built to be checkable:

  • It never imports, runs, compiles, or decodes anything in the pack. Every file is read as text.
  • Its own source imports no network and no subprocess module — a test parses the imports and pins the set to argparse, json, os, re, sys.
  • The test suite includes a booby-trapped fixture: a script that writes a marker file if it is ever executed. After every run, the marker must not exist, and the fixture tree must be byte-identical.

What it checks

It starts with a pack, not a prompt: the SKILL.md, its referenced files, and every script in the tree. That surface matters because the prompt-injection literature focuses on instructions — the files are where the behaviour lives.

  • Structure: Does every referenced file exist? Are names plausible, or do they exist to slip past a review?
  • Behaviour: Does anything read credentials, capture, or send? The checks are semantic, not grep: they follow how values are used, not just whether a string appears.
  • Disclosure: What does the pack claim to do? The SKILL.md and README are parsed for claims — offline, read-only, no network, no credentials — and every finding is checked against them, so an honest pack that discloses a send is not flagged for hidden sending.
  • The output is a verdict first. PASS, WARN, or BLOCKED, followed by findings with file-and-line evidence — not a report that buries the answer.

And the demo is a fixture, not a fable: a clean skill passes, and a deliberately hostile one fails with all three findings. The point is that the same inputs produce the same verdicts on every run, which is the difference between a check and a demo.

What's not in it

No sandbox. No network calls of its own. No language-server integration, no dependency graph, no scoring, no model calls. Every one of those is a plausible next step, but a gate that cannot be proven safe is worse than no gate, so they stay out until they can be added without breaking the read-only promise.

There's a second gap I am honest about: it checks the pack, not the interaction. A skill that is clean in isolation can still be weaponised by a prompt at runtime, or by a second pack installed alongside it. That is a different problem — one worth building for, but not one this tool pretends to solve.

The limits of a single audit

A check is a snapshot. Packs change; authors add scripts; a version that passed last month can fail this one. So the intended use is to run it before install, and again on update — treat it like a linter you run on the pack you are about to trust, not a certificate that lasts forever.

It also has the usual static-analysis trade-offs. It can't see through obfuscation it doesn't recognise, and it won't catch a pack that hides its behaviour behind a dependency the pack itself doesn't contain. Those are known limits, not hidden ones, and I would rather state them than imply the tool is complete.

What I'm asking for

Nothing yet. This is not a launch post, and there's no star request hidden in it. I am writing it two days early to get the mechanism right in public, before asking anyone to install anything.

If the rule seems wrong — if a combination should block that doesn't, or one blocks that shouldn't — say so. That is the argument I want to have before launch, not after.

If you have seen a skill do something it shouldn't, I would like to hear about it — the behaviour, not the name and shame. The hostile fixture was built from patterns that show up in the wild, and the more of those patterns are documented, the better the next version gets.

The repository is public, the code is MIT-licensed, and the demo is reproducible from the fixture in the repo.

https://github.com/jasveerai88/skill-vet

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.