T09 · Insecure Skill Coding Practices
- Location
scripts/crawl.mjs:109- Finding
Automatic Redirects Bypass Origin Restrictions and Permit SSRF
- Content
View full analysis
Vulnerability Details
File Location:
scripts/crawl.mjs:109-124,scripts/crawl.mjs:327-333,scripts/crawl.mjs:390-418
Vulnerability Type: Server-Side Request Forgery through unvalidated redirects
Risk Level: MediumComplete Code Snippet
js export async function fetchPage(url, timeoutMs, { signal, maxBodyBytes = MAX_BODY_BYTES, pauses } = {}) { const origin = new URL(url).origin; for (let attempt = 0; ; attempt++) { const wait = (pauses?.get(origin) ?? 0) - Date.now(); if (wait > 0) await sleep(wait, signal); const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); const onAbort = () => controller.abort(); if (signal?.aborted) controller.abort(); else signal?.addEventListener("abort", onAbort, { once: true }); try { const res = await fetch(url, { signal: controller.signal, redirect: "follow", headers: { "user-agent": UA, accept: "text/html,application/xhtml+xml,text/plain;q=0.9" }, });js const allowed = (href) => { try { const u = new URL(href); if (!opts.anySite && !origins.has(u.origin)) return false; if (opts.within && !u.pathname.startsWith(opts.within)) return false; return true; } catch { return false; } };js const visit = async ({ url, depth }) => { try { if (opts.robots) { const rules = await robotsFor(new URL(url).origin); if (!robotsAllows(rules, url)) { blocked++; return; } } const { bytes, contentType, finalUrl, truncated } = await fetchPage(url, timeoutMs, fetchOpts); if (truncated) truncatedPages.push(finalUrl); if (stopped) return; // Two links that redirect to the same page: search it once. const finalKey = normalizeUrl(finalUrl); if (fetched.has(finalKey)) return; fetched.add(finalKey); visited.add(finalKey); if (depth === 0) { try { origins.add(new URL(finalUrl).origin); ...[truncated 3555 chars]- Remediation
View remediation
Remediation Suggestions
- Set
redirect: "manual"and process redirects explicitly. - Before every request and every redirect hop:
- Resolve the destination hostname.
- Reject loopback, private, link-local, multicast, unspecified, and reserved IPv4 and IPv6 ranges.
- Validate every returned DNS address rather than only the hostname string.
- Revalidate the protocol, port, origin, and
--withinrestriction.
- Apply the same-origin policy to each redirect destination, not only to the original link.
- Do not add a redirected final origin to
originsunless it independently passes the complete destination policy and is consistent with the user's authorized target. - Limit the number of redirect hops and reject redirect loops.
- Mitigate DNS rebinding by binding validation to the address actually used for the connection, or by using a network layer that resolves, validates, and connects to the validated address atomically.
- Consider denying redirects to nonstandard ports unless the user explicitly authorizes them.
- Add regression tests covering:
- Public-to-loopback redirects.
- Public-to-private IPv4 and IPv6 redirects.
- Redirect chains where only a later hop enters a restricted range.
- DNS names that resolve to mixed public and private addresses.
- Start-page redirects that would otherwise add an internal origin to
origins.
- Set
