T09 · Insecure Skill Coding Practices
Error
- Location
- index.js:221
- Finding
- Server-Side Request Forgery and Domain-Policy Redirect Bypass<![CDATA[ ## Vulnerability Details **File Location**: `index.js:30-36`, `index.js:82-89`, and `index.js:221-226` **Vulnerability Type**: Server-Side Request Forgery (SSRF) with redirect-based policy bypass **Risk Level**: High ### Vulnerable Code ```js function domainAllowed(hostname) { const host = String(hostname || '').toLowerCase(); if (blocklist.has(host)) return false; // precedence: blocklist > allowlist > default if (allowlist.size > 0) return allowlist.has(host); return true; } ``` ```js async function fetchWithRetry(url, options, retries, debug) { let lastErr; for (let i = 0; i <= retries; i++) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), options.timeoutMs); try { const res = await _fetch(url, { headers: options.headers, signal: controller.signal, redirect: 'follow' }); clearTimeout(timer); return res; ``` ```js try { const u = new URL(url); if (!domainAllowed(u.hostname)) throw new Error(`Domain blocked by policy: ${u.hostname}`); const markdownEnabled = !DISABLE_MARKDOWN_ENV && options.markdown !== false; // env disable has highest priority const timeoutMs = Number(options.timeout || DEFAULT_TIMEOUT_MS); ``` ### Technical Analysis The command accepts a caller-controlled URL and sends a request using the runtime's network privileges. When no allowlist is configured, `domainAllowed()` permits every hostname except exact blocklist entries. It does not reject loopback, private, link-local, multicast, reserved, or cloud metadata addresses. Furthermore, the policy check is performed only against the hostname in the original URL. The request is then issued with `redirect: 'follow'`, allowing the HTTP client to follow redirects automatically without validating each redirect destination. A public or allowlisted host can therefore redirect the request to an internal or explicitly blocklisted destination. Exact hostname comparison is also insufficient to ...[truncated 2316 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Restrict accepted schemes to `https:` and, only where explicitly required, `http:`. Reject all other URL schemes before making a request. 2. Prefer a default-deny domain policy. Require an explicit allowlist for deployments where untrusted users can influence the URL. 3. Resolve the hostname before every connection and reject every address in loopback, private, link-local, multicast, unspecified, reserved, and cloud-metadata ranges for both IPv4 and IPv6. 4. Disable automatic redirect following by using `redirect: 'manual'`. 5. For each redirect: - Resolve the `Location` header relative to the current URL. - Reapply scheme, hostname, port, allowlist, blocklist, and resolved-address checks. - Enforce a small maximum redirect count. - Reject redirects to a less trusted network zone. 6. Ensure the validated IP address is the address actually used for the connection, or use a trusted outbound proxy that enforces destination policy, to mitigate DNS rebinding and time-of-check/time-of-use inconsistencies. 7. Block known metadata destinations explicitly, including link-local metadata addresses, as defense in depth. 8. Consider restricting destination ports to standard web ports or a deployment-specific allowlist. 9. Add automated tests covering direct loopback access, private IPv4 and IPv6 ranges, DNS names resolving to private addresses, public-to-private redirects, blocklisted redirect targets, redirect loops, and DNS rebinding scenarios. 10. Independently enforce a streaming response-size limit and abort oversized downloads. The current `maxBytes` option is applied only after the entire response has been buffered and therefore does not prevent memory or bandwidth exhaustion. ]]>
