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.
What we check: skills and coding-agent configuration.
The static scanner treats five supported object classes separately. What a coding agent does with them depends on the agent, version, trust mode, event and user approval.
SKILL.md, skill.yaml, recognized skill roots
Skill manifests and supported files under their root, including scripts, are scanned as data. They are not executed.
CLAUDE.md, AGENTS.md, GEMINI.md, Cursor and Copilot rules
Supported instruction files may enter model context. Whether and how an agent follows them depends on that agent and its current configuration.
.claude/settings.json, settings.local.json
We report broad supported permission patterns and their static implications. Effective precedence and prompting are version-sensitive runtime behavior.
hooks in .claude/settings*.json
We parse supported hook schemas and command patterns. If and when a hook runs depends on its event, matcher, harness and version.
.mcp.json and recognized mcp.json shapes
We report configured remote endpoints and package-runner commands. We do not connect to or audit a running MCP server.
These are static findings about submitted files. A declaration is not proof of effective enforcement: installed harness version, managed and user settings, provider and session conditions may change what runs. A line that describes an attack and a line that directs one can be the same bytes, and that ambiguity is the source of most accepted false positives below.
What is in scope, and what is deliberately not.
Repository checks read supported files in these five classes. One-skill checks read a pasted SKILL.md or supported files in one skill ZIP. Ordinary application code and unrelated subtrees are outside the general audit scope; supported scripts under recognized skill or agent roots can be read.
Out of scope
Runtime behaviour
Submitted files are not executed by the deterministic scanner. With LLM triage disabled, results are reproducible for the same input, engine version and scan settings.
Inter-agent traffic
A static read of submitted files cannot observe what two running agents say to each other.
Model weights and provider behaviour
A file scan does not assess model weights, reasoning quality or provider behaviour.
Whether a secret is live
Verifying a credential would mean using it.
Semantic intent
Rules match configured values and text shapes in supported files. They cannot reliably infer semantic intent; accepted false positives are published below.
Persisted memory
The scan can read supported instruction files; it does not inspect an agent's stored memory or live context.
The paths above are supported formats and recognized roots, not a claim to inspect every repository subtree. Common dependency and build directories are excluded. If no supported files are found, nothing is checked and no grade is issued.
Supported schemas include Claude settings and hooks, Codex TOML configuration, and supported Copilot repository hooks and path instructions. Invalid analyzed configuration fails the check without a grade; unsupported or invalid configuration cannot establish a clean runtime. External references, including unavailable referenced files, are not fetched from the scanner host.
Repository contents cannot reveal managed and user settings, account-synced skills, cloud or home state, installed plugins, or a live agent session unless explicitly submitted. Unknown settings source, trust, provider and session conditions remain unknown; a grade describes checked evidence, not all effective agent behavior. Unreadable supported files can leave a scan incomplete; a grade does not prove they were read.
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: 9 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.
| Rule | Severity | Axis | Evidence | ASI | CWE |
|---|---|---|---|---|---|
| SD-001 Shell Injection | critical | security | heuristic | ASI05 | CWE-78 |
| SD-002 Prompt Injection | critical | security | heuristic | ASI01 | — |
| SD-003 Path Traversal | high | permission_hygiene | heuristic | ASI02 | CWE-22 |
| SD-004 Credential Access | critical | permission_hygiene | heuristic | ASI03 | CWE-522 |
| SD-005 World-Writable Permissions | medium | permission_hygiene | heuristic | ASI03 | CWE-16 |
| SD-006 Hardcoded Secret | critical | permission_hygiene | heuristic | ASI03 | CWE-798 |
| SD-007 Outbound Network Call | high | security | heuristic | ASI02 | CWE-200 |
| SD-008 Base64 Obfuscation | medium | security | heuristic | ASI04 | CWE-506 |
| SD-009 Curl Pipe Bash | critical | security | heuristic | ASI04 | CWE-494 |
| SD-010 Runtime Download | high | security | heuristic | ASI04 | CWE-494 |
| SD-011 Vulnerable Dependencies | high | security | heuristic | ASI04 | CWE-1104 |
| SD-012 Post-Install Hook | medium | security | heuristic | ASI04, ASI05 | CWE-506 |
| SD-013 Persistence Mechanism | critical | security | heuristic | ASI10 | CWE-506 |
| SD-014 Git Hook Modification | high | security | heuristic | ASI04 | CWE-506 |
| SD-015 CLAUDE.md SQL Injection By Instruction | high | security | heuristic | ASI01, ASI02 | CWE-89 |
| SD-016 CLAUDE.md Comment-and-Control | critical | security | heuristic | ASI01 | — |
| SD-017 settings.json Bash Wildcard Grant | high | permission_hygiene | parsed | ASI03 | CWE-732 |
| SD-018 settings.json Broad Allow with Protective Deny | high | permission_hygiene | parsed | ASI03 | CWE-732 |
| SD-019 settings.json Unsanctioned Hook | medium | permission_hygiene | parsed | ASI05 | CWE-732 |
| SD-020 Hook Shell Metacharacter Interpolation | critical | security | parsed | ASI05 | CWE-78 |
| SD-021 MCP External Domain Reach | medium | permission_hygiene | parsed | ASI04 | CWE-1059 |
| SD-022 DNS Exfiltration | high | security | heuristic | ASI02 | CWE-200 |
| SD-023 settings.json Unrestricted Permission Grant | medium | permission_hygiene | parsed | ASI03 | CWE-732 |
| SD-024 MCP Auto-Installed Package Execution | medium | transparency | parsed | ASI04 | CWE-494 |
| SD-025 Reverse Shell | critical | security | heuristic | ASI05, ASI10 | CWE-506 |
| SD-026 Codex Unrestricted Sandbox | high | permission_hygiene | parsed | ASI03 | CWE-732 |
| SD-027 Nested Bare Git Execution Config | high | security | parsed | ASI05 | CWE-78 |
A variable expansion passed through eval, or backtick command substitution containing a variable, in supported shell/script content. In Markdown, only shell-tagged or untagged code fences are checked.
Accepted false positive: Prose that quotes a dangerous command while explaining it. Documentation and an instruction are the same bytes in a supported file.
Override phrasing, chat-template markers, hidden HTML-comment directives or invisible Unicode in skill manifests, instruction files, or supported content under recognized skill and agent roots.
Accepted false positive: Security documentation that demonstrates an injection payload, and legitimate emoji sequences near the bounded zero-width joiner heuristic.
References that leave the file's stamped skill root: unresolved ../ paths or absolute paths into system locations. In-root traversal is released only when the static path is unambiguous.
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.
A line in a supported file naming ~/.aws/, ~/.ssh/, ~/.gnupg/, ~/.env, .credentials, /etc/shadow or /etc/passwd, including $HOME and ${HOME} spellings for home-rooted entries.
Accepted false positive: Documentation of a credential location and some public-key references; explicit documentary, negation, import, field-doc and all-.pub shapes are damped.
SD-005 World-Writable Permissions #
A chmod pattern in a supported file that grants write access to all users: 777-style modes or a+w / o+w forms.
Accepted false positive: A permissions example inside a troubleshooting section.
Credential-shaped literals in supported files: AWS access keys, GitHub and Slack tokens, sk- API keys, or long values assigned to secret-shaped names.
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 #
Network commands, supported library calls, and some endpoint references in supported files. A sole documented call to an ordinary host is reported on transparency; suspicious or data-carrying calls remain security findings.
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.
A base64 decode command or call, or a long inline base64-shaped literal that is not recognized as a hash, integrity value, URL token or path. The decoded use is not tested.
Accepted false positive: Encoded test fixtures and embedded images.
curl or wget output piped directly into sh, bash or zsh, optionally through sudo, in a supported file.
Accepted false positive: An install snippet copied from an upstream project's own README.
A curl or wget download to a .sh, .bash, .py, .rb or .pl filename; a download chained into sh, bash or zsh; or an interpreter reading curl through process substitution.
Accepted false positive: Documentation of an optional download step.
SD-011 Vulnerable Dependencies #
pip, npm or go installation from specific URL/git forms, or a raw.githubusercontent.com script reference. The rule does not query advisories or flag every unpinned dependency.
Accepted false positive: A first-party URL or internal git host used deliberately. The line alone cannot establish trust.
A supported file naming postinstall, preinstall, prepare, prepublish or related install-lifecycle forms. Whether the named step actually executes code is not tested.
Accepted false positive: A legitimate build step declared in the same field.
SD-013 Persistence Mechanism #
A supported file referencing cron, launch agents or daemons, systemd enable/start or unit paths, Windows scheduled tasks, or shell startup files.
Accepted false positive: A skill whose declared purpose is managing the user's own shell configuration.
SD-014 Git Hook Modification #
A supported file referencing .git/hooks/ or a core.hooksPath configuration. The rule matches the reference; it does not observe a hook running.
Accepted false positive: A repository-hygiene skill that installs a pre-commit hook on purpose.
SD-015 CLAUDE.md SQL Injection By Instruction #
A supported project-instruction file that both directs SQL/query construction and shows SQL string concatenation from a variable. Both patterns must be present.
Accepted false positive: A query example in documentation.
SD-016 CLAUDE.md Comment-and-Control #
A supported project-instruction file directing the agent to treat pull-request comments, issue bodies, or URLs in them as authoritative commands, including cross-line matches.
Accepted false positive: Deliberate automation over trusted issue or comment text; the scanner cannot establish who controls those inputs.
SD-017 settings.json Bash Wildcard Grant #
A Bash or PowerShell allow entry in .claude/settings.json or settings.local.json that grants the shell outright or wildcards 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 Broad Allow with Protective Deny #
In supported Claude settings, a Bash or PowerShell deny subcommand covered by a broader wildcard allow. Current Claude Code deny precedence still blocks it; the pair reveals an overbroad allow.
Accepted false positive: A deny kept deliberately as defense in depth.
SD-019 settings.json Unsanctioned Hook #
In a supported Claude settings hook schema, a command with an absolute or otherwise slashed out-of-repository head, or a pipe into sh or bash. Actual hook timing is not observed.
Accepted false positive: A team's own standard hook, committed on purpose.
SD-020 Hook Shell Metacharacter Interpolation #
In a supported Claude settings hook schema, a command containing an unquoted shell variable. 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 #
A URL or endpoint in a recognized MCP config shape whose host is not localhost, a loopback address, 0.0.0.0 or a .local name. No connection is made.
Accepted false positive: A first-party service the team runs over the network rather than locally.
A DNS command whose hostname is built from a variable or command substitution, on a line that also contains a domain-shaped name.
Accepted false positive: Diagnostic lookups in a networking skill.
SD-023 settings.json Unrestricted Permission Grant #
A bare "*" entry in permissions.allow in .claude/settings.json or settings.local.json. Current Claude Code skips this unanchored glob with a warning; other versions or harnesses may differ.
Accepted false positive: A deliberate grant in a trusted internal repository.
SD-024 MCP Auto-Installed Package Execution #
A recognized MCP config entry whose command basename is npx, uvx, pipx or bunx. Depending on its arguments, the runner may fetch and execute registry content if the harness starts it; the server is not run or audited.
Accepted false positive: Common intended package-runner entries, including diagnostic or non-package invocations. This transparency finding does not prove a package is fetched or compromised.
A socket and shell together in a supported file: /dev/tcp or /dev/udp redirection, netcat execution flags, mkfifo/openssl relays, or an interpreter socket paired with shell execution.
Accepted false positive: A penetration-testing skill whose declared purpose is producing these payloads.
SD-026 Codex Unrestricted Sandbox #
An explicit sandbox_mode = "danger-full-access" declaration at the root or in an inline profile of .codex/config.toml or .codex/*.config.toml.
Accepted false positive: A deliberately unrestricted setup, or a dormant profile that is never selected. This reports a declaration, not proof that a trusted runtime activates it.
SD-027 Nested Bare Git Execution Config #
A nonempty command-valued core.fsmonitor or diff.external in a bounded nested bare Git directory with regular HEAD and config files, an objects directory and core.bare=true.
Accepted false positive: A legitimate Git integration in a nested bare repository that the agent never discovers or executes. A declaration does not establish the effective runtime or an affected agent version.
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.
| Axis | Letters issued | Why the rest are not |
|---|---|---|
| Security | A, C, D, F | B is reserved for a Low-severity finding, and no rule in the current ruleset emits Low. |
| Permission hygiene | A, C, D, F | Same reason. B is reachable by the algorithm and unreachable by the ruleset. |
| Transparency | A, B | Its 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 only reviews existing findings and can only demote them. It cannot find a threat no static rule detected, 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 eb11e25d39bc2c81 · MalSkillBench (arXiv 2606.07131, CC0) pinned 06e0831 · 7944 samples (3944 malicious / 4000 benign) · measured 2026-08-30
| Definition | Precision | Recall | F1 | False-positive rate |
|---|---|---|---|---|
| Default default | 71.89% | 24.77% | 36.85% | 9.55% |
| Any finding badge | 62.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.
| Behaviour | n | Recall, default | Recall, any finding |
|---|---|---|---|
| B1 Data exfiltration | 126 | 32.5% | 100.0% |
| B2 Credential theft | 156 | 28.8% | 98.1% |
| B3 Remote code execution | 99 | 21.2% | 100.0% |
| B4 Malware delivery | 107 | 13.1% | 98.1% |
| B5 Persistence | 102 | 72.5% | 98.0% |
| B6 Reverse shell | 130 | 93.8% | 98.5% |
| B7 Ransomware | 106 | 5.7% | 90.6% |
| B8 Resource abuse | 129 | 7.8% | 99.2% |
| B9 Privilege escalation | 115 | 13.0% | 97.4% |
| B1-B9 Code-level | 1070 | 32.5% | 97.9% |
| B10 Role hijack | 57 | 3.5% | 19.3% |
| B11 Safety bypass | 40 | 2.5% | 12.5% |
| B12 Instruction override | 24 | 4.2% | 20.8% |
| B13 System-prompt leak | 41 | 9.8% | 19.5% |
| B14 Goal hijacking | 27 | 3.7% | 22.2% |
| B15 Content manipulation | 41 | 2.4% | 9.8% |
| B10-B15 Agent-control | 230 | 4.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.
| Tool | Approach | F1 | Precision | Recall |
|---|---|---|---|---|
| Sentry Skill Scanner (full) | static + LLM | 88.6% | 80.5% | 98.4% |
| AI-Infra-Guard (Tencent) | LLM | 85.6% | 84.6% | 86.6% |
| Cisco Skill Scanner (LLM) | static + LLM | 80.7% | 71.4% | 92.7% |
| SkillTrust — security gate | static rules | 68.6% | 68.0% | 69.2% |
| SkillTrust — any finding | static rules | 68.5% | 62.9% | 75.2% |
| SkillScan-Security | static rules | 67.6% | 56.6% | 84.0% |
| LLM Guard (Protect AI) | LLM | 50.9% | 59.1% | 44.6% |
| Cisco Skill Scanner (static) | static rules | 49.0% | 78.5% | 35.6% |
| Snyk Agent Scan | static rules | 43.8% | 93.2% | 28.7% |
| Sentry Skill Scanner (static) | static rules | 40.2% | 67.3% | 28.7% |
| SkillTrust — default | static rules | 36.8% | 71.9% | 24.8% |
| VirusTotal | multi-engine | 35.5% | 99.5% | 21.6% |
| Panguard Skill Auditor | static rules | 29.9% | 76.2% | 18.6% |
| SkillScan (Mitchem) | static rules | 24.0% | 70.0% | 14.5% |
| Skill Security Scan (huifer) | static rules | 13.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. These instruction-based attacks remain a weak point of the scanner.
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. Triage only reviews existing findings and is suppression-only by design: it can set a finding aside, but it cannot create one or discover a threat no deterministic rule found. 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. 27 static checks over supported skill and coding-agent configuration files are a narrow instrument. Ordinary application code is outside the general audit scope, submitted files are not executed, and an A means no rule fired rather than that a skill or configuration is safe. The badge says the same three things in the same words — 27 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 benchmark measurements on this page are measurements on skills. They do not establish measured coverage across coding-agent frameworks, settings, hooks or MCP configurations.
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.