{"record":{"id":"d8a3d581d0bd8e06","repo":"mastra-ai/mastra","slug":"redirect-target-must-use-http-or-https","errorCode":null,"errorMessage":"Redirect target must use HTTP or HTTPS.","messagePattern":"Redirect target must use HTTP or HTTPS\\.","errorType":"exception","errorClass":"WebFetchError","httpStatus":null,"severity":"error","filePath":"packages/core/src/tools/builtin/web-fetch.ts","lineNumber":223,"sourceCode":"          accept: 'text/html,text/plain,application/json,application/xml;q=0.9,*/*;q=0.8',\n        },\n        lookup: createLookup(),\n        timeout: TIMEOUT_MS,\n      },\n      response => {\n        void (async () => {\n          const location = response.headers.location;\n\n          if (location && response.statusCode && response.statusCode >= 300 && response.statusCode < 400) {\n            response.resume();\n\n            if (redirectsRemaining <= 0) {\n              throw new WebFetchError(`Too many redirects. Maximum is ${MAX_REDIRECTS}.`);\n            }\n\n            const nextUrl = parseHttpUrl(new URL(location, url).toString());\n            if (!nextUrl) {\n              throw new WebFetchError('Redirect target must use HTTP or HTTPS.');\n            }\n\n            resolve(await requestUrl(nextUrl, redirectsRemaining - 1));\n            return;\n          }\n\n          const { content, truncated } = await readBody(response);\n\n          resolve({\n            content,\n            truncated,\n            status: response.statusCode,\n            statusText: response.statusMessage,\n            contentType: Array.isArray(response.headers['content-type'])\n              ? response.headers['content-type'][0]\n              : (response.headers['content-type'] ?? null),\n            url: url.toString(),\n            ok: response.statusCode ? response.statusCode >= 200 && response.statusCode < 300 : false,","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/tools/builtin/web-fetch.ts#L205-L241","documentation":"When a 3xx response carries a Location header, requestUrl builds the absolute redirect target with new URL(location, url) and re-validates it through parseHttpUrl, which only accepts http: and https: schemes. If the redirect target is not HTTP(S) — e.g. a Location pointing to ftp:, file:, data:, or a malformed relative reference that yields a non-HTTP URL — the tool throws a WebFetchError instead of following it, blocking protocol-redirection attacks.","triggerScenarios":"A server responding 3xx with a Location header whose scheme is not http/https (ftp:, file:, data:, javascript:), or a Location that cannot be parsed into an http(s) URL (e.g. an invalid relative reference or a URL with a disallowed scheme after base resolution).","commonSituations":"Malicious or misconfigured endpoints redirecting to non-HTTP schemes; legacy servers redirecting to ftp:// mirrors; attacker-controlled URLs in agent input attempting scheme-redirection SSRF or local-file reads.","solutions":["Inspect the response chain (curl -IL <url>) to see the non-HTTP Location value; if it's your server, fix the redirect to use https://.","Fetch the intended HTTP(S) target directly instead of following the redirecting entry URL.","Treat this on attacker-influenced URLs as malicious input: reject or sanitize the URL before passing it to webFetch.","If a legacy ftp mirror is the real target, retrieve it outside the web-fetch tool (the tool is HTTP-only by design)."],"exampleFix":"// before\nawait webFetchTool.execute({ context: { url: 'https://legacy.example.com/downloads' } }); // Location: ftp://mirror...\n\n// after: fetch an HTTP(S) endpoint directly\nawait webFetchTool.execute({ context: { url: 'https://mirror.example.com/downloads' } });","handlingStrategy":"try-catch","validationCode":"// Only allow http(s) URLs, and check redirect targets the server might return\nfunction isHttpUrl(raw: string): boolean {\n  try {\n    const u = new URL(raw);\n    return u.protocol === 'http:' || u.protocol === 'https:';\n  } catch { return false; }\n}","typeGuard":"function isHttpScheme(u: URL): boolean {\n  return u.protocol === 'http:' || u.protocol === 'https:';\n}","tryCatchPattern":"try {\n  await webFetchTool.execute({ context: { url } });\n} catch (err) {\n  if (err instanceof Error && err.message.includes('Redirect target must use HTTP or HTTPS')) {\n    console.warn(`redirect to non-HTTP scheme from ${url}; refusing`);\n  } else throw err;\n}","preventionTips":["Pre-screen URLs from untrusted/agent-generated input for scheme safety.","Treat non-HTTP redirect targets as a sign of a malicious or broken endpoint.","Fix servers that redirect to ftp:/file: mirrors; expose an https endpoint instead.","Log the offending URL so users can see which host emitted the bad Location header."],"tags":["network","http","redirects","security","web-fetch"],"backgroundTag":"invalid-redirect-target","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}