{"record":{"id":"2fd6431843723bbd","repo":"payloadcms/payload","slug":"file-urls-must-use-http-or-https","errorCode":null,"errorMessage":"File URLs must use http or https.","messagePattern":"File URLs must use http or https\\.","errorType":"validation","errorClass":"APIError","httpStatus":400,"severity":"error","filePath":"packages/plugin-mcp/src/mcp/builtin/collections/fileInput.ts","lineNumber":99,"sourceCode":"\n    file = {\n      name: sanitizeFilename(input.name),\n      data,\n      mimetype: input.mimeType,\n      size: data.length,\n    }\n  } else {\n    if (uploadConfig.pasteURL === false) {\n      throw new APIError(\n        `Uploading files from URLs is disabled for collection \"${collectionSlug}\".`,\n        400,\n      )\n    }\n\n    const url = new URL(input.url)\n\n    if (!['http:', 'https:'].includes(url.protocol)) {\n      throw new APIError('File URLs must use http or https.', 400)\n    }\n\n    if (\n      typeof uploadConfig.pasteURL === 'object' &&\n      !isURLAllowed(input.url, uploadConfig.pasteURL.allowList)\n    ) {\n      throw new APIError('The provided file URL is not allowed.', 400)\n    }\n\n    file = await getExternalFile({\n      data: {\n        filename: sanitizeFilename(input.name || getURLFilename(url)),\n        url: input.url,\n      } as FileData,\n      req,\n      uploadConfig: {\n        ...uploadConfig,\n        externalFileHeaderFilter: uploadConfig.externalFileHeaderFilter ?? (() => ({})),","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/plugin-mcp/src/mcp/builtin/collections/fileInput.ts#L81-L117","documentation":"Thrown as a 400 when `source: 'externalURL'` is used and the parsed `URL.protocol` is anything other than `http:` or `https:`. The protocol allowlist is hard-coded to prevent SSRF via `file:`, `ftp:`, `data:`, and other schemes reaching internal fetchers.","triggerScenarios":"Passing a `url` with scheme `ftp://`, `file://`, `data:`, `s3://`, etc. as an MCP tool's `externalURL` source; passing a URL without a scheme so `new URL(...)` yields an unexpected protocol.","commonSituations":"Client building the URL from user input that includes a `file://` path; copy-paste of an `s3://` object URI instead of its HTTPS access URL; malformed URL that defaults to an unexpected protocol.","solutions":["Ensure the `url` value begins with `http://` or `https://`","If the source object is only available via a non-http scheme, fetch it yourself and pass it as `source: 'base64'`","Sanitize client-side input to reject non-http(s) schemes before calling the tool"],"exampleFix":"// before\n{ source: 'externalURL', url: 'ftp://server/file.png' }\n// after\n{ source: 'externalURL', url: 'https://server/file.png' }","handlingStrategy":"validation","validationCode":"// Reject non-http(s) URLs before calling the tool\nfunction assertHttpUrl(u: string) {\n  const parsed = new URL(u)\n  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')\n    throw new Error(`URL must be http/https, got ${parsed.protocol}`)\n}","typeGuard":"function isHttpUrl(u: string): boolean {\n  try { const p = new URL(u).protocol; return p === 'http:' || p === 'https:' }\n  catch { return false }\n}","tryCatchPattern":"import { APIError } from 'payload'\ntry {\n  await tool.call({ source: 'externalURL', url })\n} catch (e) {\n  if (e instanceof APIError && e.statusCode === 400 && /must use http or https/.test(e.message)) {\n    // prompt the user for a valid http(s) URL\n  }\n  throw e\n}","preventionTips":["Validate URL scheme client-side with `new URL(...)` before submission","If integrating with non-http sources (S3, FTP), fetch the bytes yourself and use base64","Treat the scheme allowlist as an SSRF control — never widen it"],"tags":["mcp","file-upload","ssrf","url-validation","security"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}