Skip to main content

security

Plugin security risks: hooks, scripts, MCP configs, supply chain, prompt injection.

Source facts

Repository
xiaolai/nlpm
Last source activity
September 30, 2026 at 17:29
Detected SKILL.md language
English
Stars
153
Forks
44

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
security
description
Plugin security risks: hooks, scripts, MCP configs, supply chain, prompt injection.
version
0.1.0
user-invocable
false
# Security Scan Patterns for Claude Code Plugins ## Context-Aware File Classification Before assigning severity to any finding, classify the file by its execution context: | File Type | Examples | Can Execute? | Rule | |-----------|----------|--------------|------| | Shell scripts | `*.sh`, `*.bash` | Yes | Apply full severity table | | Code files | `*.py`, `*.js`, `*.mjs`, `*.ts` | Yes | Apply full severity table | | Hook definitions | `hooks/hooks.json` | Runs on every tool call | Apply full severity table | | MCP configs | `.mcp.json` | Yes (server launch) | Apply full severity table | | Package manifests | `package.json` | Via npm scripts | Apply full severity table | | Documentation | `*.md` (SKILL.md, CLAUDE.md, README.md) | **No** | Cap at Low — see rule below | ### Documentation Files (*.md) Patterns in `.md` files are instructional content, not executable code. A `curl | bash` in a README documents a user action the reader types manually — the plugin never runs it. Apply this rule universally: **Any Critical or High pattern found in a `.md` file → downgrade to Low (informational).** Note it as "instructional content in documentation — not executable." Examples: - `curl https://... | bash` in README.md → Low: install instruction for end users - `eval $var` in SKILL.md → Low: pattern shown as example to avoid - `new Function(...)` in CLAUDE.md → Low: educational reference Exception: if a `.md` file is explicitly referenced as a script via `command:` in `hooks.json` or executed via `bash file.md`, treat it as executable and apply full severity. ## Scanning Workflow 1. **Classify files** — categorize each file by execution context (see table above) 2. **Identify execution surfaces** — map hooks, scripts, MCP configs, commands, and install scripts 3. **Scan each surface** — apply pattern tables below, matching regex against file contents 4. **Apply context adjustments** — downgrade documentation findings to Low per the markdown rule 5. **Validate findings** — verify each Critical/High finding is in an executable context before finalizing 6. **Generate report** — produce the structured report (see Report Format section) ## Execution Surfaces Claude Code plugins have five execution surfaces that must be scanned: | Surface | Files | Risk Level | Why | |---------|-------|------------|-----| | Hooks | `hooks/hooks.json`, referenced scripts | Critical | Runs on EVERY tool call automatically | | Scripts | `scripts/*.sh`, `*.py`, `*.js` | High | Executed by commands/agents | | MCP Servers | `.mcp.json` | High | Network access, data flow | | Bash in commands | `commands/*.md` with Bash tool | Medium | Shell execution via Claude | | Install scripts | `package.json` postinstall, setup scripts | Medium | Runs on install | ## Dangerous Shell Patterns ### Critical (immediate risk) | Pattern | Regex | Why | |---------|-------|-----| | Pipe to shell | `curl.*\|.*sh`, `wget.*\|.*bash` | Remote code execution | | Eval with variables | `eval\s+["']?\$` | Arbitrary code execution | | Reverse shell | `bash\s+-i\s+>&`, `/dev/tcp/` | Backdoor | | Base64 decode and exec | `base64.*\|.*sh`, `base64.*\|.*python` | Obfuscated execution | | SSH key exfiltration | `cat.*\.ssh/`, `scp.*\.ssh/` | Key theft | | Token exfiltration | Secrets like GITHUB_TOKEN or API keys sent to curl/wget | Credential theft | ### High (likely dangerous) | Pattern | Regex | Why | |---------|-------|-----| | Subprocess with shell=True | `subprocess\.(call\|run\|Popen).*shell\s*=\s*True` | Unsanitized input reaches shell | | OS system calls | `os\.system\(` | No argument escaping; full shell interpretation | | Dynamic require/import | `require\(\s*\$`, `import\(\s*\$` | Attacker-controlled module path | | new Function with dynamic string | `new Function\(` with string concatenation or template literal | Arbitrary code execution from string; often used to deserialize data that could be imported directly | | File write outside repo | `> ~/`, `> /etc/`, `> /tmp/.*\.sh` | System modification | | Sudo usage | `sudo\s+` | Privilege escalation | | PATH modification | Appending to bashrc, zshrc, or profile | Persistent system modification | ### Medium (context-dependent) | Pattern | Regex | Why | |---------|-------|-----| | Network calls | `curl\s+`, `wget\s+`, `fetch\(`, `requests\.(get\|post)` | Could exfiltrate repo data to external host | | Environment access | `process\.env`, `os\.environ`, shell variable expansion | May leak tokens, keys, or secrets | | File reads outside repo | Reading from home directory or system paths | Exposes credentials or configs outside project | | Runtime package install | `npm install`, `pip install`, `gem install` | Unvetted dependency pulled at runtime | | Shell exec functions | Functions that execute strings as shell commands | String-to-shell boundary; injection risk | ## MCP Configuration Risks Scan `.mcp.json` for: | Risk | Check | Severity | |------|-------|----------| | Remote servers | `url` field pointing to non-localhost | High | | Unknown domains | Domain not in known-safe list | High | | Broad permissions | `permissions` with wildcard or extensive list | Medium | | File system access | Server with `fs` or `filesystem` capability | Medium | | Shell access | Server with `shell` or execution capability | Critical | | Missing auth | Remote server without `auth` field | High | Known-safe MCP domains: `localhost`, `127.0.0.1`, `modelcontextprotocol.io`, `github.com`, `api.anthropic.com` ## Hook Safety Rules Scan `hooks/hooks.json` for: | Risk | Check | Severity | |------|-------|----------| | Hook runs shell script | `command` field references `.sh`, `.py`, `.js` | Medium (must scan the script) | | Hook uses user input | Script receives prompt or input variables without sanitization | High | | Hook on every event | Triggers on PreToolUse or PostToolUse without tool filter | Medium | | Hook modifies files | Script writes to disk on every tool call | Medium | | Hook makes network calls | Script contains network request commands | High | ## Dependency Supply Chain Scan `package.json` for: | Risk | Check | Severity | |------|-------|----------| | postinstall scripts | `scripts.postinstall` exists | High | | preinstall scripts | `scripts.preinstall` exists | High | | Git URL dependencies | Deps pointing to git URLs | Medium | | Unpinned versions | Wildcard or "latest" version (suppress if lockfile present: package-lock.json, bun.lock, yarn.lock, pnpm-lock.yaml) | Medium | Scan `requirements.txt` / `pyproject.toml` for: | Risk | Check | Severity | |------|-------|----------| | Git URL deps | git+https or git+ssh URLs | Medium | | Unpinned | No version pin | Low | | Direct URL | HTTP download URLs | High | ## Prompt Injection Surfaces | Risk | Check | Severity | |------|-------|----------| | Untrusted file content in prompts | Agent reads arbitrary file then uses content in Bash | High | | User input passed to shell | Command takes arguments and passes to Bash without sanitization | Critical | | Template expansion | Variable expansion in hook scripts with user-controlled values | High | ## Severity Definitions | Severity | Meaning | Action | |----------|---------|--------| | Critical | Immediate exploitation risk: RCE, credential theft, backdoor | Block contribution, file security issue | | High | Likely dangerous: shell injection, data exfil, privilege escalation | Block contribution, report in audit | | Medium | Context-dependent: network calls, env access, runtime installs | Report in audit, flag for review | | Low | Minor concern: unpinned deps, broad permissions | Report as informational | ## Pre-Match Context Filter (apply BEFORE flagging) Before generating ANY Critical or High finding from the pattern tables above, verify the matched pattern is in **executable position** — not quoted text being displayed, documented, echoed, or used as test data. This filter applies universally to **every** Critical/High pattern in this skill, not just `curl | bash`. The audit data has shown the same class of false positives across `SEC-curl-pipe-sh`, `SEC-new-function-eval`, `SEC-eval-with-variables`, and `SEC-base64-decode-and-exec` — pattern syntactically present in the file, but in a string context where the shell or interpreter never parses it as code. **Drop the finding silently** if any of these apply: | Filter | What to skip | |--------|--------------| | Inside `echo`/`printf`/`cat` arguments | `echo "curl X \| bash"`, `printf '%s' 'wget Y \| sh'` — the shell never executes the matched substring | | Inside heredoc bodies fed to non-shell consumers | Anything between `<<EOF` / `<<-EOF` / `<<'EOF'` and the closing delimiter, when the heredoc is fed to `cat`, `echo`, a variable, or a usage function — only flag when fed to `bash`, `sh`, `eval`, or piped to a shell | | Inside single- or double-quoted strings on RHS of assignment | `MSG="run: curl X \| bash"`, `JS_CODE='const x = eval(input)'`, `INSTRUCTIONS='see: wget Y \| sh'` — the string is data, not code | | Inside object/dict literals as test/fixture data | `{"jsCode": "eval(item.json.code)"}` — the object value is a string sent to a remote system as workflow/test/fixture data, never parsed locally | | Inside shell comments | Anything after `#` on a line (outside quoted strings) | | Inside `usage()` / `help()` / `--help` output functions | Functions whose only effect is printing text to stderr/stdout | | Inside markdown code fences in `.md` files | Already covered by the documentation-file rule above; reaffirm here | A pattern is in executable position only when the shell or interpreter would actually parse it as a command — not when it is a string the program displays, returns, stores, or transmits. Apply this filter BEFORE confidence assignment, not after; once a Critical/High finding is emitted, the contribute path may ship it. ### Per-pattern guidance **`SEC-curl-pipe-sh` / `download-then-execute`**: - Match `curl ... | (bash|sh)` only when the curl invocation is at the start of a pipeline whose right-hand side is a shell, NOT when the pattern text appears as a quoted argument to another command. - A `chmod +x file && ./file` immediately after a `curl -o file ...` IS executable; flag it. A `chmod +x` shown inside a usage heredoc is NOT; drop it. **`SEC-new-function-eval` / `SEC-eval-with-variables`**: - Match `eval(...)`, `new Function(...)`, `exec(...)` only when the call is in executable position. Verify by reading the surrounding 5 lines: if the match is the value of an object property, the body of a string literal, or fixture/test data being passed to a remote system, drop it. - A `python3 -c "..."` block where the `-c` argument interpolates variables IS executable when the script runs locally; flag it. - A string constant `jsCode: 'const result = eval(item.json.code);'` defined in test data destined for an external workflow runtime is NOT executable in the audited repo; drop it. **`SEC-base64-decode-and-exec`**: - Match `base64 -d | sh`, `base64.decode(...) | exec` only when the decoded output is fed to a local shell or interpreter. If the base64 is a transport encoding for code sent to a remote sandbox/container (e.g., `printf X | base64 -d` where X is constructed locally and shipped via stdin to an E2B sandbox), the local audit has no exposure — drop it. If a pattern is in executable position but is intentional and trusted (e.g., a CI release script that pipes a known maintainer-controlled URL to bash, or `python3 -c` interpolating values from `mktemp`/`stat`/internal tools that cannot contain injection characters), mark it `false_positive: true` with an `fp_reason` explaining the trust path. The reproduction gate at the contribute step will drop it; the rule still gets the self-learning signal. ## Public-by-Design Identifiers (drop SEC-hardcoded-api-key matches) Many "API keys" embedded in client-side code are PUBLIC BY DESIGN — they identify a project to a third-party SDK but carry no privileged access. Flagging them as hardcoded secrets is a category error: the maintainer cannot remove the value without breaking the integration, and the value is already visible to any browser that visits the site. **Drop `SEC-hardcoded-api-key` findings silently when ALL of these apply:** | Filter | What to drop | |--------|--------------| | File is under `public/`, `static/`, `assets/`, `dist/`, `build/`, `_site/`, or other published-output directories | Anything served directly to browsers is, by construction, public. The maintainer can't make it private without redesigning the integration. | | Key matches a known-public-by-design pattern | See list below. | | Filename indicates client-side initialization (`posthog.js`, `gtag.js`, `analytics.js`, `mixpanel.js`, `sentry.js`, `clarity.js`, etc.) | Analytics SDKs require client-side identifiers to function. | **Known-public-by-design key patterns:** | Provider | Pattern | Example |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub