{"record":{"id":"9ae2e5d5be3a782c","repo":"payloadcms/payload","slug":"the-provided-url-is-not-allowed","errorCode":null,"errorMessage":"The provided URL is not allowed.","messagePattern":"The provided URL is not allowed\\.","errorType":"http","errorClass":"APIError","httpStatus":400,"severity":"error","filePath":"packages/payload/src/uploads/endpoints/getFileFromURL.ts","lineNumber":66,"sourceCode":"  const { searchParams } = new URL(req.url)\n  const src = searchParams.get('src')\n\n  if (!src || typeof src !== 'string') {\n    throw new APIError('A valid URL string is required.', 400)\n  }\n\n  const hasAllowList =\n    typeof config.upload.pasteURL === 'object' && Array.isArray(config.upload.pasteURL.allowList)\n\n  let fileURL: string\n  try {\n    fileURL = new URL(src).href\n  } catch {\n    throw new APIError('A valid URL string is required.', 400)\n  }\n\n  if (hasAllowList && !isURLAllowed(fileURL, config.upload.pasteURL.allowList)) {\n    throw new APIError('The provided URL is not allowed.', 400)\n  }\n\n  let redirectCount = 0\n  const maxRedirects = 3\n  let response!: Response\n\n  while (true) {\n    if (hasAllowList && isURLAllowed(fileURL, config.upload.pasteURL.allowList)) {\n      // Allow-listed URLs bypass SSRF filtering (e.g. internal/localhost CDNs)\n      response = await fetch(fileURL, {\n        headers: { 'Accept-Encoding': 'identity' },\n        redirect: 'manual',\n        signal: AbortSignal.timeout(30_000),\n      })\n    } else {\n      response = await safeFetch(fileURL, {\n        headers: {\n          'Accept-Encoding': 'identity',","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/payload/src/uploads/endpoints/getFileFromURL.ts#L48-L84","documentation":"APIError (HTTP 400, 'The provided URL is not allowed.') thrown when an allowList is configured for pasteURL and the initial requested URL does not match any allowList entry. isURLAllowed compares protocol (with appended ':'), hostname/port/search for equality, and pathname via glob (*/**) translation; an invalid URL denies by default.","triggerScenarios":"pasteURL.allowList is set (array of {hostname, protocol, pathname?, port?} criteria) and the user-submitted src URL's hostname, protocol, port, or pathname fails to match any entry. Also fires if the URL fails to parse (isURLAllowed returns false on parse error).","commonSituations":"Allow-listing cdn.example.com but the user pastes from a different host; protocol mismatch (allowList https, user submits http); pathname glob too narrow (e.g. '/images/*' won't match '/videos/x.mp4'); forgetting to allow the protocol key; typo in the allowList hostname.","solutions":["Add the source host (and protocol, pathname pattern) to upload.pasteURL.allowList.","Widen the pathname glob — use '**' for multi-segment matching (e.g. '/media/**').","Confirm the protocol field matches (pasteURL compares 'https' → 'https:').","On the client, restrict the picker to allow-listed hosts so users can't submit disallowed URLs."],"exampleFix":"// before — too narrow\npasteURL: { allowList: [{ hostname: 'cdn.example.com', pathname: '/img/*' }] }\n// after — cover the host and broad path\npasteURL: {\n  allowList: [\n    { hostname: 'cdn.example.com', protocol: 'https', pathname: '/**' },\n    { hostname: 'images.example.com', protocol: 'https', pathname: '/**' },\n  ],\n}","handlingStrategy":"validation","validationCode":"import { isURLAllowed } from 'payload/utilities' // if exported; else replicate\nconst ALLOW_LIST = [{ hostname: 'cdn.example.com', protocol: 'https', pathname: '/**' }]\nfunction urlAllowed(u: string): boolean {\n  return isURLAllowed ? isURLAllowed(u, ALLOW_LIST) : (() => { try { const p = new URL(u); return ALLOW_LIST.some(a => p.hostname === a.hostname && p.protocol === a.protocol + ':') } catch { return false } })()\n}\nif (!urlAllowed(src)) throw new Error('That host is not in the paste-URL allow list')","typeGuard":"const matchesAllowList = (u: string, list: Array<{ hostname: string; protocol?: string }>): boolean => {\n  try {\n    const p = new URL(u)\n    return list.some((a) => p.hostname === a.hostname && (!a.protocol || p.protocol === `${a.protocol}:`))\n  } catch { return false }\n}","tryCatchPattern":"try {\n  await fetch(`/api/media/paste-url?src=${encodeURIComponent(src)}`, { method: 'POST' })\n} catch (e) {\n  if (/not allowed/.test(e.message)) alert('Use a URL from an allowed host')\n}","preventionTips":["Mirror the server's pasteURL.allowList in client-side validation.","Restrict the URL picker to allow-listed hosts.","Document allowList semantics (protocol compared with trailing ':', pathname globbing).","Test allowList entries with realistic URLs before shipping."],"tags":["upload","paste-url","security","allow-list","validation"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}