GitHub Security Advisory: GHSA-hfvx-25r5-qc3w
Package: fabric (NPM)
Severity: HIGH (CVSS 7.6)
CWE: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting'), CWE-116: Improper Encoding or Escaping of Output
Vulnerable versions: < 7.2.0
Patched in: 7.2.0

Summary: Fabric.js Affected by Stored XSS via SVG Export

Details:
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.

### Deserialization Path (no sanitization)

`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.

---

### Finding 1: XSS via `id` Property Injection 

The `id` property from deserialized JSON is interpolated directly into SVG attribute strings without escaping.

**Vulnerable code (`src/shapes/Object/FabricObjectSVGExportMixin.ts`, line 89, `getSvgCommons()`):**
```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()` (same file, line 178) which wraps every object's SVG output in a `<g>` 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.

**Contrast with text content, which IS escaped:**
```typescript
// src/shapes/Text/TextSVGExportMixin.ts:186
return `<tspan ...>${escapeXml(char)}</tspan>`;
```

The inconsistency shows that the intention was to prevent injection but was missed w attribute contexts.

---

### Finding 2: XSS via Image `src` / `xlink:href` Injection 

Image source URLs are interpolated raw into `xlink:href` in `_toSVG()`.

**Vulnerable code (`src/shapes/Image.ts`, line 404, `_toSVG()`):**
```typescript
imageMarkup.push(
  '\t<image ',
  'COMMON_PARTS',
  `xlink:href="${this.getSvgSrc(true)}" x="${x - this.cropX}" y="${
    y - this.cropY
  }" ...`  // <-- unescaped
);
```

`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.

---

### Finding 3: XSS via Pattern `sourceToString()` 

**Vulnerable code (`src/Pattern/Pattern.ts`, line 181, `toSVG()`):**
```typescript
`<image x="0" y="0" ... xlink:href="${this.sourceToString()}"></image>`
// <-- unescaped, returns this.source.src for image sources
```

Additionally, Pattern's construct