{"record":{"id":"ec1c12e664572f3e","repo":"fabricjs/fabric.js","slug":"resource-url-is-not-allowed","errorCode":null,"errorMessage":"Resource '${url}' is not allowed","messagePattern":"Resource '(.+?)' is not allowed","errorType":"validation","errorClass":"FabricError","httpStatus":null,"severity":"error","filePath":"packages/core/src/util/misc/objectEnlive.ts","lineNumber":41,"sourceCode":"/**\n * Loads image element from given url and resolve it, or catch.\n * @param {String} url URL representing an image\n * @param {LoadImageOptions} [options] image loading options\n * @returns {Promise<HTMLImageElement>} the loaded image.\n */\nexport const loadImage = (\n  url: string,\n  { signal, crossOrigin = null, resourceValidator }: LoadImageOptions = {},\n): Promise<HTMLImageElement> => {\n  if (signal && signal.aborted) {\n    return Promise.reject(new SignalAbortedError('loadImage'));\n  }\n  if (url && resourceValidator) {\n    return Promise.resolve()\n      .then(() => resourceValidator(url))\n      .then((isAllowed) => {\n        if (!isAllowed) {\n          throw new FabricError(`Resource '${url}' is not allowed`);\n        }\n        if (signal && signal.aborted) {\n          throw new SignalAbortedError('loadImage');\n        }\n        return loadImage(url, { signal, crossOrigin });\n      });\n  }\n  return new Promise<HTMLImageElement>(function (resolve, reject) {\n    if (signal && signal.aborted) {\n      return reject(new SignalAbortedError('loadImage'));\n    }\n    const img = createImage();\n    let abort: EventListenerOrEventListenerObject;\n    if (signal) {\n      abort = function (err: Event) {\n        img.src = '';\n        reject(err);\n      };","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/fabricjs/fabric.js/blob/2bd4992cabf4ec9609aa349ea09b723cadc94bef/packages/core/src/util/misc/objectEnlive.ts#L23-L59","documentation":"Thrown by loadImage in objectEnlive when a configured resourceValidator rejects the URL being loaded. It is a security feature (SSRF/XSS protection): when a resourceValidator function is set in config, every image URL must be approved by it before loading. The message names the exact URL that was denied.","triggerScenarios":"Calling fabric.loadImage / enlivenObjects (fromJSON) with an image URL while config.resourceValidator is set and returns false (or a falsy value) for that URL, e.g. only allowing https:// or same-origin URLs but the object contains http:// or a data: URL.","commonSituations":"Apps that added a resourceValidator for security and then load older saved JSONs containing external http URLs, relative URLs, or data URIs; or a validator with a bug (returning undefined instead of true) causing every image to be rejected.","solutions":["Inspect the URL in the message and update the resourceValidator to allow it if it is trusted (e.g. add the host to an allowlist)","If the URL is untrusted, sanitize the stored JSON data to use safe/rewritten URLs instead of relaxing the validator","Fix a validator that accidentally returns undefined/falsy for valid URLs (make sure it resolves boolean true)","If intentional denial, catch the error and handle the missing image gracefully (placeholder)"],"exampleFix":"// before\nimport { config } from 'fabric';\nconfig.resourceValidator = (url) => new URL(url, location.href).origin === location.origin;\n// loading an external image throws: Resource 'https://cdn.example.com/a.png' is not allowed\n\n// after\nconfig.resourceValidator = (url) => {\n  const u = new URL(url, location.href);\n  return u.origin === location.origin || u.hostname === 'cdn.example.com';\n};","handlingStrategy":"validation","validationCode":"import { config } from 'fabric';\nconst isAllowed = config.resourceValidator\n  ? await config.resourceValidator(url)\n  : true;\nif (!isAllowed) {\n  // rewrite/skip the URL before loading\n}","typeGuard":"const isSafeUrl = (url: string): boolean => {\n  try {\n    const u = new URL(url, location.href);\n    return u.protocol === 'https:' && ALLOWED_HOSTS.has(u.hostname);\n  } catch { return false; }\n};","tryCatchPattern":"try {\n  const img = await fabric.loadImage(url, { crossOrigin: 'anonymous' });\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith(\"Resource '\")) {\n    // URL rejected by validator: use placeholder or rewrite URL\n  } else throw e;\n}","preventionTips":["Keep an explicit URL allowlist and sanitize saved JSON before loadFromJSON","Make resourceValidator return an explicit boolean","Log rejected URLs to catch legacy data with unsafe URLs early","Test loading old saved canvases after adding a validator"],"tags":["security","image","ssrf","validator","config"],"backgroundTag":"url-allowlist-rejected","analyzedSha":"2bd4992cabf4ec9609aa349ea09b723cadc94bef","analyzedAt":"2026-08-28T10:56:44.286Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}