Human
Machine
REPRO-2026-00105 HIGH XSS
Verified
Fabric.js: Stored XSS via SVG Export
fabric (npm) Feb 19, 2026
What's the vulnerability?
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.
Root Cause Analysis
# 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 `<g>` 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: `""><script>alert(1)</script><rect id="`
- Exports the object to SVG using `toSVG()`
- Checks if the payload is escaped or unescaped in the output
4. Returns exit code 0 if vulnerable, 1 if patched
### Expected Evidence of Reproduction
When vulnerable, the generated SVG contains the unescaped payload:
```xml
<g transform="matrix(1 0 0 1 0 0)" id=""><script>alert(1)</script><rect id="" >
```
The malicious `id` value breaks out of the `id` attribute and injects a `<script>` tag directly into the SVG markup.
## Evidence
### Reproduction Log
- **Location:** `logs/reproduction.log`
- **Key Excerpt:**
```
❌ VULNERABILITY CONFIRMED: Payload is NOT escaped in SVG output!
The id attribute allows XSS injection via SVG export.
```
### Generated Vulnerable SVG Output
```xml
<g transform="matrix(1 0 0 1 0 0)" id=""><script>alert(1)</script><rect id="" >
<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,0,0); fill-rule: nonzero; opacity: 1;" x="-50" y="-50" rx="0" ry="0" width="100" height="100" />
</g>
```
### 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.
One Command
Verify with pruva-verify
Run the Pruva CLI to automatically fetch and execute the reproduction script.
pruva-verify REPRO-2026-00105 or
pruva-verify GHSA-hfvx-25r5-qc3w or
pruva-verify CVE-2026-27013 Install:
curl -fsSL https://pruva.dev/install.sh | sh Or Run Manually
1
Download the script
curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00105/artifacts/reproduction_steps.sh 2
Make executable
chmod +x reproduction_steps.sh 3
Run the script
./reproduction_steps.sh Run in a VM, container, or disposable environment. This exploits a real vulnerability.
How Pruva Reproduced This
Watch the AI agent's step-by-step process.
Loading session...
Artifacts
No artifacts available