Back to skill

Security audit

Skill Review

Security checks for vulnerabilities and agentic risk

Overview

This is a real security scanner, but its analysis agent can run broad shell commands, fetch arbitrary URLs, and inspect paths without strong containment.

Review before installing. Run this only in an isolated, disposable environment with no sensitive files or credentials available, mount the target skill read-only, restrict network access away from private/internal addresses, and pin or lock dependencies before trusting results.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
src/tools.mjs:532
Finding
Model-Controlled Commands Are Executed Through an Unrestricted System Shell<![CDATA[ ## Vulnerability Details **File Location**: `src/tools.mjs:532-568` **Vulnerability Type**: Command injection through an unrestricted LLM-accessible shell tool **Risk Level**: High ### Vulnerable Code ```javascript export function makeBashTool(cwd) { return { name: "bash", label: "Bash", description: "Execute a shell command and return stdout/stderr. " + "Use this to explore the filesystem, read files, etc. " + "IMPORTANT: All commands run inside the skill directory. " + "Do NOT run commands that modify files or install anything. " + "NEVER execute, run, or invoke any target files — no python/node/bash scripts, " + "no binary execution, no deserialization (pickle.load, yaml.load, eval, etc.). " + "Only use safe read-only commands: cat, head, tail, hexdump, xxd, file, strings, grep, find, ls, wc.", parameters: Type.Object({ command: Type.String({ description: "The shell command to execute" }), }), execute: async (_toolCallId, params) => { const rawCommand = String(params.command || ""); const command = rawCommand .replace(/<\/?tool_call>/gi, " ") .replace(/<\/?function_call>/gi, " ") .replace(/<\/?tool>/gi, " ") .replace(/<\/?function>/gi, " ") .replace(/[{}]+$/g, "") .trim(); if (!command) { throw new Error("Command failed: empty command after sanitization"); } try { const stdout = execSync(command, { encoding: "utf-8", timeout: 30_000, maxBuffer: 1024 * 1024, cwd, }); return { content: [{ type: "text", text: stdout || "(no output)" }], details: { command, rawCommand }, }; } catch (err) { const msg = err.stderr || err.stdout || err.message; throw new Error(`Command failed: ${msg}`); } }, }; } ``` ### Technical Analysis The scanner exposes a general-purpose shell t ...[truncated 2592 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the general-purpose shell tool and replace it with dedicated operations such as: - `readTextFile(relativePath)` - `listDirectory(relativePath)` - `searchText(relativePath, fixedPattern)` - `inspectFileMetadata(relativePath)` 2. Canonicalize every requested path and verify that it remains beneath the canonical Skill root. 3. If an external utility is indispensable, invoke a fixed executable with `execFile` and a validated argument array. Do not invoke a shell. 4. Maintain an explicit executable allowlist and argument schema. Reject metacharacters, absolute paths, traversal components, redirections, substitutions, and unsupported flags. 5. Run analysis in a separate sandbox with: - A read-only mount of the Skill directory - No access to user home directories or credential stores - Network access disabled by default - A low-privilege, disposable operating-system identity - Resource and subprocess limits 6. Treat all target content as untrusted data and do not rely on model instructions to enforce security controls. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/tools.mjs:439
Finding
Binary Analysis Accepts Paths Outside the Audited Skill Directory<![CDATA[ ## Vulnerability Details **File Location**: `src/tools.mjs:439-458` **Vulnerability Type**: Path traversal and missing workspace containment **Risk Level**: High ### Vulnerable Code ```javascript // Keep binary analysis limited to static metadata. Never execute, import, // deserialize, or otherwise load the target artifact. function analyzeBinary(cwd, filePath) { const absPath = path.resolve(cwd, filePath); const lines = []; lines.push(`## Binary Analysis: ${filePath}`); lines.push(""); try { const stat = fs.statSync(absPath); lines.push(`- **Size**: ${stat.size} bytes`); try { const fileType = execFileSync("file", ["-b", absPath], { encoding: "utf-8", timeout: 5_000 }).trim(); lines.push(`- **Type**: ${fileType}`); } catch { lines.push(`- **Type**: unknown`); } const head = readFileHead(absPath, Math.min(BINARY_HEAD_BYTES, stat.size)); const stats = scanBinaryBytes(absPath); ``` The model-accessible dispatch passes the supplied value directly to this function: ```javascript case "binary": return { content: [{ type: "text", text: analyzeBinary(cwd, data) }] }; ``` ### Technical Analysis `path.resolve(cwd, filePath)` produces an absolute path but does not guarantee that the resulting path remains under `cwd`. Inputs containing parent traversal sequences or absolute paths can select files outside the Skill directory. For example, a value resembling `../../sensitive-file` resolves relative to the Skill root and can escape it. An absolute path ignores the intended workspace root entirely. The implementation does not compare the canonical target against `realpath(cwd)`, and it does not reject symlink-mediated escapes. The selected external file is accessed through `statSync`, `readFileHead`, `scanBinaryBytes`, and the `file` utility. The generated output includes file metadata, the first bytes as a hexdump, embedded URLs, and whole-file byte statistics. That output becomes tool context a ...[truncated 1535 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Canonicalize the workspace root using `fs.realpathSync`. 2. Resolve and canonicalize the requested target before opening it. 3. Verify that the canonical target is either the root itself or starts with the root plus the platform path separator. 4. Reject absolute paths, parent traversal components, NUL characters, and unsupported path forms before filesystem access. 5. Use `lstat` and explicit symlink policies. Reject symlinks or verify that their canonical destinations remain inside the workspace. 6. Open files with defenses against symlink races where supported, and verify the opened descriptor rather than relying solely on a pre-open path check. 7. Run the scanner in a sandbox where the Skill directory is the only readable mounted input. 8. Add tests covering absolute paths, repeated `../` components, symlink escapes, and platform-specific path variants. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
src/tools.mjs:296
Finding
Model-Controlled URL Analysis Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `src/tools.mjs:296-338` **Vulnerability Type**: Server-side request forgery through unrestricted URL fetching **Risk Level**: High ### Vulnerable Code ```javascript async function analyzeUrl(targetUrl) { try { const res = await fetch(targetUrl, { redirect: "follow", signal: AbortSignal.timeout(10_000), }); const body = Buffer.from(await res.arrayBuffer()); const redirected = res.redirected; const finalUrl = res.url; const lines = []; lines.push(`## URL Analysis: ${targetUrl}`); lines.push(""); if (redirected) { lines.push(`- **Redirected**: yes`); lines.push(`- **Final URL**: ${finalUrl}`); } lines.push(`- **Status**: ${res.status} ${res.statusText}`); lines.push(`- **Content-Type**: ${res.headers.get("content-type") || "unknown"}`); lines.push(`- **Content-Length**: ${res.headers.get("content-length") || "not specified"}`); lines.push(`- **Actual Size**: ${body.length} bytes`); lines.push(`- **Server**: ${res.headers.get("server") || "unknown"}`); const ct = res.headers.get("content-type") || ""; if (ct.includes("text") || ct.includes("json") || ct.includes("javascript")) { const preview = body.toString("utf-8").substring(0, 500); lines.push(""); lines.push(`### Content Preview`); lines.push("```"); lines.push(preview); lines.push("```"); } return lines.join("\n"); } catch (err) { const isTimeout = err.name === "TimeoutError" || err.code === "ABORT_ERR"; const statusText = isTimeout ? "timeout (10s)" : "unreachable"; return `## URL Analysis: ${targetUrl}\n\n- **Status**: ${statusText}\n- **Error**: ${err.message}`; } } ``` The function is exposed to model-generated parameters through: ```javascript case "url": return { content: [{ type: "text", text: await analyzeUrl(data) }] }; ``` ### Technical Analysis The URL analyzer fetches a model-contro ...[truncated 2311 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict URL analysis to an explicit allowlist of necessary registry and documentation origins. 2. Permit only HTTPS unless a narrowly defined use case requires another protocol. 3. Resolve hostnames before connecting and reject loopback, private, link-local, multicast, reserved, and cloud metadata address ranges for both IPv4 and IPv6. 4. Disable automatic redirects or manually validate every redirect destination with the same rules. 5. Protect against DNS rebinding by connecting only to the validated resolved address and ensuring the HTTP host and TLS checks remain correct. 6. Apply strict response-size limits while streaming; abort the request before buffering excessive content. 7. Do not return response bodies to the model unless necessary. Prefer minimal metadata and redact potentially sensitive values. 8. Separate dependency-registry verification from arbitrary URL analysis and use fixed trusted endpoints for package checks. 9. Run network analysis in a sandbox with egress controls that cannot reach local, private, or metadata networks. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (14)

Credential Access

High
Category
Privilege Escalation
Content
* Initialize configuration. Can only be called once.
 * @param {string} [configFile] - Optional path to a JSON config file.
 *
 * Precedence: .env > inherited shell environment > JSON config file.
 */
export function loadConfig(configFile) {
  if (_config) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
* Initialize configuration. Can only be called once.
 * @param {string} [configFile] - Optional path to a JSON config file.
 *
 * Precedence: .env > inherited shell environment > JSON config file.
 */
export function loadConfig(configFile) {
  if (_config) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
* Initialize configuration. Can only be called once.
 * @param {string} [configFile] - Optional path to a JSON config file.
 *
 * Precedence: .env > inherited shell environment > JSON config file.
 */
export function loadConfig(configFile) {
  if (_config) {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
### Layer-specific rules
- **\`obfuscation_binary\`**: reviewed \`risk_score: 0\` entries are allowed so the user can see what was checked, but do NOT call an artifact safe based only on its filename, extension, location, or commonness. Metadata-like or cache-like files are not automatically safe; if the skill decodes, loads, extracts from, or otherwise uses them in an execution path, judge them from that behavior rather than from the filename alone.
- **\`dependencies\`**: for every declared or undeclared-but-referenced npm/PyPI dependency you identify, you MUST verify it with \`deepAnalysis\` first and include exactly one result entry for that dependency in \`findings.dependencies\`, even when it appears safe. Do NOT judge a dependency from its name, scope, brand, or your own prior knowledge alone. Score 0 only when \`deepAnalysis\` and surrounding context support benign usage consistent with declared purpose.
- **\`code_quality\`**: this layer is problem-oriented. If there is no real issue, return an empty array and do NOT output safe or praise-only entries.

## Output format
Confidence
85% confidence
Finding
Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The skill exposes a general-purpose shell execution primitive via execSync on attacker-controlled input, with only superficial string sanitization that removes a few tag-like tokens but does not restrict commands or shell metacharacters. In a security-review skill, this is especially dangerous because reviewing untrusted skill content can influence tool usage, turning the scanner into a command-execution surface that can read sensitive files, access the network through shell utilities, or modify the local environment.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The documentation claims commands should be read-only and must not execute target files, but the implementation does not enforce any of those constraints before passing the full string to execSync. This creates a dangerous mismatch where callers may trust the safety guidance while an adversarial prompt or file content can still cause destructive commands, script execution, exfiltration, or environment tampering.

Unrestricted Tool Access

Medium
Category
Excessive Agency
Content
await runPrompt(
    agent,
    "Your previous response did not contain valid JSON. Output ONLY the final JSON block now, with no extra text. Do not call any tools. Do not add explanation before or after the JSON.",
    label
  );
Confidence
80% confidence
Finding
Skill grants unrestricted tool access without appropriate constraints. An agent with unfettered tool access can perform arbitrary actions including file modification, network requests, and code execution.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The manifest describes a security scanner for Claude Code Skill packages, which is primarily an analysis/review function. This file invokes the host's `which` command via `execFileSync` to enumerate installed tools, introducing subprocess execution and host environment inspection that are not obviously required by the stated purpose.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The function accumulates the full assistant text output and returns it for later retrieval, effectively capturing the complete model transcript. For a code file, this is a data-handling behavior that lacks any visible warning, comment about privacy implications, or user-facing disclosure beyond the minimal functional description.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
This code logs full tool invocation details, including raw bash commands and serialized tool arguments, which may expose sensitive user or system data in logs. While the behavior is implemented intentionally, the file contains no user-facing warning, confirmation, or disclosure about this potentially sensitive logging behavior.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The URL analysis routine performs outbound network requests to arbitrary supplied URLs, follows redirects, and captures response metadata and preview content without any user-facing disclosure at invocation time. This can leak analyzer IP/network metadata to third parties, trigger SSRF-style access to internal resources if attacker-controlled URLs are analyzed, and retrieve potentially sensitive content into the analysis output.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The bash tool enables subprocess execution without any runtime warning, confirmation, or hard enforcement of safe behavior, despite operating in the context of untrusted skill reviews. Even if intended for inspection, silent shell access materially increases the chance that malicious skill content or prompt injection causes sensitive local command execution before the user understands the risk.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"main": "index.mjs",
  "type": "module",
  "dependencies": {
    "@mariozechner/pi-agent-core": "^0.63.1",
    "dotenv": "^17.3.1"
  }
}
Confidence
86% confidence
Finding
The dependency is specified with a caret range, which allows newer compatible versions to be installed over time. This can introduce supply-chain risk because a later published version of the package or one of its transitive dependencies could change behavior or become compromised without the skill author explicitly reviewing and pinning that update.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"type": "module",
  "dependencies": {
    "@mariozechner/pi-agent-core": "^0.63.1",
    "dotenv": "^17.3.1"
  }
}
Confidence
86% confidence
Finding
The dotenv dependency is also defined with a caret range, so installs may resolve to newer releases than originally tested. In a security-review skill, even small supply-chain changes are meaningful because the tool may be trusted to evaluate other packages and could be a target for dependency compromise.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.exposed_secret_literal

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
src/tools.mjs:369

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
src/config.mjs:35