Methodology

What SkillTrust reads, what it detects, how it grades, how well it does it, and what it misses. Every number on this page carries the engine version, the ruleset checksum and the flag definition it was measured under.

The agent obeys more than the skill.

A skill is not a program the user runs. It is a set of instructions, permissions and triggers that a model acts on, and each of those is a separate attack path. This is the surface we scan.

Instructions

SKILL.md, CLAUDE.md, AGENTS.md, .cursorrules

The model reads these as directives. Text hidden from a human reviewer — an HTML comment, zero-width characters — is not hidden from the model.

Permissions

.claude/settings.json

A committed settings file decides what the agent may run. One wildcard grant makes every deny rule below it decorative.

Triggers

hooks

A hook declared in a repository executes shell at agent init. Nothing is installed and nothing is clicked.

Reach

.mcp.json

An MCP entry decides which servers the agent talks to and, when it auto-installs a package, what code runs before anyone reads it.

In every one of these the same property holds: the documentation IS the program. A line in a manifest that describes an attack and a line that performs one are the same bytes, and that ambiguity is the source of most of what this page reports as an accepted false positive.

What is in scope, and what is deliberately not.

A directory holding a skill manifest is a skill root, and its whole subtree is in scope — not only the agent-configuration files inside it. A payload in a sibling script is read.

SKILL.mdskill.yamlCLAUDE.mdAGENTS.md.cursorrules.windsurfrules.claude/settings.json.claude/skills/.mcp.jsonhooks/.agents/.codex/.opencode/

Out of scope

Runtime behaviour

Nothing is executed. The engine reads files and never runs them, which is what makes the same input produce the same answer forever.

Inter-agent traffic

A static read of one repository cannot observe what two running agents say to each other.

Model weights and provider behaviour

Out of reach of a file scan, and out of the threat model.

Whether a secret is live

Verifying a credential would mean using it.

Semantic intent

We match shapes. A shape that means an attack in one skill is documentation in another, and the engine cannot always tell — see the accepted false-positive column in the catalogue.

A stated boundary is a strength. An unstated one is a bug: a directory that nobody had declared either in or out of scope was silently invisible to us until it was found and fixed, and an empty scan graded A while examining nothing.

Every check, and the false positive each one accepts.

The catalogue is read from the engine's rule registry when this page renders, so it cannot fall behind the engine it describes. A test fails the build if the two ever disagree in either direction.

Evidence class is derived from what a rule is gated to, not declared: 7 rules read a config value with defined semantics (parsed), and the rest match a pattern over prose or script text (heuristic), where the same bytes can be an instruction or a description of one.

The ASI and CWE columns are our own mapping onto OWASP's Top 10 for Agentic Applications and CWE. They are a navigation aid, not a certification by either body.

RuleSeverityAxisEvidenceASICWE
SD-001 Shell InjectioncriticalsecurityheuristicASI05CWE-78
SD-002 Prompt InjectioncriticalsecurityheuristicASI01
SD-003 Path Traversalhighpermission_hygieneheuristicASI02CWE-22
SD-004 Credential Accesscriticalpermission_hygieneheuristicASI03CWE-522
SD-005 World-Writable Permissionsmediumpermission_hygieneheuristicASI03CWE-16
SD-006 Hardcoded Secretcriticalpermission_hygieneheuristicASI03CWE-798
SD-007 Outbound Network CallhighsecurityheuristicASI02CWE-200
SD-008 Base64 ObfuscationmediumsecurityheuristicASI04CWE-506
SD-009 Curl Pipe BashcriticalsecurityheuristicASI04CWE-494
SD-010 Runtime DownloadhighsecurityheuristicASI04CWE-494
SD-011 Vulnerable DependencieshighsecurityheuristicASI04CWE-1104
SD-012 Post-Install HookmediumsecurityheuristicASI04, ASI05CWE-506
SD-013 Persistence MechanismcriticalsecurityheuristicASI10CWE-506
SD-014 Git Hook ModificationhighsecurityheuristicASI04CWE-506
SD-015 CLAUDE.md SQL Injection By InstructionhighsecurityheuristicASI01, ASI02CWE-89
SD-016 CLAUDE.md Comment-and-ControlcriticalsecurityheuristicASI01
SD-017 settings.json Bash Wildcard Granthighpermission_hygieneparsedASI03CWE-732
SD-018 settings.json Redundant Deny Rulehighpermission_hygieneparsedASI03CWE-732
SD-019 settings.json Unsanctioned Hookmediumpermission_hygieneparsedASI05CWE-732
SD-020 Hook Shell Metacharacter InterpolationcriticalsecurityparsedASI05CWE-78
SD-021 MCP External Domain Reachmediumpermission_hygieneparsedASI04CWE-1059
SD-022 DNS ExfiltrationhighsecurityheuristicASI02CWE-200
SD-023 settings.json Unrestricted Permission Grantmediumpermission_hygieneparsedASI03CWE-732
SD-024 MCP Auto-Installed Package ExecutionmediumtransparencyparsedASI04CWE-494
SD-025 Reverse ShellcriticalsecurityheuristicASI05, ASI10CWE-506

SD-001 Shell Injection #

A variable expansion in a command the skill tells the agent to run: eval with a $ in its argument, or backtick command substitution containing a variable. In Markdown, only inside shell-tagged or untagged code fences.

Accepted false positive: Prose that quotes a dangerous command while explaining it. Documentation and an instruction are the same bytes in a manifest.

SD-002 Prompt Injection #

Instructions addressed to the agent rather than the reader: role overrides, 'ignore previous', hidden or zero-width text, invisible Unicode.

Accepted false positive: Security documentation that demonstrates an injection payload, and legitimate emoji sequences near the zero-width joiner heuristic.

SD-003 Path Traversal #

References that resolve outside the skill's own directory: traversal sequences and absolute paths into system locations.

Accepted false positive: An absolute path used as an example or as a documented output location. This rule is the engine's most frequent single source of findings.

SD-004 Credential Access #

A line naming a credential location: ~/.aws/, ~/.ssh/, ~/.gnupg/, ~/.env, .credentials, /etc/shadow or /etc/passwd — in the ~/, $HOME/ and ${HOME}/ spellings.

Accepted false positive: A skill that documents where its own credentials belong, and reads of public key material such as a .pub file.

SD-005 World-Writable Permissions #

Permission bits granting write access to everyone — chmod 777 and equivalents.

Accepted false positive: A permissions example inside a troubleshooting section.

SD-006 Hardcoded Secret #

Credential-shaped literals committed into the skill: AWS access keys, GitHub and Slack tokens, sk- API keys, and long values assigned to a secret-shaped name.

Accepted false positive: Placeholder and example keys. We do not verify a secret is live, because verifying would mean using it.

SD-007 Outbound Network Call #

Outbound network calls the skill makes or instructs the agent to make. A lone call in a documentation or config file, to a plain host and carrying no local data, is reported on transparency instead of security.

Accepted false positive: Documented API usage, and links to the project's own repository. This is the rule most often demoted by the documented-endpoint path.

SD-008 Base64 Obfuscation #

A base64 decode command or decode call, or a long inline base64 literal that is not a hash, a lockfile integrity value, or a path. What becomes of the decoded content is not tested.

Accepted false positive: Encoded test fixtures and embedded images.

SD-009 Curl Pipe Bash #

Remote content piped straight into a shell: curl or wget piped into sh, bash or zsh, including through sudo.

Accepted false positive: An install snippet copied from an upstream project's own README.

SD-010 Runtime Download #

A script fetched at run time and then run: a curl or wget download of a .sh, .py, .rb or .pl file, a fetch chained into a shell, or an interpreter reading a fetch through process substitution.

Accepted false positive: Documentation of an optional download step.

SD-011 Vulnerable Dependencies #

A dependency installed from a URL or a git reference rather than by name from a registry — the pip, npm and go install forms — or a reference to a raw GitHub script. No advisory database is consulted.

Accepted false positive: An install from a first-party URL or an internal git host, deliberate policy rather than an oversight. We cannot tell the two apart from the line alone.

SD-012 Post-Install Hook #

A reference to an install lifecycle name — postinstall, preinstall, prepare, prepublish, or the pre/post/after/before/on-install spellings — naming a step that runs as a side effect of dependency installation. Whether the step executes code is not tested.

Accepted false positive: A legitimate build step declared in the same field.

SD-013 Persistence Mechanism #

A reference to a mechanism that outlives the session: cron, macOS launch agents and daemons, systemd units, Windows scheduled tasks, or a shell startup file.

Accepted false positive: A skill whose declared purpose is managing the user's own shell configuration.

SD-014 Git Hook Modification #

A reference to a repository's git hooks directory, or a core.hooksPath setting repointing where hooks are read from. Hooks execute on ordinary developer commands.

Accepted false positive: A repository-hygiene skill that installs a pre-commit hook on purpose.

SD-015 CLAUDE.md SQL Injection By Instruction #

An instruction file that both directs the agent how to construct a query and shows one built by string concatenation from a variable. Both have to be present.

Accepted false positive: A query example in documentation.

SD-016 CLAUDE.md Comment-and-Control #

An instruction file directing the agent to treat pull-request comments, issue bodies, or the URLs in them as authoritative — to execute them, or to act without asking.

Accepted false positive: A repository whose automation is genuinely meant to act on issue and comment text, where the instruction is deliberate and the inputs are trusted.

SD-017 settings.json Bash Wildcard Grant #

A settings.json allow entry granting the shell tool outright, as Bash or Bash(*), or with a wildcard over curl, wget, sh, bash, zsh or eval.

Accepted false positive: A wildcard grant that the repository owner intends. The finding still says what was granted.

SD-018 settings.json Redundant Deny Rule #

A deny entry for a shell subcommand that a wildcard allow entry already covers. Deny takes precedence, so the deny still blocks; what the pair shows is an allowlist wider than it implies.

Accepted false positive: A deny kept deliberately as defence in depth.

SD-019 settings.json Unsanctioned Hook #

A hook in committed settings whose command runs something the repository does not contain — an absolute or otherwise out-of-repo path — or pipes into a shell. Hooks execute without the user invoking anything.

Accepted false positive: A team's own standard hook, committed on purpose.

SD-020 Hook Shell Metacharacter Interpolation #

A hook command containing an unquoted shell variable expansion. The harness's own CLAUDE_-prefixed variables are exempt.

Accepted false positive: Interpolation of a value that is in practice fixed. We do not assume it is.

SD-021 MCP External Domain Reach #

An MCP server configured with a URL whose host is not localhost, a loopback address, or a .local name.

Accepted false positive: A first-party service the team runs, reached over the network rather than on localhost.

SD-022 DNS Exfiltration #

A DNS lookup command whose hostname is built from a variable or a command substitution, on a line that also carries a domain-shaped name.

Accepted false positive: Diagnostic lookups in a networking skill.

SD-023 settings.json Unrestricted Permission Grant #

A settings.json allow entry that is a bare "*" — an entry stating that the allowlist should not apply.

Accepted false positive: A deliberate grant in a trusted internal repository.

SD-024 MCP Auto-Installed Package Execution #

An MCP server that installs and executes a package on start, so the config alone determines what code runs.

Accepted false positive: The common and intended npx/uvx invocation pattern. This rule reports on the transparency axis for exactly that reason: it is disclosure, not accusation.

SD-025 Reverse Shell #

A socket and a shell together: /dev/tcp redirections, netcat with an execution flag, the mkfifo and openssl s_client relays, or an interpreter socket paired with a shell exec elsewhere in the file.

Accepted false positive: A penetration-testing skill whose declared purpose is producing these payloads.

What the letters mean, and which ones we actually issue.

Three scoring axes, A to F, worst finding on an axis sets that axis's letter. The scale is not evenly used, and we would rather say so than let the badge imply otherwise.

AxisLetters issuedWhy the rest are not
SecurityA, C, D, FB is reserved for a Low-severity finding, and no rule in the current ruleset emits Low.
Permission hygieneA, C, D, FSame reason. B is reachable by the algorithm and unreachable by the ruleset.
TransparencyA, BIts rules are Medium by construction, and Medium caps to B on this axis.

An A on an axis means nothing was counted against it. It is the absence of a detection, not a positive assessment.

"Counted" is the precise word. Where AI triage is enabled we ask a model to check each finding, and a finding it judges to be a benign example — documentation showing what an attack looks like, a test fixture — is set aside and does not affect the grade. Nothing is dropped: every set-aside finding is still shown, grouped under its own heading, so you can see exactly what was not counted.

Triage can only ever demote. It cannot create a finding, raise a severity, or turn a passing grade into a failing one. That is a deliberate limit, and the limits section below says what it costs us.

This is a property of the current ruleset, not of the grading algorithm. The cap table would issue B on security the day a rule emits a Low-severity finding there. We publish the scale as it is rather than renaming grades, because renaming would change grades already recorded on past scans and shown on badges already issued.

How it is measured.

On someone else's benchmark, with someone else's ground truth, using a published protocol, against a binary downloaded from our own release page rather than built locally.

The corpus. MalSkillBench (arXiv 2606.07131), 3944 malicious and 4000 benign skills. Malice is verified by execution, not asserted: a candidate enters the benchmark only if its declared behaviour is actually observed at run time in a sandbox. We did not build it, we cannot influence it, and it is CC0-licensed so anyone can check our numbers against it.

The protocol. Every sample is copied to a clean directory and scanned once in each of the two install layouts a skill can arrive in. The benchmark's own provenance file is removed first: it is present on 97% of benign samples and on none of the malicious ones, and leaving it in would measure the benchmark's packaging rather than the skill.

The definitions — and there are three. They are not interchangeable, and a figure moved from one to another is how this project has produced wrong conclusions internally. Every number below names the one it belongs to.

Default

skill-detector scan .

Fails on a Critical finding and nothing else. No flag needed.

Any finding

grade worse than A on any scoring axis

The quantity the badge renders. Worst grade across security, permission hygiene and transparency.

Security gate

--fail-on-axis security=B

The security axis alone, strictly worse than B. Opt-in, and stricter than the default.

One important caveat travels with every precision figure on this page. The corpus is half malicious by construction. A real population of repositories is not, and precision falls steeply as the true rate of malicious skills falls. Recall does not. Read our precision as a comparison against the other tools measured on the same corpus, never as a prediction of how often a flag you see in your own CI will be correct.

Results.

The whole pool, both install layouts, on a released binary.

engine v0.10.0 · ruleset 2414c32f04000b5d · MalSkillBench (arXiv 2606.07131, CC0) pinned 06e0831 · 7944 samples (3944 malicious / 4000 benign) · measured 2026-08-30

DefinitionPrecisionRecallF1False-positive rate
Default default 71.89%24.77%36.85%9.55%
Any finding badge62.95%75.20%68.53%43.65%
Security gate 67.98%69.17%68.57%32.12%

Out of the box the engine is a precise, low-recall critical gate: it fails on a Critical finding and nothing else. The stricter settings trade that precision for recall, and the false-positive rate rises with it.

Two readings we would rather state than have a reader derive: 978 of the 3944 known-malicious samples are graded A, so a green badge is wrong about roughly a quarter of the malicious population. And 43.65% of the benign samples draw a badge worse than A.

Both install layouts — a skill directory as it sits in a repository, and one installed under .claude/skills/ — produce identical results on every one of the 7944 samples: same grades, same files scanned, same findings, same exit codes. That was not true before. A 23.7-point recall gap between them existed, was found by measuring both rather than the convenient one, and is now closed.

Recall by attack behaviour

The corpus labels 1300 of its malicious samples with the behaviour they perform. This is the most useful table on the page, because the two halves of it are very different products.

BehaviournRecall, defaultRecall, any finding
B1 Data exfiltration12632.5%100.0%
B2 Credential theft15628.8%98.1%
B3 Remote code execution9921.2%100.0%
B4 Malware delivery10713.1%98.1%
B5 Persistence10272.5%98.0%
B6 Reverse shell13093.8%98.5%
B7 Ransomware1065.7%90.6%
B8 Resource abuse1297.8%99.2%
B9 Privilege escalation11513.0%97.4%
B1-B9 Code-level107032.5%97.9%
B10 Role hijack573.5%19.3%
B11 Safety bypass402.5%12.5%
B12 Instruction override244.2%20.8%
B13 System-prompt leak419.8%19.5%
B14 Goal hijacking273.7%22.2%
B15 Content manipulation412.4%9.8%
B10-B15 Agent-control2304.3%17.0%

Code-level attacks — what the skill's code does — we catch at 97.9%. Agent-control attacks — what the skill says to the model — we catch at 17.0%.

The second number is the honest limit of this instrument. The limits section below says why it is where it is, and why the machinery we already have cannot move it.

Note also that the default column is much lower than the any-finding column across almost every code-level family. Out of the box we reliably catch reverse shells and persistence, and little else. The high code-level recall requires the stricter setting, which carries the higher false-positive rate in the table above. That trade is yours to make, and it is the reason both columns are printed.

Where we sit.

MalSkillBench benchmarked twelve tools on the corpus above. Their table, with our three operating points inserted at their real positions rather than at the top.

ToolApproachF1PrecisionRecall
Sentry Skill Scanner (full)static + LLM88.6%80.5%98.4%
AI-Infra-Guard (Tencent)LLM85.6%84.6%86.6%
Cisco Skill Scanner (LLM)static + LLM80.7%71.4%92.7%
SkillTrust — security gatestatic rules68.6%68.0%69.2%
SkillTrust — any findingstatic rules68.5%62.9%75.2%
SkillScan-Securitystatic rules67.6%56.6%84.0%
LLM Guard (Protect AI)LLM50.9%59.1%44.6%
Cisco Skill Scanner (static)static rules49.0%78.5%35.6%
Snyk Agent Scanstatic rules43.8%93.2%28.7%
Sentry Skill Scanner (static)static rules40.2%67.3%28.7%
SkillTrust — defaultstatic rules36.8%71.9%24.8%
VirusTotalmulti-engine35.5%99.5%21.6%
Panguard Skill Auditorstatic rules29.9%76.2%18.6%
SkillScan (Mitchem)static rules24.0%70.0%14.5%
Skill Security Scan (huifer)static rules13.3%28.5%8.7%

Read it as a class result, not a leaderboard. Static rule engines cluster at high precision and low recall; LLM-assisted engines invert that. The highest-recall tool in the table reaches 98.4% by flagging 23.4% of clean skills, and a gate built on it would block a quarter of clean builds.

Four caveats before anyone quotes a row of this. The twelve figures are the paper's, measured by its authors; ours are ours, measured by us on the same corpus and the same protocol, which is better than self-assertion and weaker than an independent evaluation. The tools were measured at whatever setting their authors chose, so the comparison is between configurations as much as between engines. Every precision figure here rests on a 50/50 corpus and none of them predicts precision in the wild. And our three rows are one product at three settings, not three products.

We are not the most precise engine in this table and we do not claim to be. Snyk reaches 93.2% precision and VirusTotal 99.5%, both far above us. The trade is visible in the same rows: they recall 28.7% and 21.6% of the malicious set, where our two stricter settings recall 69.2% and 75.2%.

The median recall among the static engines here is 28.7%, and 2 of our 3 settings sit above it. The default sits below, at 24.8%. A deterministic gate with recall well above the static median is an unusual place to be, and it is the position we are actually defending — not a claim to precision we do not hold.

What we do not catch.

Published because a limitation you can read is worth more than a claim you cannot check.

Agent-control attacks, at 17.0% recall. Role hijack, safety bypass, instruction override, system-prompt leakage, goal hijacking and content manipulation. This is the category people buy an agent-skill scanner for, and it is where we are weakest.

It will not be fixed by the AI triage layer, and it is worth saying why rather than leaving the impression that it might be. That layer is suppression-only by design: it can set a finding aside, never create one. Low recall is a missing-finding problem, and a mechanism that can only remove has nothing to remove when no rule fired. Closing this family needs deterministic rules that read what a skill says to the model, and that work belongs in the engine.

OWASP ASI coverage is partial, not complete. Inter-agent communication, cascading failures and human-agent trust exploitation are runtime properties that a static read of one repository cannot observe. Memory and context poisoning is partial: we read poisoned instruction files and do not read persisted agent memory. Our mapping claims ASI01 goal hijack, and at the recall above the honest label for it is partial.

A grade is not a security audit. 25 static checks over agent configuration is a narrow instrument aimed at a specific surface. It does not read your application code, it does not run anything, and an A means no rule fired rather than that a skill is safe. The badge says the same three things in the same words — 25 static checks, agent configuration only, not a security audit — because a reader who only ever sees the badge deserves the qualification in the same glance as the letter.

The corpus cannot measure everything. A benchmark measures what a change costs on shapes the benchmark contains. It cannot tell us what leaving a hole open costs, because the attack that walks through the hole is not in it. Four live bypasses were once found by adversarial review of code that a full corpus run had reported as unchanged. Both methods are needed, and neither substitutes for the other.