T09 · Insecure Skill Coding Practices
- Location
- scripts/research.mjs:194
- Finding
- Opt-In Web Research Allows Server-Side Request Forgery to Arbitrary HTTPS Destinations<![CDATA[ ## Vulnerability Details **File Location**: `scripts/research.mjs:75-88, 194-202`; `scripts/fetch.mjs:58-62, 78-102` **Vulnerability Type**: Server-Side Request Forgery (SSRF) caused by insufficient destination validation **Risk Level**: Medium ### Vulnerable Code `scripts/research.mjs:75-88` accepts HTTPS links extracted from DuckDuckGo results without validating the destination: ```js async function ddgSearch(query, fetcher) { const url = `https://duckduckgo.com/html/?q=${encodeURIComponent(query)}`; try { const r = await fetcher.get(url); if (r.outcome !== "OK") return []; const links = []; const re = /<a[^>]*class="result__a"[^>]*href="([^"]+)"/g; let m; while ((m = re.exec(r.body)) !== null && links.length < 3) { const u = m[1].startsWith("//duckduckgo.com/l/?") ? decodeURIComponent((m[1].match(/uddg=([^&]+)/) || [])[1] || "") : m[1]; if (u && /^https?:/.test(u)) links.push(u); } return links; } catch { return []; } } ``` `scripts/research.mjs:194-202` sends requests to those untrusted result URLs: ```js const links = await ddgSearch(query, web); for (const link of links.slice(0, 2)) { try { const w = await web.get(link); if (w.outcome !== "OK") { log("web", "blocked", link, w.reason); continue; } r = await extractSpec(w.body.slice(0, 8000), "extract_spec_web"); log("web", r.confidence >= CONFIDENCE_THRESHOLD ? "ok" : "insufficient", link, `conf=${r.confidence}`); if (r.confidence >= CONFIDENCE_THRESHOLD) { return { ...r, spec_status: "ok", spec_source: "web" }; } ``` `scripts/fetch.mjs:58-62` validates only the URL scheme: ```js function hostOf(url) { const u = new URL(url); if (u.protocol !== "https:") throw new UrlNotAllowed(`refusing non-HTTPS URL: ${url}`); return u.hostname.toLowerCase(); } ``` `scripts/fetch.mjs:78-102` follows redirects without applying private-address or host-allowlist checks: ```js async function rawGet(url) ...[truncated 3555 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Apply a shared SSRF guard to every web-research request and every redirect: - Require HTTPS. - Reject embedded credentials and malformed hostnames. - Resolve all A and AAAA records. - Reject the request if any resolved address is loopback, private, link-local, CGNAT, multicast, reserved, or unspecified. 2. Revalidate each redirect target before issuing the next request. Do not treat scheme validation as destination validation. 3. Prefer a narrowly defined host policy: - Allow `duckduckgo.com` only for the search request. - Permit producer domains only after explicit validation or operator approval. - Consider returning external links for separate review instead of fetching arbitrary search results automatically. 4. Mitigate DNS rebinding by using a connection mechanism that binds the HTTP request to the validated address while preserving TLS hostname verification. 5. Reject redirects from a public hostname to an IP literal or non-public destination. 6. Introduce response limits for HTML pages: - Check `Content-Length` when available. - Read the body incrementally. - Cancel the stream after a conservative byte limit. - Enforce connection and total-request timeouts. 7. Add regression tests covering direct and redirected requests to IPv4, IPv6, IPv4-mapped IPv6, loopback, private, link-local, and cloud metadata ranges. 8. Correct the security documentation so it does not claim that redirects are securely revalidated until this control is implemented. ]]>
