T09 · Insecure Skill Coding Practices
- Location
scripts/upload.js:433- Finding
Unrestricted URL Fetching Enables SSRF and Public Data Exfiltration
- Content
View full analysis
Vulnerability Details
File Location:
scripts/upload.js:433-457,scripts/upload.js:480-485,scripts/upload.js:517-524, andscripts/upload.js:572-578
Vulnerability Type: Server-Side Request Forgery (SSRF) with public disclosure
Risk Level: HighVulnerable code:
js function getBufferFromUrl(url) { return new Promise((resolve, reject) => { const parsed = new URL(url); const client = parsed.protocol === 'https:' ? https : http; client.get(url, { timeout: 60000 }, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { return getBufferFromUrl(res.headers.location).then(resolve).catch(reject); } const chunks = []; let size = 0; res.on('data', (chunk) => { size += chunk.length; if (size > MAX_SIZE) { res.destroy(); reject(new Error('File exceeds the 100 MB limit')); return; } chunks.push(chunk); }); res.on('end', () => resolve(Buffer.concat(chunks))); res.on('error', reject); }).on('error', reject); }); }js if (/^https?:\/\//i.test(input)) { try { buffer = await getBufferFromUrl(input); } catch (e) { console.error('Download failed:', e.message || e); process.exit(1); }The downloaded bytes are subsequently made publicly readable:
js await client.put(key, buffer, { timeout: 600000, headers: { 'content-type': getContentType(filename), 'x-oss-object-acl': 'public-read', }, });js const putParams = { Bucket: bucket, Region: region, Key: key, Body: buffer, ContentType: getContentType(filename), ACL: 'public-read', };Technical Analysis
The script accepts any user-supplied HTTP or HTTPS URL and requests it from the host running the skill. It does not resolve ...[truncated 2297 chars]
- Remediation
View remediation
Remediation Suggestions
- Disable remote URL ingestion by default unless it is essential to the skill.
- Prefer an explicit allowlist of trusted HTTPS hostnames.
- Resolve the hostname before connecting and reject every address in loopback, private, link-local, multicast, unspecified, documentation, carrier-grade NAT, and other reserved IPv4 and IPv6 ranges.
- Perform destination validation for every resolved address, not only the first result.
- Re-resolve and revalidate the destination after every redirect.
- Set a small redirect limit and resolve relative
Locationvalues withnew URL(location, currentUrl). - Reject protocol changes and allow only HTTPS where possible.
- Protect against DNS rebinding by connecting to a previously validated address while preserving the expected TLS hostname, or use a hardened outbound proxy.
- Accept only successful response statuses and enforce response-size limits using both
Content-Lengthand streaming byte counts. - Require explicit user confirmation before publishing remotely fetched content and consider private objects or short-lived signed URLs instead of
public-read. - Block cloud metadata destinations at both application and network/firewall layers.
