Back to skill

Security audit

Sideload Avatar Generator

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims, but its generator script trusts remote URLs and output filenames too broadly, which creates review-worthy local network and file-write risk.

Install only if you are comfortable sending prompts, image inputs, and an x402 payment token to Sideload.gg. Avoid sensitive images, prefer --no-download when you only need URLs, and do not allow untrusted text to choose --output values until filename and URL validation are added.

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

Warning
Location
scripts/generate.js:196
Finding
Unvalidated Server-Controlled URLs Enable Client-Side SSRF and Unbounded Downloads<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.js`, lines 196–207 and 236–250 **Vulnerability Type**: Server-Side Request Forgery from the client environment, unrestricted redirects, and unbounded resource consumption **Risk Level**: Medium ### Vulnerable Code ```javascript const fullStatusUrl = data.statusUrl?.startsWith('http') ? data.statusUrl : `https://sideload.gg${data.statusUrl}`; let result = null; for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt++) { await new Promise(r => setTimeout(r, POLL_INTERVAL)); const statusRes = await fetch(fullStatusUrl); const statusData = await statusRes.json(); ``` The same trust issue affects result downloads: ```javascript const downloads = [ { url: result.glbUrl, ext: '.glb', label: 'GLB' }, { url: result.vrmUrl, ext: '.vrm', label: 'VRM' }, { url: result.processedImageUrl, ext: '.png', label: 'PNG' }, ]; console.log('📥 Downloading...'); for (const { url, ext, label } of downloads) { if (!url) continue; try { const res = await fetch(url); if (res.ok) { const buffer = Buffer.from(await res.arrayBuffer()); const filePath = join(OUTPUT_DIR, `${baseName}${ext}`); writeFileSync(filePath, buffer); console.log(` ✅ ${label}: ${filePath}`); } } catch (e) { console.log(` ⚠️ ${label}: ${e.message}`); } } ``` ### Technical Analysis The generation API controls `statusUrl`, `glbUrl`, `vrmUrl`, and `processedImageUrl`. The script performs requests to these values without validating: - URL scheme - Destination hostname - Destination port - Resolved IP address - Redirect destinations - Response content type - Response or download size An absolute `statusUrl` is accepted whenever it begins with `http`, including plaintext HTTP and arbitrary external or internal hosts. Result URLs are accepted with no validation at all. Node.js `fetch` also follows redirects by default, so validating only an initial URL would not be sufficie ...[truncated 2277 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require HTTPS for every status and asset URL. 2. Maintain separate allowlists for the status endpoint and documented asset hosts. 3. Parse URLs with `new URL()` and reject embedded credentials, fragments, unexpected ports, and malformed values. 4. Resolve destination hostnames and reject loopback, private, link-local, multicast, unspecified, and reserved IPv4 and IPv6 ranges. 5. Disable automatic redirects or validate every redirect destination against the same rules. 6. Prefer deriving the status URL locally from the validated `jobId` rather than trusting `data.statusUrl`. 7. Add request timeouts with `AbortController`. 8. Enforce maximum `Content-Length` and streamed-byte limits before writing downloads. 9. Stream assets directly to files instead of loading the entire response into memory. 10. Validate expected content types and optionally verify file signatures for PNG, GLB, and VRM files. 11. Apply a total download quota and remove partial files when a request fails. Example status URL construction: ```javascript const jobIdPattern = /^avt-[A-Za-z0-9-]+$/; if (!jobIdPattern.test(jobId)) { throw new Error('Invalid job ID'); } const fullStatusUrl = `https://sideload.gg/api/agent/generate/${encodeURIComponent(jobId)}/status`; ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate.js:228
Finding
Unsanitized Output Name Permits Path Traversal and File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.js`, lines 228–250 **Vulnerability Type**: Path traversal and unsafe file overwrite **Risk Level**: Medium ### Vulnerable Code ```javascript const timestamp = new Date().toISOString().slice(0, 19).replace(/[:-]/g, ''); const baseName = outputName || `avatar_${timestamp}`; const downloads = [ { url: result.glbUrl, ext: '.glb', label: 'GLB' }, { url: result.vrmUrl, ext: '.vrm', label: 'VRM' }, { url: result.processedImageUrl, ext: '.png', label: 'PNG' }, ]; console.log('📥 Downloading...'); for (const { url, ext, label } of downloads) { if (!url) continue; try { const res = await fetch(url); if (res.ok) { const buffer = Buffer.from(await res.arrayBuffer()); const filePath = join(OUTPUT_DIR, `${baseName}${ext}`); writeFileSync(filePath, buffer); console.log(` ✅ ${label}: ${filePath}`); } else { console.log(` ⚠️ ${label}: download failed (${res.status})`); } } catch (e) { console.log(` ⚠️ ${label}: ${e.message}`); } } ``` ### Technical Analysis The `--output` command-line value is assigned directly to `outputName` and then used as part of a filesystem path. There is no validation preventing: - Absolute paths - `..` traversal components - Forward or backward path separators - Platform-specific path syntax - Names resolving outside the intended `output` directory `path.join()` normalizes traversal components but does not confine the result to `OUTPUT_DIR`. Therefore, a value such as `../../existing/path/avatar` can resolve outside the intended directory. `writeFileSync()` uses overwrite behavior by default. If a resolved target already exists and is writable, it is replaced without confirmation. The script appends `.glb`, `.vrm`, or `.png`, so the direct overwrite scope is limited to paths ending in those extensions, but those files may still be valuable project or user assets. This risk is particularly relevant when ...[truncated 1453 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat `--output` strictly as a filename stem, not as a path. 2. Reject absolute paths, `..`, forward slashes, backslashes, null bytes, and platform-specific separators. 3. Apply a conservative allowlist, such as letters, digits, underscores, and hyphens. 4. Resolve the final destination and verify that it remains directly beneath `OUTPUT_DIR`. 5. Use exclusive file creation to prevent silent overwrite unless the user explicitly requests replacement. 6. Set a reasonable maximum filename length. 7. Consider generating server-independent random filenames and using the supplied name only as sanitized display metadata. Example hardening: ```javascript const safeNamePattern = /^[A-Za-z0-9_-]{1,80}$/; if (outputName && !safeNamePattern.test(outputName)) { throw new Error( 'Invalid output name: use only letters, digits, underscores, and hyphens' ); } const baseName = outputName || `avatar_${timestamp}`; const outputRoot = resolve(OUTPUT_DIR); const filePath = resolve(outputRoot, `${baseName}${ext}`); if (dirname(filePath) !== outputRoot) { throw new Error('Output path escapes the output directory'); } writeFileSync(filePath, buffer, { flag: 'wx' }); ``` If overwrite support is required, expose it through a separate explicit option and clearly warn before replacing an existing file. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (9)

Ae1

High
Category
analysis-evasion
Content
node scripts/generate.js --probe
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/generate.js --probe
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/generate.js --probe
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/generate.js --probe
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/generate.js --probe
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/generate.js --probe
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Missing User Warnings

Medium
Confidence
93% confidence
Finding
This markdown file documents an image-based generation flow using an external URL and later states that images must be publicly accessible. That behavior can expose user-provided images to third-party processing and public access, but the document provides no user warning about privacy or data exposure.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill documents and enables outbound network access to Sideload.gg but does not declare any explicit tool scope or permissions boundary. That omission weakens user and platform visibility into what the skill can contact, making unintended or unauthorized remote requests easier to conceal and increasing the risk of data exfiltration or misuse if the implementation changes.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill asks users to submit prompts, image URLs, or local image files to a third-party generation service, and it may download generated artifacts locally, but the description lacks a clear privacy and data-handling warning. Users may unknowingly transmit sensitive images or prompts off-platform and persist remote content onto their machine, creating confidentiality and supply-chain risk in a context that processes user-provided inputs and remote files.

Static analysis

No suspicious patterns detected.