{"id":"CVE-2026-27013","aliases":["EUVD-2026-7973","GHSA-hfvx-25r5-qc3w"],"status":"published","title":"Fabric.js Affected by Stored XSS via SVG Export","description":"Fabric.js is a Javascript HTML5 canvas library. Prior to version 7.2.0, Fabric.js applies `escapeXml()` to text content during SVG export (`src/shapes/Text/TextSVGExportMixin.ts:186`) but fails to apply it to other user-controlled string values that are interpolated into SVG attribute markup. When attacker-controlled JSON is loaded via `loadFromJSON()` and later exported via `toSVG()`, the unescaped values break out of XML attributes and inject arbitrary SVG elements including event handlers. Any application that accepts user-supplied JSON (via `loadFromJSON()`, collaborative sharing, import features, CMS plugins) and renders the `toSVG()` output in a browser context (SVG preview, export download rendered in-page, email template, embed) is vulnerable to stored XSS. An attacker can execute arbitrary JavaScript in the victim's browser session. Version 7.2.0 contains a fix.","severity":{"version":"3.1","score":7.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00328,"percentile":0.26114,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-116","CWE-79"],"published":"2026-02-18T22:44:58Z","modified":"2026-10-28T02:30:36Z","fixes":[{"ecosystem":"npm","package":"fabric","fixed_in":["7.2.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"fabric","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.2.0"}]}]},{"source":"osv","ecosystem":"npm","package":"fabric","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"7.2.0"}]}]}],"products":[{"vendor":"fabricjs","product":"fabric.js"}],"references":[{"url":"https://github.com/fabricjs/fabric.js/security/advisories/GHSA-hfvx-25r5-qc3w","tags":["x_refsource_CONFIRM","Exploit","Patch","Vendor Advisory","advisory","web"]},{"url":"https://github.com/fabricjs/fabric.js/commit/7e1a122defd8feefe4eb7eaf0c180d7b0aeb6fee","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://github.com/fabricjs/fabric.js/releases/tag/v720","tags":["x_refsource_MISC","Product","Release Notes","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-27013","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-hfvx-25r5-qc3w"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/27xxx/CVE-2026-27013.json","tags":["advisory"]},{"url":"https://github.com/fabricjs/fabric.js","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-27013","title":"Fabric.js Affected by Stored XSS via SVG Export","description":"Fabric.js is a Javascript HTML5 canvas library. Prior to version 7.2.0, Fabric.js applies `escapeXml()` to text content during SVG export (`src/shapes/Text/TextSVGExportMixin.ts:186`) but fails to apply it to other user-controlled string values that are interpolated into SVG attribute markup. When attacker-controlled JSON is loaded via `loadFromJSON()` and later exported via `toSVG()`, the unescaped values break out of XML attributes and inject arbitrary SVG elements including event handlers. Any application that accepts user-supplied JSON (via `loadFromJSON()`, collaborative sharing, import features, CMS plugins) and renders the `toSVG()` output in a browser context (SVG preview, export download rendered in-page, email template, embed) is vulnerable to stored XSS. An attacker can execute arbitrary JavaScript in the victim's browser session. Version 7.2.0 contains a fix.","status":"published","published":"2026-02-19T19:38:19.711Z","modified":"2026-02-19T21:21:59.208Z","cvss":[{"version":"3.1","score":7.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L","level":"high","assessor":"cna"}],"cwes":["CWE-79","CWE-116"],"updated_at":"2026-11-02T14:19:49.199185Z"}],"euvd":[{"id":"EUVD-2026-7973","description":"Fabric.js is a Javascript HTML5 canvas library. Prior to version 7.2.0, Fabric.js applies `escapeXml()` to text content during SVG export (`src/shapes/Text/TextSVGExportMixin.ts:186`) but fails to apply it to other user-controlled string values that are interpolated into SVG attribute markup. When attacker-controlled JSON is loaded via `loadFromJSON()` and later exported via `toSVG()`, the unescaped values break out of XML attributes and inject arbitrary SVG elements including event handlers. Any application that accepts user-supplied JSON (via `loadFromJSON()`, collaborative sharing, import features, CMS plugins) and renders the `toSVG()` output in a browser context (SVG preview, export download rendered in-page, email template, embed) is vulnerable to stored XSS. An attacker can execute arbitrary JavaScript in the victim's browser session. Version 7.2.0 contains a fix.","status":"published","published":"2026-02-19T19:38:19Z","modified":"2026-02-19T21:21:59Z","cvss":[{"version":"3.1","score":7.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L","level":"high"}],"updated_at":"2026-11-02T19:48:37.056368Z"}],"ghsa":[{"id":"GHSA-hfvx-25r5-qc3w","title":"Fabric.js Affected by Stored XSS via SVG Export","description":"fabric.js applies `escapeXml()` to text content during SVG export (`src/shapes/Text/TextSVGExportMixin.ts:186`) but fails to apply it to other user-controlled string values that are interpolated into SVG attribute markup. When attacker-controlled JSON is loaded via `loadFromJSON()` and later exported via `toSVG()`, the unescaped values break out of XML attributes and inject arbitrary SVG elements including event handlers.\n\n### Deserialization Path (no sanitization)\n\n`loadFromJSON()` (`src/canvas/StaticCanvas.ts:1229`) calls `enlivenObjects()` which calls `_fromObject()` (`src/shapes/Object/Object.ts:1902`). `_fromObject` passes all deserialized properties to the shape constructor via `new this(enlivedObjectOptions)`. The constructor ultimately calls `_setOptions()` (`src/CommonMethods.ts:9`) which iterates over every property and assigns it to the object via `this.set(prop, options[prop])`. There is no allowlist or sanitization - any property in the JSON, including `id`, is set verbatim on the fabric object.\n\n---\n\n### Finding 1: XSS via `id` Property Injection \n\nThe `id` property from deserialized JSON is interpolated directly into SVG attribute strings without escaping.\n\n**Vulnerable code (`src/shapes/Object/FabricObjectSVGExportMixin.ts`, line 89, `getSvgCommons()`):**\n```typescript\ngetSvgCommons(\n  this: FabricObjectSVGExportMixin \u0026 FabricObject \u0026 { id?: string },\n) {\n  return [\n    this.id ? `id=\"${this.id}\" ` : '',  // \u003c-- unescaped, user-controlled\n    this.clipPath\n      ? `clip-path=\"url(#${...})\" `\n      : '',\n  ].join('');\n}\n```\n\nThis method is called in `_createBaseSVGMarkup()` (same file, line 178) which wraps every object's SVG output in a `\u003cg\u003e` element. Every fabric object type (Rect, Circle, Path, Text, Image, Group, etc.) inherits this mixin, so the `id` injection vector applies to all object types.\n\n**Contrast with text content, which IS escaped:**\n```typescript\n// src/shapes/Text/TextSVGExportMixin.ts:186\nreturn `\u003ctspan ...\u003e${escapeXml(char)}\u003c/tspan\u003e`;\n```\n\nThe inconsistency shows that the intention was to prevent injection but was missed w attribute contexts.\n\n---\n\n### Finding 2: XSS via Image `src` / `xlink:href` Injection \n\nImage source URLs are interpolated raw into `xlink:href` in `_toSVG()`.\n\n**Vulnerable code (`src/shapes/Image.ts`, line 404, `_toSVG()`):**\n```typescript\nimageMarkup.push(\n  '\\t\u003cimage ',\n  'COMMON_PARTS',\n  `xlink:href=\"${this.getSvgSrc(true)}\" x=\"${x - this.cropX}\" y=\"${\n    y - this.cropY\n  }\" ...`  // \u003c-- unescaped\n);\n```\n\n`getSvgSrc()` returns the image `src` property which is set from JSON during deserialization. An attacker can inject a `src` value that breaks out of the `xlink:href` attribute.\n\n---\n\n### Finding 3: XSS via Pattern `sourceToString()` \n\n**Vulnerable code (`src/Pattern/Pattern.ts`, line 181, `toSVG()`):**\n```typescript\n`\u003cimage x=\"0\" y=\"0\" ... xlink:href=\"${this.sourceToString()}\"\u003e\u003c/image\u003e`\n// \u003c-- unescaped, returns this.source.src for image sources\n```\n\nAdditionally, Pattern's constructor (`line 92–94`) runs `this.id = uid()` *before* `Object.assign(this, options)`, meaning a user-supplied `id` in the pattern JSON overwrites the auto-generated uid. The pattern `id` is then interpolated unescaped on line 180:\n```typescript\n`\u003cpattern id=\"SVGID_${id}\" x=\"${patternOffsetX}\" ...\u003e`\n```\n\n---\n\n### Finding 4: Gradient `id` Partial Injection (lower Severity)\n\n**Vulnerable code (`src/gradient/Gradient.ts`, line 212, `toSVG()`):**\n```typescript\n`id=\"SVGID_${this.id}\"`  // \u003c-- unescaped\n```\n\nGradient's constructor (`line 125`) computes `id: id ? `${id}_${uid()}` : uid()`. If a user-supplied `id` is present in the gradient JSON, it is prepended to the auto-generated uid. The user-controlled portion is interpolated unescaped into the SVG. This is exploitable but the payload is constrained by the `_\u003cuid\u003e` suffix appended after it.\n\n---\n\n## Impact\n\nAny application that:\n1. Accepts user-supplied JSON (via `loadFromJSON()`, collaborative sharing, import features, CMS plugins), AND\n2. Renders the `toSVG()` output in a browser context (SVG preview, export download rendered in-page, email template, embed)\n\n...is vulnerable to stored XSS. An attacker can execute arbitrary JavaScript in the victim's browser session.\n\nReal-world attack scenarios:\n- Collaborative design tools (Canva-like apps) where users share canvas state as JSON\n- CMS or e-commerce platforms with fabric.js-based editors that store/render designs\n- Any export-to-SVG workflow where the SVG is later displayed in a browser\n\n---\n\n## Remediation\n\nUpdate to [fabric.js 7.2.0](https://github.com/fabricjs/fabric.js/releases/tag/v720) or newer version. \n\n---\n\n## Confirmed Affected Files\n\n| File | Issue | Method | Exploitable |\n|---|---|---|---|\n| `src/shapes/Object/FabricObjectSVGExportMixin.ts` | Unescaped `this.id` in attribute | `getSvgCommons()` | Yes - primary vector, all object types |\n| `src/shapes/Image.ts` | Unescaped `getSvgSrc()` in `xlink:href` | `_toSVG()` | Yes |\n| `src/Pattern/Pattern.ts` | Unescaped `sourceToString()` in `xlink:href`; unescaped `id` in attribute | `toSVG()` | Yes |\n| `src/gradient/Gradient.ts` | User-supplied `id` prefix interpolated unescaped | `toSVG()` | Yes (partial - uid suffix appended) |","status":"published","published":"2026-02-18T22:44:58Z","modified":"2026-02-19T21:57:27Z","cvss":[{"version":"3.1","score":7.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L","level":"high"}],"cwes":["CWE-79","CWE-116"],"updated_at":"2026-11-02T14:39:25.82905Z"}],"nvd":[{"id":"CVE-2026-27013","description":"Fabric.js is a Javascript HTML5 canvas library. Prior to version 7.2.0, Fabric.js applies `escapeXml()` to text content during SVG export (`src/shapes/Text/TextSVGExportMixin.ts:186`) but fails to apply it to other user-controlled string values that are interpolated into SVG attribute markup. When attacker-controlled JSON is loaded via `loadFromJSON()` and later exported via `toSVG()`, the unescaped values break out of XML attributes and inject arbitrary SVG elements including event handlers. Any application that accepts user-supplied JSON (via `loadFromJSON()`, collaborative sharing, import features, CMS plugins) and renders the `toSVG()` output in a browser context (SVG preview, export download rendered in-page, email template, embed) is vulnerable to stored XSS. An attacker can execute arbitrary JavaScript in the victim's browser session. Version 7.2.0 contains a fix.","status":"published","published":"2026-02-19T20:25:44.23Z","modified":"2026-06-17T10:26:32.11Z","cvss":[{"version":"3.1","score":7.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L","level":"high","assessor":"secondary"},{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"primary"}],"cwes":["CWE-79","CWE-116"],"updated_at":"2026-11-02T14:12:06.274729Z"}],"osv":[{"id":"CVE-2026-27013","title":"Fabric.js Affected by Stored XSS via SVG Export","description":"Fabric.js is a Javascript HTML5 canvas library. Prior to version 7.2.0, Fabric.js applies `escapeXml()` to text content during SVG export (`src/shapes/Text/TextSVGExportMixin.ts:186`) but fails to apply it to other user-controlled string values that are interpolated into SVG attribute markup. When attacker-controlled JSON is loaded via `loadFromJSON()` and later exported via `toSVG()`, the unescaped values break out of XML attributes and inject arbitrary SVG elements including event handlers. Any application that accepts user-supplied JSON (via `loadFromJSON()`, collaborative sharing, import features, CMS plugins) and renders the `toSVG()` output in a browser context (SVG preview, export download rendered in-page, email template, embed) is vulnerable to stored XSS. An attacker can execute arbitrary JavaScript in the victim's browser session. Version 7.2.0 contains a fix.","status":"published","published":"2026-02-19T19:38:19.711Z","modified":"2026-10-28T02:30:36.564415771Z","cvss":[{"version":"3.1","score":7.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L","level":"high"}],"cwes":["CWE-116","CWE-79"],"updated_at":"2026-11-02T15:44:59.80315Z"},{"id":"GHSA-hfvx-25r5-qc3w","title":"Fabric.js Affected by Stored XSS via SVG Export","description":"fabric.js applies `escapeXml()` to text content during SVG export (`src/shapes/Text/TextSVGExportMixin.ts:186`) but fails to apply it to other user-controlled string values that are interpolated into SVG attribute markup. When attacker-controlled JSON is loaded via `loadFromJSON()` and later exported via `toSVG()`, the unescaped values break out of XML attributes and inject arbitrary SVG elements including event handlers.\n\n### Deserialization Path (no sanitization)\n\n`loadFromJSON()` (`src/canvas/StaticCanvas.ts:1229`) calls `enlivenObjects()` which calls `_fromObject()` (`src/shapes/Object/Object.ts:1902`). `_fromObject` passes all deserialized properties to the shape constructor via `new this(enlivedObjectOptions)`. The constructor ultimately calls `_setOptions()` (`src/CommonMethods.ts:9`) which iterates over every property and assigns it to the object via `this.set(prop, options[prop])`. There is no allowlist or sanitization - any property in the JSON, including `id`, is set verbatim on the fabric object.\n\n---\n\n### Finding 1: XSS via `id` Property Injection \n\nThe `id` property from deserialized JSON is interpolated directly into SVG attribute strings without escaping.\n\n**Vulnerable code (`src/shapes/Object/FabricObjectSVGExportMixin.ts`, line 89, `getSvgCommons()`):**\n```typescript\ngetSvgCommons(\n  this: FabricObjectSVGExportMixin \u0026 FabricObject \u0026 { id?: string },\n) {\n  return [\n    this.id ? `id=\"${this.id}\" ` : '',  // \u003c-- unescaped, user-controlled\n    this.clipPath\n      ? `clip-path=\"url(#${...})\" `\n      : '',\n  ].join('');\n}\n```\n\nThis method is called in `_createBaseSVGMarkup()` (same file, line 178) which wraps every object's SVG output in a `\u003cg\u003e` element. Every fabric object type (Rect, Circle, Path, Text, Image, Group, etc.) inherits this mixin, so the `id` injection vector applies to all object types.\n\n**Contrast with text content, which IS escaped:**\n```typescript\n// src/shapes/Text/TextSVGExportMixin.ts:186\nreturn `\u003ctspan ...\u003e${escapeXml(char)}\u003c/tspan\u003e`;\n```\n\nThe inconsistency shows that the intention was to prevent injection but was missed w attribute contexts.\n\n---\n\n### Finding 2: XSS via Image `src` / `xlink:href` Injection \n\nImage source URLs are interpolated raw into `xlink:href` in `_toSVG()`.\n\n**Vulnerable code (`src/shapes/Image.ts`, line 404, `_toSVG()`):**\n```typescript\nimageMarkup.push(\n  '\\t\u003cimage ',\n  'COMMON_PARTS',\n  `xlink:href=\"${this.getSvgSrc(true)}\" x=\"${x - this.cropX}\" y=\"${\n    y - this.cropY\n  }\" ...`  // \u003c-- unescaped\n);\n```\n\n`getSvgSrc()` returns the image `src` property which is set from JSON during deserialization. An attacker can inject a `src` value that breaks out of the `xlink:href` attribute.\n\n---\n\n### Finding 3: XSS via Pattern `sourceToString()` \n\n**Vulnerable code (`src/Pattern/Pattern.ts`, line 181, `toSVG()`):**\n```typescript\n`\u003cimage x=\"0\" y=\"0\" ... xlink:href=\"${this.sourceToString()}\"\u003e\u003c/image\u003e`\n// \u003c-- unescaped, returns this.source.src for image sources\n```\n\nAdditionally, Pattern's constructor (`line 92–94`) runs `this.id = uid()` *before* `Object.assign(this, options)`, meaning a user-supplied `id` in the pattern JSON overwrites the auto-generated uid. The pattern `id` is then interpolated unescaped on line 180:\n```typescript\n`\u003cpattern id=\"SVGID_${id}\" x=\"${patternOffsetX}\" ...\u003e`\n```\n\n---\n\n### Finding 4: Gradient `id` Partial Injection (lower Severity)\n\n**Vulnerable code (`src/gradient/Gradient.ts`, line 212, `toSVG()`):**\n```typescript\n`id=\"SVGID_${this.id}\"`  // \u003c-- unescaped\n```\n\nGradient's constructor (`line 125`) computes `id: id ? `${id}_${uid()}` : uid()`. If a user-supplied `id` is present in the gradient JSON, it is prepended to the auto-generated uid. The user-controlled portion is interpolated unescaped into the SVG. This is exploitable but the payload is constrained by the `_\u003cuid\u003e` suffix appended after it.\n\n---\n\n## Impact\n\nAny application that:\n1. Accepts user-supplied JSON (via `loadFromJSON()`, collaborative sharing, import features, CMS plugins), AND\n2. Renders the `toSVG()` output in a browser context (SVG preview, export download rendered in-page, email template, embed)\n\n...is vulnerable to stored XSS. An attacker can execute arbitrary JavaScript in the victim's browser session.\n\nReal-world attack scenarios:\n- Collaborative design tools (Canva-like apps) where users share canvas state as JSON\n- CMS or e-commerce platforms with fabric.js-based editors that store/render designs\n- Any export-to-SVG workflow where the SVG is later displayed in a browser\n\n---\n\n## Remediation\n\nUpdate to [fabric.js 7.2.0](https://github.com/fabricjs/fabric.js/releases/tag/v720) or newer version. \n\n---\n\n## Confirmed Affected Files\n\n| File | Issue | Method | Exploitable |\n|---|---|---|---|\n| `src/shapes/Object/FabricObjectSVGExportMixin.ts` | Unescaped `this.id` in attribute | `getSvgCommons()` | Yes - primary vector, all object types |\n| `src/shapes/Image.ts` | Unescaped `getSvgSrc()` in `xlink:href` | `_toSVG()` | Yes |\n| `src/Pattern/Pattern.ts` | Unescaped `sourceToString()` in `xlink:href`; unescaped `id` in attribute | `toSVG()` | Yes |\n| `src/gradient/Gradient.ts` | User-supplied `id` prefix interpolated unescaped | `toSVG()` | Yes (partial - uid suffix appended) |","status":"published","published":"2026-02-18T22:44:58Z","modified":"2026-02-22T23:24:35.168357Z","cvss":[{"version":"3.1","score":7.6,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L","level":"high"}],"cwes":["CWE-116","CWE-79"],"updated_at":"2026-11-02T15:46:48.920784Z"}]}}
