T09 · Insecure Skill Coding Practices
- Location
ronin-worker.mjs:167- Finding
Unrestricted Marketplace Attachment URLs Enable Blind SSRF
- Content
View full analysis
Vulnerability Details
File Location:
ronin-worker.mjs, lines 167–175 and 190–204
Vulnerability Type: Server-Side Request Forgery through untrusted attachment URLs
Risk Level: MediumVulnerable Code
js async function fetchVerified(ref, path) { const url = /^https?:/.test(ref.url) ? ref.url : HUB + ref.url; const r = await fetch(url); if (!r.ok) throw new Error(`could not fetch ${url}: ${r.status}`); const bytes = Buffer.from(await r.arrayBuffer()); const got = createHash('sha256').update(bytes).digest('hex'); if (got !== ref.hash) throw new Error(`hash mismatch on ${url}: signed ${ref.hash}, got ${got}`); writeFileSync(path, bytes); return bytes; }The function is reached for both skill bundles and input attachments:
js if (t.attachments.skill.kind === 'bundle') { const bytes = await fetchVerified(t.attachments.skill, join(tmp, 'skill.zip')); for (const [name, data] of readBundle(bytes).files) { const dest = join(vars.skill_dir, ...name.split('/')); mkdirSync(dirname(dest), { recursive: true }); writeFileSync(dest, data); } } else await fetchVerified(t.attachments.skill, join(vars.skill_dir, 'SKILL.md')); if (t.attachments?.inputs?.length) { vars.inputs_dir = join(tmp, 'inputs'); mkdirSync(vars.inputs_dir, { recursive: true }); for (const [i, f] of t.attachments.inputs.entries()) await fetchVerified(f, join(vars.inputs_dir, (f.name ?? `input-${i + 1}`).replace(/[^\w.-]/g, '_'))); }Technical Analysis
The worker accepts an absolute HTTP or HTTPS URL from an accepted marketplace task and passes it directly to
fetch. It does not restrict the URL to the configured Ronin hub, validate the resolved IP address, reject private or loopback networks, or validate redirect destinations.The SHA-256 comparison verifies the response body only after the network request has occurred. It therefore protects artifact integrity but does not prevent SSRF. A failed hash check also does not un ...[truncated 1661 chars]
- Remediation
View remediation
Remediation Suggestions
- Permit relative artifact paths under the configured Ronin hub by default.
- If external artifact hosting is required, enforce an explicit origin allowlist.
- Parse URLs with the platform URL API and allow only expected protocols, ports, and origins.
- Resolve hostnames before connecting and reject loopback, link-local, private, multicast, unspecified, and cloud metadata address ranges for both IPv4 and IPv6.
- Disable redirects or repeat full destination validation for every redirect hop.
- Consider downloading attachments through a trusted artifact service rather than allowing workers to contact arbitrary origins.
- Apply response-size and request-time limits in addition to the existing post-download ZIP limits.
