T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/html2md.js:77
- Finding
- Unrestricted URL Fetching Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/html2md.js:77-109`, with the user-controlled URL reaching the function at `scripts/html2md.js:321-324` **Vulnerability Type**: Server-Side Request Forgery (SSRF) **Risk Level**: High ### Vulnerable Code ```js async function fetchHtml(url) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), 15000); let response; try { response = await fetch(url, { signal: controller.signal, redirect: 'follow', headers: { 'User-Agent': 'html2md/1.0 (agent-friendly HTML converter; +https://github.com/openclaw/html2md)', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.5', }, }); } catch (err) { if (err.name === 'AbortError') die(`Timeout: request exceeded 15s for ${url}`); const cause = err.cause?.message || err.message; die(`Network error fetching ${url}: ${cause}`); } finally { clearTimeout(timer); } if (!response.ok) die(`HTTP ${response.status} ${response.statusText} — ${url}`); const ct = response.headers.get('content-type') || ''; if (!ct.includes('html') && !ct.includes('xml') && !ct.includes('text')) { die(`Non-HTML content type: ${ct} — use a different tool for binary content`); } return { html: await response.text(), finalUrl: response.url }; } ``` The untrusted URL reaches this function directly: ```js } else if (url) { const result = await fetchHtml(url); html = result.html; pageUrl = result.finalUrl; } ``` ### Technical Analysis The CLI fetches a caller-provided URL without validating its scheme, hostname, resolved IP addresses, port, or network destination. It also enables automatic redirects through `redirect: 'follow'` without validating each redirect target. This behavior is necessary at a basic level because URL retrieval is part of the declared HTML conversion functionality. However, ...[truncated 1962 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Permit only explicitly supported schemes, normally `https:` and optionally `http:`. 2. Reject URLs containing embedded credentials or otherwise unexpected URL components. 3. Resolve the hostname before connecting and reject every address in prohibited ranges, including: - IPv4 and IPv6 loopback. - RFC1918 and IPv6 unique-local addresses. - Link-local addresses. - Multicast, unspecified, reserved, and documentation ranges. - Known cloud metadata destinations. 4. Prevent DNS rebinding by ensuring the validated address is the address used for the connection, or enforce destination controls at the network layer. 5. Replace automatic redirect following with manual redirect processing. Parse, resolve, and validate every redirect target before issuing the next request. 6. Apply an explicit destination allowlist when the expected set of websites is known. 7. Run the Skill in a sandbox whose egress policy denies private networks and metadata services. 8. Retain the timeout, and additionally impose response-size and redirect-count limits to reduce resource-exhaustion risks. 9. Document that untrusted users must not be allowed to choose arbitrary destinations unless these controls are enabled. ]]>
