Vet a SKILL.md before you install it
Treat an agent skill like the untrusted dependency it is: parse its SKILL.md, confirm the frontmatter is well-formed, and surface every executable script it bundles, since the research flagged script-bearing skills as the most dangerous, before you ever let your agent run it.
Run this workflow
CI-verified, 2/2 fixtures passing.
Build this with your agent
One copy-paste hands Claude Code, Codex, or Cursor the full recipe, steps included, nothing to fetch.
Intended Use
Anyone adding a third-party skill to Claude Code, Cursor, or a similar agent. CI parses a SKILL.md fixture, asserts the frontmatter carries a name and description, and counts the executable scripts it references so they can be flagged for a scan. Deciding to trust it, and running it, is fenced.
Not for
- Standing in for a security scan, this checks structure and inventories scripts, it does not detect prompt injection or exfiltration (pipe the skill through skillspector for that)
- Skills with no readable SKILL.md or provenance, if you cannot inspect it, do not install it
The Stack
Tested Against
agent-skills SKILL.md format (2026-06)ruby@3.x (YAML stdlib)Side effects & data flow
- Network
- none, local only
- Writes
- ./SKILL.md
- Credentials
- none required
Steps
- 1
Parse the SKILL.md and flag its executable scripts
Write or drop in the SKILL.md, then parse it: the frontmatter (between the first two --- lines) must carry a name and a description, and any reference to a bundled script is counted and flagged for a scan. CI asserts the structure and the script count; the decision to trust and run the skill is fenced.
cat > SKILL.md <<'MD' --- name: pdf-extract description: Extract text and tables from a PDF for downstream analysis. allowed-tools: Read, Bash --- # PDF Extract Run the bundled helper, then summarize the output: bash scripts/extract.sh input.pdf MD ruby -ryaml -e ' text = File.read("SKILL.md") parts = text.split("---", 3) abort "BAD: no frontmatter block" unless parts.length >= 3 fm = YAML.safe_load(parts[1]) || {} body = parts[2].to_s name = fm["name"].to_s desc = fm["description"].to_s abort "BAD: frontmatter must set a name" if name.empty? abort "BAD: frontmatter must set a description" if desc.empty? scripts = body.scan("scripts/").length puts "skill OK: frontmatter valid (name + description), " + scripts.to_s + " executable script reference(s) flagged for scan" ' - 2
Scan, scope, and only then trust it (fenced)
For anything you did not write, run it through SkillSpector, give it the narrowest permissions it needs, and read what the scripts actually do. The flagged scripts are the class the 42,447-skill study found 2.12x more likely to be vulnerable. This judgment step is fenced.
Eval, 2 fixtures
Last passed: verified todayskill-okcontainstimeout 30s · max $0Expected:
skill OK: frontmatter valid (name + description), 1 executable script reference(s) flagged for scanclean-exitexit_codetimeout 30s · max $0Expected:
0
Results
A skill inherits your agent's permissions, including its file access and credentials, with almost no vetting. The cheapest first filter is provenance plus a read: start from an inspectable source like Addy Osmani's agent-skills, then for anything you did not write, parse the SKILL.md and inventory its executable scripts so the risky part is visible before you trust it. This validates structure and surfaces scripts; it is not a security scan (see the SkillSpector recipe for that).
Did this work for you?
Our CI checks the setup runs. You tell us if the whole thing worked. Tell us straight.
Related workflows
Liked this workflow?
Get new verified workflows in WebAfterAI, three issues a week (Tue, Thu, Sat).