payloadcms/payload · warning · APIError

Redirect target is not allowed.

Error message

Redirect target is not allowed.

What it means

For server-side URL fetching, Payload optionally restricts origins via `upload.pasteURL.allowList`. When following a redirect, the resolved `Location` URL is checked against that allow list; if it does not match, Payload throws `APIError` HTTP 400 `Redirect target is not allowed.` This prevents a redirect from sneaking a file in from an unapproved host (a common SSRF/toxic-proxy bypass).

Source

Thrown at packages/payload/src/uploads/getExternalFile.ts:85

          headers,
          method: 'GET',
        })
      }

      if (res.status >= 300 && res.status < 400) {
        redirectCount++
        if (redirectCount > maxRedirects) {
          throw new APIError(`Too many redirects (max ${maxRedirects})`, 403)
        }
        const location = res.headers.get('location')
        if (location) {
          fileURL = new URL(location, fileURL).toString()
          if (
            uploadConfig.pasteURL &&
            uploadConfig.pasteURL.allowList &&
            !isURLAllowed(fileURL, uploadConfig.pasteURL.allowList)
          ) {
            throw new APIError('Redirect target is not allowed.', 400)
          }
          continue
        }
      }

      break
    }

    if (!res || !res.ok) {
      throw new APIError(`Failed to fetch file from ${fileURL}`, res?.status)
    }

    const data = await res.arrayBuffer()

    return {
      name: filename,
      data: Buffer.from(data),
      mimetype: res.headers.get('content-type') || undefined!,

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. Add the redirect target's hostname (and any differing port/protocol) to `upload.pasteURL.allowList`.
  2. Use the direct final URL of the file rather than a URL known to redirect.
  3. Broaden the allow-list entry (e.g. add the CDN's secondary host, or a pathname wildcard) while staying scoped.
  4. Verify the redirect is legitimate and not a malicious open-redirect on the source host.

Example fix

// before
const Media = {
  slug: 'media',
  upload: {
    pasteURL: {
      allowList: [{ hostname: 'cdn.example.com' }], // redirect goes to cdn-secondary.example.com
    },
  },
}

// after
const Media = {
  slug: 'media',
  upload: {
    pasteURL: {
      allowList: [
        { hostname: 'cdn.example.com' },
        { hostname: 'cdn-secondary.example.com' },
      ],
    },
  },
}
Defensive patterns

Strategy: validation

Validate before calling

import { isURLAllowed } from 'payload/utilities' // or replicate the matcher

function targetAllowed(target: string, allowList: AllowList): boolean {
  return isURLAllowed(target, allowList)
}

const allowList = collection.upload?.pasteURL?.allowList ?? []
if (!targetAllowed(redirectTarget, allowList)) {
  // add the target host or use a direct URL
}

Type guard

function matchesAllowList(url: string, list: AllowList[]): boolean {
  try {
    const u = new URL(url)
    return list.some((e) => e.hostname === u.hostname &&
      (!e.port || e.port === u.port) &&
      (!e.protocol || e.protocol === u.protocol.replace(':', '')))
  } catch { return false }
}

Try / catch

try {
  await payload.update({ collection: 'media', id, data: { url } })
} catch (err) {
  if (err instanceof Error && /redirect target is not allowed/i.test(err.message)) {
    // broaden upload.pasteURL.allowList with the redirect host, or switch to a direct URL
  } else throw err
}

Prevention

When it happens

Trigger: `upload.pasteURL.allowList` is configured, the fetched URL returns a 3xx, and the `Location` header resolves to a host/path/port/protocol not matching any allow-list entry (entries match `hostname`, optional `pathname`, `port`, `protocol`, `search`).

Common situations: An allowed CDN redirects to an internal/secondary host not in the list. The provider moved to a new domain. The allow list was set too narrowly (hostname only, but the redirect lands on a different port/path). `isURLAllowed` matched the initial URL but not the redirect target.

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/d6e59f725f3b4dc8. Report an issue: GitHub.