Back to skill

Security audit

aavegotchi-sprites-generator

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real Aavegotchi sprite generator, but it needs review because malformed input can write outside the chosen output folder and extreme GIF settings can exhaust resources.

Install only if you trust the inputs being rendered, run it in a constrained working directory, honor the lockfile, and avoid processing untrusted JSON until the ID path traversal and GIF scale limits are fixed.

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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/render-gotchi-sprite.mjs:324
Finding

Path Traversal Through an Unvalidated JSON-Supplied Gotchi ID

Content
View full analysis
({ trait_type: attr.trait_type, value: attr.trait_type === "Base Body" ? normalizeCollateral(attr.value) : attr.trait_type === "Eye Shape" ? normalizeEyeShape(attr.value) : attr.trait_type === "Eye Color" ? normalizeEyeColor(attr.value) : attr.trait_type.startsWith("Wearable") ? normalizeWearableName(attr.value) : attr.value })) : []; if (!attributes.some((attr) => attr.trait_type === "Eye Color")) { const eyeShape = attributes.find((attr) => attr.trait_type === "Eye Shape")?.value || ""; const fallbackEyeColor = defaultEyeColorForShape(eyeShape); if (fallbackEyeColor) { attributes.push({ trait_type: "Eye Color", value: fallbackEyeColor }); } } return { ...gotchi, collateral: normalizeCollateral(gotchi.collateral), attributes }; } ``` ```js function loadGotchi(args) { if (args.help) { printHelp(); process.exit(0); } if (args.input) { const inputPath = path.resolve(process.cwd(), requireString(args.input)); return normalizeGotchiPayload(JSON.parse(fs.readFileSync(inputPath, "utf8"))); } return buildGotchiFromFlags(args); } ``` ```js const gotchi = loadGotchi(args); const outputDir = path.resolve( process.cwd(), requireString(args["output-dir"], `./output/${slug}`) ); fs.mkdirSync(outputDir, { recursive: true }); const generatedPath = path.join(outputDir, `${gotchi.id}.png`); const rawSpritePath = path.join(outputDir, `${gotchi.id}.raw.png`); const finalSpritePath = ...[truncated 2874 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/render-gotchi-sprite.mjs:547
Finding

Unbounded GIF Scale Allows Memory and CPU Resource Exhaustion

Content
View full analysis
row >= 0 && row < maxRows); const gifWidth = frameWidth * scale; const gifHeight = frameHeight * scale; const delay = Math.max(1, Math.round(1000 / fps)); const gif = GIFEncoder(); let wroteFrame = false; for (const row of validRows) { const frameCount = ROW_FRAME_COUNTS[row] || Math.max(1, Math.floor((metadata.width || FRAME_WIDTH) / FRAME_WIDTH)); for (let col = 0; col < frameCount; col += 1) { const frame = source.clone().extract({ left: col * frameWidth, top: row * frameHeight, width: frameWidth, height: frameHeight }); const resized = scale > 1 ? frame.resize({ width: gifWidth, height: gifHeight, kernel: 'nearest' }) : frame; const { data, info } = await resized .ensureAlpha() .raw() .toBuffer({ resolveWithObject: true }); if (info.channels !== 4) { throw new Error(`Expected 4 channels for GIF frame, got ${info.channels}`); } const rgba = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); const palette = quantize(rgba, 256, { format: 'rgba4444', oneBitAlpha: true, clearAlpha: false }); const index = applyPalette(rgba, palette, 'rgba4444'); const transparentIndex = palette.findIndex((entry) => entry[3] === 0); gif.writeFrame(index, gifWidth, gifHeight, { palette, delay, repeat: 0, ...[truncated 2293 chars]
Remediation
View remediation
MAX_GIF_SCALE) { throw new Error(`GIF scale must be an integer from 1 to ${MAX_GIF_SCALE}`); } return parsed; } ``` 3. Enforce a maximum pixel budget before any resize: ```js const gifWidth = frameWidth * scale; const gifHeight = frameHeight * scale; const pixelsPerFrame = gifWidth * gifHeight; if (!Number.isSafeInteger(pixelsPerFrame) || pixelsPerFrame > MAX_PIXELS_PER_FRAME) { throw new Error("Requested GIF dimensions exceed the rendering limit"); } ``` 4. Apply a total-work limit based on frame count multiplied by pixels per frame. 5. Run rendering in an isolated worker or container with memory, CPU, execution-time, and output-size limits. 6. Add tests for zero, negative, nonnumeric, extremely large, and maximum-boundary scale values. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (7)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 125)May include surrounding context.

md
node ./scripts/render-gotchi-sprite.mjs \

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 133)May include surrounding context.

md
node ./scripts/render-gotchi-sprite.mjs \

Known Vulnerable Dependency: sharp==0.33.5 — 2 advisory(ies): GHSA-f88m-g3jw-g9cj (sharp inherited vulnerabilities in libvips: CVE-2026-33327, CVE-2026-33328, CVE-); GHSA-rgj7-g3m4-5g8c (sharp: Vulnerabilities in libheif: GHSA-g89c-p67h-r497 and GHSA-2jg2-4ch7-h545)

High
Category
Supply Chain
Confidence
92% confidence
Finding

The lockfile pins sharp to 0.33.5, and the supplied advisory metadata indicates known vulnerabilities in bundled/native image-processing dependencies such as libvips/libheif. This skill’s purpose is to generate sprites and GIFs from image/SVG-like assets, so it directly processes untrusted or semi-trusted image content in a parser-heavy native library, which raises the likelihood of denial of service, memory corruption, or potentially code execution depending on the underlying vulnerable codec path.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
85% confidence
Finding

The skill instructs use of an upstream package and wrapper scripts that imply code execution with likely network-capable dependencies, but it declares no explicit tool scope such as permissions or allowed-tools. That creates an authorization gap: an agent may permit broader execution or network access than intended, increasing the chance of supply-chain fetches, data exfiltration, or unexpected outbound requests during sprite generation.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The default prompt instructs the agent to invoke this skill for a wide range of loosely defined user requests such as any 'sprite', 'retro sprite', or 'battle sprite', even when the user does not name the skill. That creates overbroad routing and increases the chance the skill is triggered in contexts where another tool, clarification step, or no tool use would be more appropriate, which can lead to unintended execution paths or user-intent mismatches.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This code creates a temporary directory, deletes it recursively, and elsewhere writes or renames files in the user-supplied output directory. While the operations are part of sprite rendering, the script provides no explicit user-facing disclosure in help text, comments, or prompts that it will modify the filesystem and remove temporary directories.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 15)May include surrounding context.

json
"node": ">=18"
  },
  "dependencies": {
    "gifenc": "^1.0.3",
    "gotchi-generator": "1.1.3"
  }
}

Static analysis

No suspicious patterns detected.