T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/pexels.ts:107
- Finding
- Unrestricted Pexels Download URL Enables SSRF and Arbitrary File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `scripts/pexels.ts:107-116`, invoked by `scripts/pexels.ts:181-191` **Vulnerability Type**: Server-Side Request Forgery and unrestricted file write **Risk Level**: Medium ### Vulnerable Code ```ts async function downloadFile(url: string, output: string): Promise<void> { const resp = await fetch(url, { headers: { "User-Agent": "PexelsCLI/0.1" }, }); if (!resp.ok) { console.error(`Download failed - HTTP ${resp.status}`); process.exit(1); } const buf = await resp.arrayBuffer(); await Bun.write(output, new Uint8Array(buf)); console.error(`Downloaded: ${output}`); } ``` ```ts async function download(flags: Record<string, string>) { if (!flags["--url"]) { console.error("Error: --url is required"); process.exit(1); } if (!flags["--output"]) { console.error("Error: --output is required"); process.exit(1); } await downloadFile(flags["--url"], flags["--output"]); } ``` ### Technical Analysis The `download` command passes a caller-controlled URL directly to `fetch`. It does not enforce HTTPS, restrict downloads to Pexels media hosts, resolve and reject non-public addresses, or validate redirect destinations. Consequently, the command can make HTTP requests to loopback addresses, private network services, link-local services, or cloud metadata endpoints. This network access exceeds the minimum privilege required to download media returned by Pexels. The caller also fully controls the output path. `Bun.write` can overwrite files writable by the current process, and the implementation does not restrict writes to a designated media directory or reject existing files. ### Attack Path 1. An attacker supplies a URL presented as a Pexels media resource. 2. The Agent invokes `pexels.ts download` with the attacker-controlled `--url`. 3. The script requests an internal target such as a loopback service, private network host, or metadata endpoint. 4. The response is writt ...[truncated 811 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Parse the supplied URL with `new URL()` and permit only `https:`. - Allowlist the exact Pexels API and documented Pexels media/CDN hostnames required by the Skill. - Resolve hostnames before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved IPv4 and IPv6 ranges. - Disable automatic redirects or validate the protocol, hostname, and resolved address of every redirect target. - Prefer downloading URLs taken directly from a validated Pexels API response rather than accepting arbitrary URLs. - Restrict output to a dedicated download directory after canonicalizing the path. - Reject path traversal, symbolic-link escapes, and existing files unless overwrite is explicitly requested. - Apply response-size and timeout limits to prevent memory and disk exhaustion. ]]>
