# REPRO-2026-00105: Fabric.js: Stored XSS via SVG Export ## Summary Status: published Severity: high Type: security Confidence: Unknown ## Identifiers REPRO ID: REPRO-2026-00105 GHSA: GHSA-hfvx-25r5-qc3w CVE: CVE-2026-27013 ## Package Name: fabric Ecosystem: npm Affected: < 7.2.0 Fixed: 7.2.0 ## Root Cause # Root Cause Analysis Report ## Summary Fabric.js versions prior to 7.2.0 contain a stored Cross-Site Scripting (XSS) vulnerability in their SVG export functionality. When user-controlled JSON is loaded via `loadFromJSON()` and later exported via `toSVG()`, the `id` property and other user-controlled string values are interpolated directly into SVG attribute markup without XML escaping. This allows an attacker to break out of XML attributes and inject arbitrary SVG elements including event handlers, leading to stored XSS. ## Impact - **Package:** fabric (npm) - **Affected Versions:** `< 7.2.0` - **Fixed Version:** `7.2.0` - **CVE:** CVE-2026-27013 - **GHSA:** GHSA-hfvx-25r5-qc3w - **CVSS Score:** 7.6 (HIGH) - **CWEs:** CWE-79 (XSS), CWE-116 (Improper Encoding or Escaping) ### Risk and Consequences Any application that: 1. Accepts user-supplied JSON (via `loadFromJSON()`, collaborative sharing, import features, CMS plugins) 2. Renders the `toSVG()` output in a browser context (SVG preview, export download, email templates, embeds) ...is vulnerable to stored XSS. An attacker can execute arbitrary JavaScript in the victim's browser session. Real-world attack scenarios include: - Collaborative design tools (Canva-like apps) where users share canvas state as JSON - CMS or e-commerce platforms with fabric.js-based editors that store/render designs - Any export-to-SVG workflow where the SVG is later displayed in a browser ## Root Cause The vulnerability exists because fabric.js fails to apply XML escaping to user-controlled string values that are interpolated into SVG attribute markup. While `escapeXml()` is correctly applied to text content during SVG export (in `src/shapes/Text/TextSVGExportMixin.ts:186`), it is NOT applied to other user-controlled string values. ### Vulnerable Code Location **File:** `src/shapes/Object/FabricObjectSVGExportMixin.ts` **Method:** `getSvgCommons()` (line 85-97) ```typescript getSvgCommons( this: FabricObjectSVGExportMixin & FabricObject & { id?: string }, ) { return [ this.id ? `id="${this.id}" ` : '', // <-- UNESCAPED, user-controlled this.clipPath ? `clip-path="url(#${...})" ` : '', ].join(''); } ``` This method is called in `_createBaseSVGMarkup()` which wraps every object's SVG output in a `` element. Since all fabric object types (Rect, Circle, Path, Text, Image, Group, etc.) inherit this mixin, the `id` injection vector applies to **all object types**. ### Deserialization Path (No Sanitization) `loadFromJSON()` (`src/canvas/StaticCanvas.ts:1229`) → `enlivenObjects()` → `_fromObject()` (`src/shapes/Object/Object.ts:1902`) → constructor → `_setOptions()` (`src/CommonMethods.ts:9`) There is no allowlist or sanitization - any property in the JSON, including `id`, is set verbatim on the fabric object. ### Fix Commit The security fix was applied in commit: https://github.com/fabricjs/fabric.js/commit/7e1a122defd8feefe4eb7eaf0c180d7b0aeb6fee The fix adds `escapeXml()` calls to all affected locations: - `src/shapes/Object/FabricObjectSVGExportMixin.ts`: Escapes `this.id` - `src/shapes/Image.ts`: Escapes `getSvgSrc()` in `xlink:href` - `src/Pattern/Pattern.ts`: Escapes pattern `id` and `sourceToString()` - `src/gradient/Gradient.ts`: Escapes gradient `id` - `src/Shadow.ts`: Escapes shadow `id` ## Reproduction Steps Run the reproduction script: ```bash ./repro/reproduction_steps.sh ``` ### What the Script Does 1. Clones fabric.js repository at the vulnerable version (7.1.0) 2. Builds the project using npm 3. Creates a Node.js test script that: - Creates a `Rect` object with a malicious `id` property containing XSS payload: `""> ``` The malicious `id` value breaks out of the `id` attribute and injects a ` ``` ### Environment Details - **Node.js Version:** 22.22.0 - **Fabric.js Version:** 7.1.0 (vulnerable) - **OS:** Linux ## Recommendations / Next Steps ### Immediate Actions 1. **Upgrade** to fabric.js 7.2.0 or newer immediately 2. **Validate** any stored JSON data that may have been created by untrusted users 3. **Sanitize** SVG output if you cannot upgrade immediately (apply XML escaping to all interpolated values) ### Testing Recommendations - Add regression tests that verify XML escaping for all user-controlled properties - Test SVG export with payloads containing: `"`, `<`, `>`, `&`, `'`, and JavaScript event handlers - Consider using a content security policy (CSP) as defense-in-depth ### Additional Affected Files (Per Ticket Analysis) | File | Issue | Method | |---|---|---| | `src/shapes/Image.ts` | Unescaped `getSvgSrc()` in `xlink:href` | `_toSVG()` | | `src/Pattern/Pattern.ts` | Unescaped `sourceToString()` in `xlink:href`; unescaped `id` | `toSVG()` | | `src/gradient/Gradient.ts` | User-supplied `id` prefix interpolated unescaped | `toSVG()` | ## Additional Notes ### Idempotency Confirmation The reproduction script has been run **twice consecutively** with consistent results: - Run 1: Vulnerability confirmed (exit code 0) - Run 2: Vulnerability confirmed (exit code 0) ### Edge Cases - The vulnerability affects **all** fabric object types (Rect, Circle, Path, Text, Image, Group, etc.) through the shared `getSvgCommons()` mixin - Multiple attack vectors exist: `id` property, Image `src`, Pattern `source`, Gradient `id` - The payload needs to break out of XML attribute context (`"`) and inject new elements ### Limitations This reproduction focuses on the primary `id` property injection vector. Other vectors (Image `src`, Pattern `source`, Gradient `id`) are also exploitable per the ticket analysis but were not explicitly tested in this reproduction script. ## Reproduction Details Reproduced: 2026-02-19T21:14:47.175Z Duration: 985 seconds Tool calls: 97 Turns: 59 Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00105 pruva-verify GHSA-hfvx-25r5-qc3w pruva-verify CVE-2026-27013 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00105&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00105/artifacts/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - GitHub Advisory: https://github.com/advisories/GHSA-hfvx-25r5-qc3w - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-27013 ## Artifacts - repro/rca_report.md (analysis, 6735 bytes) - repro/reproduction_steps.sh (reproduction_script, 2469 bytes) - bundle/ticket.md (ticket, 6257 bytes) - bundle/source.json (other, 8517 bytes) - bundle/ticket.json (other, 15381 bytes) - logs/variant_run_1.log (log, 1238 bytes) - logs/variant_vuln_console.log (log, 530 bytes) - logs/variant_fixed_console.log (log, 413 bytes) - logs/variant-fixed.log (log, 5911 bytes) - logs/variant-vuln.log (log, 5981 bytes) - logs/variant_run_2.log (log, 1172 bytes) - logs/reproduction.log (log, 619 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00105 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00105/artifacts/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00105 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev