Check S10 · execution surface family · severity HIGH · automatic · one of the 15 skill and plugin checks
The question this check asks: what do the scripts beside this skill's SKILL.md actually do, and what binaries ship with it? The instructions tell the agent what to run; S9 reads those. This check reads the things they run — as code — and lists the things it cannot read at all.
The February 2026 campaigns on ClawHub did not end in the SKILL.md. OpenSourceMalware's write-up of 1 February describes what the instructions pointed at: for Windows a password-protected AuthTool.zip or PolymarketAuthTool.zip on a GitHub release; for macOS a base64-wrapped bash line that fetched a binary, in variants that stripped the quarantine attribute with xattr first — Koi calls the payload Atomic Stealer, as quoted by The Hacker News; OpenSourceMalware “probably a new strain of the NovaStealer malware”. Snyk's ToxicSkills report of 5 February names a payload repository, aztr0nutzs/NET_NiNjA.v1.2, that on 20 September 2026 still carries three *-1.0.0.zip archives beside its instructions. And on 11 February Snyk found the shape turned on the defenders: SkillGuard, a “scanner” skill by c-goro carrying a reverse shell and credential exfiltration — removed, while “hundreds retained the compromised version”. A script is code the agent is told to execute, and a binary is code nobody can read from a listing. The malicious skills page holds each incident with its source.
S10 reads every script this tier fetched for a skill — the files under scripts/ and bin/, and any .sh, .bash, .zsh, .py, .js, .mjs, .cjs, .ts, .ps1, .psm1, .rb, .pl or .php beside the SKILL.md — with comment lines and docstrings blanked first, so that what is matched is the code the script runs and not the prose around it. The shapes are S9's, with S9's guards, and each becomes a row of its own:
| Finding | Fires on | Severity · points |
|---|---|---|
| skill-script-fetch-exec | a fetch piped into a shell, an inline bash -c "curl …", PowerShell's iex — with a URL or a variable on the line; a fetch that is echoed, or that sits in a string literal with no exec call, is displayed, not run | HIGH · 15 points; a note from the vendor's own host or a maintained installer |
| skill-script-credential | a read of ~/.ssh, id_rsa, ~/.aws/credentials, ~/.config/gh/hosts.yml, ~/.netrc, ~/.claude/.credentials.json, the keychain, an env dump of _API_KEY/_TOKEN, a read of .env | HIGH · 15 points on a secret store or beside a HIGH send; .env alone a note; otherwise MED on a listing under 90 days, a note on an established one |
| skill-script-exfil | a send beside a credential file, a host fingerprint ($(uname -a), $(whoami), $(env)) or a file read (@/path), or to a paste, tunnel or request-bin host alone | HIGH · 15 points; a send to the plugin's own host a note |
| skill-script-persistence | crontab, launchctl, LaunchAgents or LaunchDaemons, systemctl enable, a Run key, schtasks, reg add, an append to .bashrc, .zshrc or .profile | HIGH · 12 points |
| skill-script-obfuscated | a base64 blob of 200 characters or more beside a decode, a \x-escaped run of 40 or more, a line over 2 KB | MEDIUM · 6 points; a note on a bundle (*.min.js, *.umd.js, *.bundle.js, dist/, vendor/) |
| skill-binary | a binary or archive under a skill directory, bin/, scripts/ or hooks/ — listed, not read | HIGH · 10 points; a note for a Gradle wrapper or a testdata/, fixtures/ or test/ path |
Three rows per kind, worst first. Every row above HIGH warns under the default profile and is a REVIEW; none blocks on its own, because a script's fetch or a shipped executable is what a good many honest tools do, and the finding's job is to put the file name and the line on the decision. The binary row reads exactly what it is: “a binary or archive shipped beside the instructions — listed, not read on this tier”. What the archive holds is the deep scan's question, and the deep scan reads the whole package.
Measured on 20 September 2026 through the same code as /v1/skill/score: S10 fired on 2 of 189 skills.sh keys, 6 of 310 official-marketplace plugins, 0 of 100 community plugins and 1 of the malicious set's 12 verdict rows. Every one is a real file. On skills.sh: anthropics/skills ships shadcn-components.tar.gz beside a SKILL.md and vercel-labs/agent-skills a deploy-to-vercel/Archive.zip — both graded HIGH by the letter of the rule on an established listing, and the record marks both as a grading to revisit rather than a misread. On the official marketplace: Carta's three plugins (carta-cap-table, carta-crm, carta-investors) ship a compiled hook, hooks/bin/hooks-windows-amd64.exe; semgrep ships scripts/hook.exe and its Windows twin; zscaler a zip beside each of three SKILL.md files; and shopify-ai-toolkit a bundled scripts/log_feedback.mjs whose longest line runs to 3,887 characters — the obfuscation shape, on a telemetry script that is not on the bundle list, graded MED as decided. The malicious row is aztr0nutzs/NET_NiNjA.v1.2, REVIEW 62 on its three archives, with 116 of its 128 files unread on this tier: the honest result for a repository the deep scan is the tool for. No skill on the three top lists was blocked by this check, and no script on them carried a fetch-and-execute, a credential read, a send or a persistence shape above a note.
An archive that is a product asset. A component library shipped as a tarball beside the skill that installs it, or a deployment bundle the skill uploads, is the same file list as a payload, and this tier cannot tell them apart; it lists the file and says so. A compiled hook is the same case: Carta's and Semgrep's Windows executables are what a cross-platform hook looks like, and they are HIGH because a hook is code that runs on every tool call without the model deciding. A bundled script over the line-length rule that is not under dist/ or named *.min.js — Shopify's telemetry script — is MED by the letter, and the deep scan reads it. Each of these is a REVIEW with the file named, never a BLOCK, and an override records why.
What the first measurement got wrong, and no longer does: a Python helper whose docstring described the credential file it deliberately skips was read as reading it, and a vendored bundle's one long line was read as obfuscation — comments and docstrings are blanked now, and a bundle is a note. A send to an API needs a credential file, a fingerprint or a file read beside it, because “post the result to https://api.example.com with your token” is every integration.
What is inside a binary or an archive. S10 lists it; the deep scan opens it. The Cloud Security Alliance's 10 June 2026 note records a precompiled .pyc defeating a vendor's skill scanner — a .pyc is a binary here, listed and not read, so that trick lands as a REVIEW on the file's presence rather than on its contents. Scripts this tier did not fetch. Twelve content reads per key, SKILL.md files first: a skill with many scripts is a partial scan, the note says how many files went unread, and a partial scan is never a pass on them. A script fetched at run time by an instruction — the curl … | sh in the SKILL.md is S9's; what the fetched script does is nobody's until it exists. ClawHub (@owner/slug) is parsed and routed but not assessed on this tier: no script is read there yet, and the key answers “not assessed” — never a pass.
Scope, the same on every page of this roster: RepoGates does not see git clone, package managers or curl — outside Claude Code with the RepoGates plugin, whose hook refuses a clone or install that names a blocked repository on the command line, before it runs. The hook sees Bash tool calls in that one client and nothing else: npx skills add owner/repo and claude plugin install name@marketplace are shell lines it judges; /plugin install is a slash command the hook never sees; a skill copied by hand into ~/.claude/skills is never seen at all. This tier reads the listing record, the tree once, and at most twelve files of 64 KB each — SKILL.md files first, then a plugin's agents and commands, then the scripts beside each skill; more than fits is a partial scan, stated, and never a pass on what was not read. The deep scan reads the whole package. A PASS means nothing matched on what was read, never that the skill was run.
List what ships beside the instructions before the agent runs any of it. An executable, an archive or a compiled file under a skill directory is the thing to open first:
find SKILL_DIR -type f \( -name '*.zip' -o -name '*.tar.gz' -o -name '*.exe' -o -name '*.dll' -o -name '*.so' -o -name '*.dylib' -o -name '*.pyc' -o -name '*.jar' \)
Then read the scripts for the same shapes S9 looks for — a decode piped into a shell, a password on an archive, a fetch piped into sh, a read of ~/.ssh or ~/.aws, a curl -d to a host you do not recognise — and for the two that only a script can do: an append to your shell profile, and a crontab, launchctl or systemctl enable that outlives the session.
Why is a zip beside a SKILL.md a finding when the check cannot read it? Because the archive is the delivery path the February 2026 campaigns actually used. OpenSourceMalware's account of ClawHavoc describes Windows victims sent a password-protected AuthTool.zip and macOS victims a base64-wrapped bash line that fetched a binary and stripped its quarantine attribute; Snyk's ToxicSkills report names a repository that still carries three *-1.0.0.zip archives beside its instructions. This tier lists the file and says plainly that it did not read it — HIGH, ten points, a REVIEW — because a binary an agent is told to run is a surface no text check can see into. The deep scan reads it. On 20 September 2026 the finding fired on two skills.sh repositories (a tarball of shadcn components, a deployment archive) and on Carta's, Semgrep's and Zscaler's official plugins, each shipping a compiled hook or a zip: real files, listed as what they are.
Does S10 read a script's comments? No. Comment lines and docstrings are blanked before the shapes are matched, so a script whose docstring says “skip ~/.aws/credentials” or whose comment names a paste host is documentation, not a read or a send. A fetch that is echoed, or that sits inside a string literal with no exec call on the same line, is displayed, not run. That rule came out of the first measurement, where a cloud provider's helper script was read as reading credentials because its docstring described the file it deliberately avoids.
Why is a script's installer line sometimes a note? For the same reason a SKILL.md's is on S9: a vendor's documented curl | sh from its own host, or from one of the maintained first-party installers in the policy list, is the shape every campaign used, and the only thing that separates them is whose host it is. When every host on the line is the plugin's own — decided by the name tokens of its source, its name and its homepage — or a listed installer, the finding is a note; on any other host it is HIGH and the line is shown. A send to the plugin's own host is a note the same way, until it sits beside a read of a secret store.
S9 Instruction shapes — the same shapes in the SKILL.md that tells the agent to run these scripts · S8 Hidden Unicode — read on every script too · C9 Release-asset binaries — the archive on the source repository's release page, where ClawHavoc's payloads sat · C15 Committed binaries and C16 Install scripts — the same two questions, asked of the whole source repository by the 22.
Add RepoGates to Chrome Pricing
Numbers on this page: the 20 September 2026 measurement of 189 skills.sh keys, all 310 official-marketplace plugins, the first 100 community-marketplace plugins and the named incidents, run through the same code as /v1/skill/score; the incident record behind the malicious skills page (OpenSourceMalware, 1 February 2026; The Hacker News quoting Koi, 2 February; Snyk, 5 and 11 February; Cloud Security Alliance, 10 June); thresholds and weights from the product's policy and scoring tables.