Back to skill

Security audit

Cartoon Pet Generator

Security checks for vulnerabilities and agentic risk

Overview

This pet-image skill has a coherent purpose, but its image conversion script can turn a user-controlled output filename into shell command execution.

Review before installing. The skill does not appear deceptive or persistent, but only use it with trusted, fixed output paths until the script replaces shell-string execSync calls with shell-free argument arrays and validates output filenames.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate_pet.js:222
Finding

OS Command Injection Through an Attacker-Controlled Output Path

Content
View full analysis

Vulnerability Details

File Location: scripts/generate_pet.js, lines 7, 222, 228, and 233
Vulnerability Type: OS command injection caused by unsafe shell command construction
Risk Level: High

Vulnerable Code

The output path is obtained directly from command-line arguments:

js
const args = process.argv.slice(2);
const petType = args[0] || 'dog';
const outputPath = args[1] || '/tmp/pet.png';

It is then used to derive another path and both values are interpolated into shell command strings:

js
const svg = svgGenerator(colors, size);
const svgPath = outputPath.replace('.png', '.svg');

// Write SVG
fs.writeFileSync(svgPath, svg);

// Convert to PNG
try {
  // Try rsvg-convert first
  execSync(`rsvg-convert "${svgPath}" -o "${outputPath}"`, { stdio: 'inherit' });
  console.log(`PNG created: ${outputPath}`);
} catch (e) {
  // Fall back to ImageMagick
  try {
    execSync(`convert "${svgPath}" "${outputPath}"`, { stdio: 'inherit' });
    console.log(`PNG created: ${outputPath}`);
  } catch (e2) {

Technical Analysis

outputPath is attacker-controlled command-line input. The application places it inside a JavaScript template literal and passes the resulting string to execSync. By default, execSync executes string commands through a system shell.

Wrapping the path in double quotes is not sufficient shell escaping. An attacker can supply embedded quotation marks to terminate the quoted argument and append shell operators and commands. Shell command substitutions may also be evaluated within double-quoted strings. The derived svgPath remains unsafe because it is generated from the same untrusted input using only a string replacement.

Both conversion attempts are independently vulnerable:

  • The primary rsvg-convert command interpolates svgPath and outputPath.
  • The ImageMagick fallback interpolates the same untrusted values and provides a second c ...[truncated 2012 chars]
Remediation
View remediation

Remediation Suggestions

  1. Replace shell-based execSync calls with execFileSync or spawnSync, passing every argument through an argument array:

    js
    const { execFileSync } = require('child_process');
    
    try {
      execFileSync('rsvg-convert', [svgPath, '-o', outputPath], {
        stdio: 'inherit'
      });
    } catch (error) {
      execFileSync('convert', [svgPath, outputPath], {
        stdio: 'inherit'
      });
    }
    

    This avoids shell parsing and ensures that filename characters are passed to the converter as data rather than executable shell syntax.

  2. Validate and normalize the output path before use:

    • Require the output filename to have a .png extension.
    • Reject null bytes and control characters.
    • Resolve the path with path.resolve.
    • If output must remain in a designated directory, verify that the resolved path is contained within that directory.
    • Consider generating the filename internally rather than accepting an arbitrary path.
  3. Derive the SVG path using the path module instead of unrestricted string replacement:

    js
    const path = require('path');
    const parsed = path.parse(outputPath);
    const svgPath = path.join(parsed.dir, `${parsed.name}.svg`);
    
  4. Run conversion utilities under a minimally privileged account with restricted filesystem and network access.

  5. Add regression tests using paths containing quotes, semicolons, command-substitution characters, whitespace, and traversal sequences. Tests should verify that these values are either rejected or passed as literal process arguments without shell interpretation.

  6. Avoid attempting to repair this issue with ad hoc shell escaping. A shell-free process invocation API is the preferred and more reliable control.

Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (2)

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

The manifest frames this as a code-based cartoon pet image generator, but the implementation shells out to rsvg-convert and convert via execSync. Spawning subprocesses is a sensitive capability that goes beyond pure SVG generation and is not obviously necessary from the high-level purpose alone, especially since the manifest emphasizes simple local generation rather than command execution.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

The example conversation uses a Chinese user utterance and presents it as the activation example, but the skill does not state that it is region-specific or that users may interact in other languages. Under the language/locale policy, forcing or implying a specific language without opt-in can be a natural-language policy issue.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/generate_pet.js:227