agalwood/Motrix · error · HttpError

plugin.http.redirect_not_allowed

plugin.http.redirect_not_allowed

Error message

redirect: 'error' set; refusing to follow ${status} to ${location}

What it means

The caller explicitly set redirect:'error', and the server returned a 3xx with a Location header. The body is dumped (drained) and the request fails instead of following or returning the redirect. This is the strict opt-in mode for plugins that want to forbid silent redirects (e.g. to avoid SSRF or auth stripping).

Source

Thrown at src/core/plugin/capabilities/http.ts:396

        const location = response.headers.location
        const isRedirect = status >= 300 && status < 400 && location

        if (isRedirect) {
          if (redirect === 'manual') {
            // Surface the 3xx as-is.
            return await buildResponse<R>(
              response,
              opts.responseType,
              maxBodyBytes,
              internalCtrl,
              currentUrl,
              redirected,
              doCleanup
            )
          }
          if (redirect === 'error') {
            await response.body.dump?.()
            throw new HttpError(
              'plugin.http.redirect_not_allowed',
              `redirect: 'error' set; refusing to follow ${status} to ${location}`
            )
          }
          // redirect === 'follow'
          if (hops >= MAX_REDIRECTS) {
            await response.body.dump?.()
            throw new HttpError(
              'plugin.http.too_many_redirects',
              `Too many redirects (>${MAX_REDIRECTS})`
            )
          }
          await response.body.dump?.()
          const loc = Array.isArray(location) ? (location[0] ?? '') : location
          const nextUrl = new URL(loc, currentUrl)
          // Re-validate the scheme on every hop. checkScheme only ran on the
          // initial URL, so a 3xx Location to file:// (or any non-http scheme)
          // would otherwise escape the allowlist.

View on GitHub (pinned to 1a708ee577)

Solutions

  1. Switch to redirect:'follow' (the default) if following is acceptable.
  2. Switch to redirect:'manual' to inspect the 3xx and the Location yourself.
  3. Fix the upstream URL so it resolves directly without a redirect.

Example fix

// before
await http.request({ url, responseType: 'json', redirect: 'error' })

// after
await http.request({ url, responseType: 'json', redirect: 'follow' })
Defensive patterns

Strategy: validation

Validate before calling

const redirect = expectRedirect ? 'follow' : 'manual'
await http.request({ ...opts, redirect })

Try / catch

try {
  await http.request({ ...opts, redirect: 'error' })
} catch (e) {
  if (e instanceof HttpError && e.code === 'plugin.http.redirect_not_allowed') {
    // expected; switch to 'manual' or 'follow' depending on policy
  } else throw e
}

Prevention

When it happens

Trigger: Passing redirect:'error' and hitting any URL that returns 301/302/303/307/308; an endpoint that always redirects (e.g. http->https, trailing-slash normalization); a short-link service.

Common situations: Security-hardened plugin that disallows redirects; misconfigured base URL pointing at a redirecting endpoint; ops changed a URL to redirect.

Related errors


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/dc7b37c1ba24a87a. Report an issue: GitHub.