ToolJet/ToolJet · critical · QueryError

Redirect blocked by SSRF protection

Error message

Redirect blocked by SSRF protection

What it means

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.

Source

Thrown at marketplace/plugins/common/lib/ssrf-protection.ts:710

  const ssrfOptions: any = {
    ...existingOptions,
    // Custom DNS lookup function to validate resolved IPs
    dnsLookup: createSSRFSafeLookup(config),
  };

  // Redirect validation hook - prevents SSRF bypass via open redirects
  // This validates redirect URLs using the same SSRF rules as the initial request,
  // blocking attacks where an allowed domain redirects to a private IP/endpoint
  const beforeRedirectHook = async (options: any, response: any) => {
    // Validate redirect URL
    const redirectUrl = response.headers.location;
    if (redirectUrl) {
      try {
        // Validate the redirect URL
        await validateUrlForSSRF(redirectUrl, config);
      } catch (error) {
        throw new QueryError(
          'Redirect blocked by SSRF protection',
          `Redirect to "${redirectUrl}" was blocked: ${error.message}`,
          { redirectUrl, originalError: error }
        );
      }
    }
  };

  // Properly merge hooks
  if (existingOptions?.hooks) {
    ssrfOptions.hooks = {
      ...existingOptions.hooks,
      beforeRedirect: [
        ...(existingOptions.hooks.beforeRedirect || []),
        beforeRedirectHook
      ]
    };
  } else {

View on GitHub (pinned to 20602a8e10)

Solutions

  1. Inspect error.data.redirectUrl and error.data.originalError to see which SSRF rule the redirect violated.
  2. If the redirect target is legitimate on a self-hosted instance, add it to SSRF_ALLOWED_HOSTNAMES.
  3. Disable redirect-following for the request (followRedirects:false) and resolve the final URL yourself if you need explicit control.
  4. Audit the upstream API: an unexpected redirect to a private target may indicate a compromised or misconfigured service.

Example fix

// before — got(url, { followRedirect: true }) where upstream redirects to http://169.254.169.254/...
// after  — got(url, { followRedirect: false }) then explicitly fetch the validated final URL, or allowlist the legitimate redirect target
Defensive patterns

Strategy: validation

Validate before calling

function shouldFollowRedirect(currentUrl: string, nextUrl: string): boolean {
  // caller-controlled: validate nextUrl against SSRF policy (or the allowlist) before following
  try { /* sync-validate nextUrl */; return true; } catch { return false; }
}

Try / catch

try { await got(url, ssrfOptions); }  // ssrfOptions wires the beforeRedirect hook
catch (e) {
  if (e instanceof QueryError && e.message === 'Redirect blocked by SSRF protection') {
    // inspect e.data.redirectUrl and e.data.originalError; either allowlist the target or disable followRedirect for this call
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of ToolJet/ToolJet@20602a8e10 (2026-08-13). Data as JSON: /api/errors/02a4c68e6f3a63a1. Report an issue: GitHub.