{"record":{"id":"02a4c68e6f3a63a1","repo":"ToolJet/ToolJet","slug":"redirect-blocked-by-ssrf-protection","errorCode":null,"errorMessage":"Redirect blocked by SSRF protection","messagePattern":"Redirect blocked by SSRF protection","errorType":"exception","errorClass":"QueryError","httpStatus":null,"severity":"critical","filePath":"marketplace/plugins/common/lib/ssrf-protection.ts","lineNumber":710,"sourceCode":"\n  const ssrfOptions: any = {\n    ...existingOptions,\n    // Custom DNS lookup function to validate resolved IPs\n    dnsLookup: createSSRFSafeLookup(config),\n  };\n\n  // Redirect validation hook - prevents SSRF bypass via open redirects\n  // This validates redirect URLs using the same SSRF rules as the initial request,\n  // blocking attacks where an allowed domain redirects to a private IP/endpoint\n  const beforeRedirectHook = async (options: any, response: any) => {\n    // Validate redirect URL\n    const redirectUrl = response.headers.location;\n    if (redirectUrl) {\n      try {\n        // Validate the redirect URL\n        await validateUrlForSSRF(redirectUrl, config);\n      } catch (error) {\n        throw new QueryError(\n          'Redirect blocked by SSRF protection',\n          `Redirect to \"${redirectUrl}\" was blocked: ${error.message}`,\n          { redirectUrl, originalError: error }\n        );\n      }\n    }\n  };\n\n  // Properly merge hooks\n  if (existingOptions?.hooks) {\n    ssrfOptions.hooks = {\n      ...existingOptions.hooks,\n      beforeRedirect: [\n        ...(existingOptions.hooks.beforeRedirect || []),\n        beforeRedirectHook\n      ]\n    };\n  } else {","sourceCodeStart":692,"sourceCodeEnd":728,"githubUrl":"https://github.com/ToolJet/ToolJet/blob/20602a8e101f2e59686c9afde0d1402aac2c8871/marketplace/plugins/common/lib/ssrf-protection.ts#L692-L728","documentation":"Thrown by the beforeRedirect hook attached to outgoing requests. When the server returns a redirect, the Location header is validated through validateUrlForSSRF; if it fails, the redirect is blocked and the original validation error is wrapped with context. This closes the open-redirect-to-SSRF bypass where an allowed domain redirects to a private/metadata target.","triggerScenarios":"An allowed host returns a 3xx with a Location pointing at a private IP, metadata endpoint, blocked scheme, or localhost; the redirect chain crosses into a blocked hostname; the Location header is itself malformed.","commonSituations":"Legitimate APIs that redirect to S3-style signed URLs (rarely blocked) versus a crafted API that redirects to 169.254.169.254; CDNs that redirect to internal addresses on misconfigured origins; login flows that redirect to an internal IdP.","solutions":["Inspect error.data.redirectUrl and error.data.originalError to see which SSRF rule the redirect violated.","If the redirect target is legitimate on a self-hosted instance, add it to SSRF_ALLOWED_HOSTNAMES.","Disable redirect-following for the request (followRedirects:false) and resolve the final URL yourself if you need explicit control.","Audit the upstream API: an unexpected redirect to a private target may indicate a compromised or misconfigured service."],"exampleFix":"// before — got(url, { followRedirect: true }) where upstream redirects to http://169.254.169.254/...\n// after  — got(url, { followRedirect: false }) then explicitly fetch the validated final URL, or allowlist the legitimate redirect target","handlingStrategy":"validation","validationCode":"function shouldFollowRedirect(currentUrl: string, nextUrl: string): boolean {\n  // caller-controlled: validate nextUrl against SSRF policy (or the allowlist) before following\n  try { /* sync-validate nextUrl */; return true; } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":"try { await got(url, ssrfOptions); }  // ssrfOptions wires the beforeRedirect hook\ncatch (e) {\n  if (e instanceof QueryError && e.message === 'Redirect blocked by SSRF protection') {\n    // inspect e.data.redirectUrl and e.data.originalError; either allowlist the target or disable followRedirect for this call\n  }\n  throw e;\n}","preventionTips":["Where possible, set followRedirect:false and resolve the final URL explicitly so you control validation.","Allowlist only known-good redirect targets (e.g., a CDN's signed-URL host).","Audit upstream APIs that redirect unexpectedly — they may be misconfigured or compromised."],"tags":["ssrf","security","redirect","open-redirect","typescript"],"backgroundTag":null,"analyzedSha":"20602a8e101f2e59686c9afde0d1402aac2c8871","analyzedAt":"2026-08-13T05:58:54.221Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}