T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/lib/providers.mjs:24
- Finding
- Unrestricted Remote Image Fetching Enables SSRF and Data Exfiltration<![CDATA[ ## Vulnerability Details **File Location**: `scripts/lib/providers.mjs:24-43`, with provider call sites at `scripts/lib/providers.mjs:147-151` and `scripts/lib/providers.mjs:201-209` **Vulnerability Type**: Server-Side Request Forgery and unintended external disclosure **Risk Level**: High ### Vulnerable Code ```js const isUrl = (s) => /^https?:\/\//i.test(s) const MIME = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.webp': 'image/webp', '.gif': 'image/gif', } const mimeOf = (p) => MIME[path.extname(p).toLowerCase()] || 'image/jpeg' async function asBase64(p) { if (isUrl(p)) { const r = await fetch(p) if (!r.ok) throw new Error(`拉取参考图失败 ${r.status}: ${p}`) return Buffer.from(await r.arrayBuffer()).toString('base64') } return (await readFile(p)).toString('base64') } const asDataUri = async (p) => isUrl(p) ? p : `data:${mimeOf(p)};base64,${await asBase64(p)}` ``` The OpenAI provider independently fetches remote image URLs: ```js for (const p of req.images) { const buf = isUrl(p) ? Buffer.from(await (await fetch(p)).arrayBuffer()) : await readFile(p) fd.append('image[]', new Blob([buf], { type: mimeOf(p) }), path.basename(p)) } ``` The Gemini provider fetches and forwards the retrieved data: ```js const key = env.GEMINI_API_KEY || env.GOOGLE_API_KEY const parts = [{ text: req.prompt }] for (const p of req.images || []) { parts.push({ inline_data: { mime_type: mimeOf(p), data: await asBase64(p) } }) } const j = await postJson( `https://generativelanguage.googleapis.com/v1beta/models/${gemini.model()}:generateContent`, { contents: [{ parts }] }, { 'x-goog-api-key': key }, req.timeoutMs, ) ``` ### Technical Analysis The `--images` interface accepts any string beginning with `http://` or `https://` and passes it directly to `fetch()`. The implementation does not: - Restrict remote images to trusted or user-approved hosts. - Resolve and reject loopback, private, link-local, or r ...[truncated 2963 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Reject remote URLs by default and require an explicit option such as `--allow-remote-images`. 2. Prefer downloading only from a narrowly defined allowlist of trusted asset hosts. 3. Before connecting, resolve every hostname and reject all loopback, private, link-local, multicast, unspecified, and reserved IPv4 and IPv6 ranges. 4. Disable redirects or validate the resolved destination again after every redirect. 5. Explicitly block well-known metadata endpoints, including link-local metadata addresses and provider-specific metadata hostnames. 6. Permit only HTTPS unless a documented local workflow explicitly requires HTTP. 7. Enforce strict connect, response, and total-operation timeouts. 8. Stream downloads with a conservative maximum byte limit instead of buffering unbounded responses with `arrayBuffer()`. 9. Verify both the response `Content-Type` and the downloaded file's magic bytes against an allowlist of supported image formats. 10. Require user confirmation before uploading face images, product images, or remotely retrieved content to a third-party provider. 11. Document the destination provider and data-retention implications before transmission. 12. Add automated tests for loopback URLs, private IPv4 and IPv6 ranges, DNS rebinding, redirects to private addresses, metadata endpoints, non-image responses, and oversized files. ]]>
