GHSA-87V3-4CFP-CM76
Vulnerability from github – Published: 2026-03-18 16:10 – Updated: 2026-03-18 16:10Summary
The SVG schema plugin in @pdfme/schemas renders user-supplied SVG content using container.innerHTML = value without any sanitization, enabling arbitrary JavaScript execution in the user's browser.
Details
In packages/schemas/src/graphics/svg.ts, line 87, the SVG schema's ui renderer assigns raw SVG markup directly to innerHTML when in viewer mode or form mode with readOnly: true:
// svg.ts, line 81-94 (non-editable rendering path)
} else {
if (!value) return;
if (!isValidSVG(value)) {
rootElement.appendChild(createErrorElm());
return;
}
container.innerHTML = value; // <-- VULNERABLE: unsanitized SVG injected into DOM
const svgElement = container.childNodes[0];
if (svgElement instanceof SVGElement) {
svgElement.setAttribute('width', '100%');
svgElement.setAttribute('height', '100%');
rootElement.appendChild(container);
}
}
The isValidSVG() function (lines 11-37) only validates that the string contains <svg and </svg> tags and passes DOMParser well-formedness checks. It does NOT strip or block:
- <script> tags embedded in SVG
- Event handler attributes (onload, onerror, onclick, etc.)
- <foreignObject> elements containing HTML with event handlers
- <animate> / <set> elements with onbegin / onend handlers
- SVG <use> elements referencing malicious external resources
All of these are valid SVG and pass isValidSVG(), but execute JavaScript when inserted via innerHTML.
Attack Vectors
1. Malicious Template (readOnly SVG schema)
An attacker crafts a template JSON with a readOnly SVG schema containing a malicious content value. When loaded into the pdfme Form or Viewer component, the SVG executes JavaScript.
2. Application-Supplied Inputs + Viewer
If an application uses the pdfme Viewer component and passes user-controlled data as inputs for a non-readOnly SVG schema, the attacker's SVG flows directly to innerHTML.
Proof of Concept
Loading the following template into a pdfme Form or Viewer component triggers JavaScript execution:
{
"basePdf": { "width": 210, "height": 297, "padding": [20, 20, 20, 20] },
"schemas": [[
{
"name": "malicious_svg",
"type": "svg",
"content": "<svg xmlns='http://www.w3.org/2000/svg' onload='alert(document.domain)'><rect width='100' height='100' fill='red'/></svg>",
"readOnly": true,
"position": { "x": 20, "y": 20 },
"width": 80,
"height": 40
}
]]
}
Additional payloads that bypass isValidSVG() and execute JavaScript:
<!-- Via foreignObject -->
<svg xmlns="http://www.w3.org/2000/svg"><foreignObject width="200" height="60"><body xmlns="http://www.w3.org/1999/xhtml"><img src="x" onerror="alert(1)"/></body></foreignObject></svg>
<!-- Via animate onbegin -->
<svg xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100"><animate attributeName="x" values="0" dur="0.001s" onbegin="alert(1)"/></rect></svg>
Impact
An attacker who can supply a malicious template (via file upload, shared template URL, multi-tenant template storage, or updateTemplate() API) can execute arbitrary JavaScript in the context of any user who views or fills the template. This enables:
- Session hijacking via cookie/token theft
- Keylogging of form inputs (including sensitive data being entered into PDF forms)
- Phishing attacks by modifying the rendered page
- Data exfiltration from the application
The attack is particularly concerning for multi-tenant SaaS applications using pdfme where templates may be user-supplied.
Suggested Fix
Sanitize SVG content before DOM insertion using DOMPurify or a similar library:
import DOMPurify from 'dompurify';
// Replace line 87:
container.innerHTML = DOMPurify.sanitize(value, { USE_PROFILES: { svg: true } });
Alternatively, parse the SVG via DOMParser, strip all script elements and event handler attributes, then append the sanitized DOM nodes.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 5.5.8"
},
"package": {
"ecosystem": "npm",
"name": "@pdfme/schemas"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "5.5.9"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-79"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-18T16:10:26Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "## Summary\n\nThe SVG schema plugin in `@pdfme/schemas` renders user-supplied SVG content using `container.innerHTML = value` without any sanitization, enabling arbitrary JavaScript execution in the user\u0027s browser.\n\n## Details\n\nIn `packages/schemas/src/graphics/svg.ts`, line 87, the SVG schema\u0027s `ui` renderer assigns raw SVG markup directly to `innerHTML` when in viewer mode or form mode with `readOnly: true`:\n\n```typescript\n// svg.ts, line 81-94 (non-editable rendering path)\n} else {\n if (!value) return;\n if (!isValidSVG(value)) {\n rootElement.appendChild(createErrorElm());\n return;\n }\n container.innerHTML = value; // \u003c-- VULNERABLE: unsanitized SVG injected into DOM\n const svgElement = container.childNodes[0];\n if (svgElement instanceof SVGElement) {\n svgElement.setAttribute(\u0027width\u0027, \u0027100%\u0027);\n svgElement.setAttribute(\u0027height\u0027, \u0027100%\u0027);\n rootElement.appendChild(container);\n }\n}\n```\n\nThe `isValidSVG()` function (lines 11-37) only validates that the string contains `\u003csvg` and `\u003c/svg\u003e` tags and passes `DOMParser` well-formedness checks. It does NOT strip or block:\n- `\u003cscript\u003e` tags embedded in SVG\n- Event handler attributes (`onload`, `onerror`, `onclick`, etc.)\n- `\u003cforeignObject\u003e` elements containing HTML with event handlers\n- `\u003canimate\u003e` / `\u003cset\u003e` elements with `onbegin` / `onend` handlers\n- SVG `\u003cuse\u003e` elements referencing malicious external resources\n\nAll of these are valid SVG and pass `isValidSVG()`, but execute JavaScript when inserted via `innerHTML`.\n\n## Attack Vectors\n\n### 1. Malicious Template (readOnly SVG schema)\nAn attacker crafts a template JSON with a readOnly SVG schema containing a malicious `content` value. When loaded into the pdfme Form or Viewer component, the SVG executes JavaScript.\n\n### 2. Application-Supplied Inputs + Viewer\nIf an application uses the pdfme Viewer component and passes user-controlled data as inputs for a non-readOnly SVG schema, the attacker\u0027s SVG flows directly to `innerHTML`.\n\n## Proof of Concept\n\nLoading the following template into a pdfme Form or Viewer component triggers JavaScript execution:\n\n```json\n{\n \"basePdf\": { \"width\": 210, \"height\": 297, \"padding\": [20, 20, 20, 20] },\n \"schemas\": [[\n {\n \"name\": \"malicious_svg\",\n \"type\": \"svg\",\n \"content\": \"\u003csvg xmlns=\u0027http://www.w3.org/2000/svg\u0027 onload=\u0027alert(document.domain)\u0027\u003e\u003crect width=\u0027100\u0027 height=\u0027100\u0027 fill=\u0027red\u0027/\u003e\u003c/svg\u003e\",\n \"readOnly\": true,\n \"position\": { \"x\": 20, \"y\": 20 },\n \"width\": 80,\n \"height\": 40\n }\n ]]\n}\n```\n\nAdditional payloads that bypass `isValidSVG()` and execute JavaScript:\n\n```svg\n\u003c!-- Via foreignObject --\u003e\n\u003csvg xmlns=\"http://www.w3.org/2000/svg\"\u003e\u003cforeignObject width=\"200\" height=\"60\"\u003e\u003cbody xmlns=\"http://www.w3.org/1999/xhtml\"\u003e\u003cimg src=\"x\" onerror=\"alert(1)\"/\u003e\u003c/body\u003e\u003c/foreignObject\u003e\u003c/svg\u003e\n\n\u003c!-- Via animate onbegin --\u003e\n\u003csvg xmlns=\"http://www.w3.org/2000/svg\"\u003e\u003crect width=\"100\" height=\"100\"\u003e\u003canimate attributeName=\"x\" values=\"0\" dur=\"0.001s\" onbegin=\"alert(1)\"/\u003e\u003c/rect\u003e\u003c/svg\u003e\n```\n\n## Impact\n\nAn attacker who can supply a malicious template (via file upload, shared template URL, multi-tenant template storage, or `updateTemplate()` API) can execute arbitrary JavaScript in the context of any user who views or fills the template. This enables:\n- Session hijacking via cookie/token theft\n- Keylogging of form inputs (including sensitive data being entered into PDF forms)\n- Phishing attacks by modifying the rendered page\n- Data exfiltration from the application\n\nThe attack is particularly concerning for multi-tenant SaaS applications using pdfme where templates may be user-supplied.\n\n## Suggested Fix\n\nSanitize SVG content before DOM insertion using DOMPurify or a similar library:\n\n```typescript\nimport DOMPurify from \u0027dompurify\u0027;\n\n// Replace line 87:\ncontainer.innerHTML = DOMPurify.sanitize(value, { USE_PROFILES: { svg: true } });\n```\n\nAlternatively, parse the SVG via `DOMParser`, strip all script elements and event handler attributes, then append the sanitized DOM nodes.",
"id": "GHSA-87v3-4cfp-cm76",
"modified": "2026-03-18T16:10:26Z",
"published": "2026-03-18T16:10:26Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/pdfme/pdfme/security/advisories/GHSA-87v3-4cfp-cm76"
},
{
"type": "PACKAGE",
"url": "https://github.com/pdfme/pdfme"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Cross-Site Scripting (XSS) via SVG Schema innerHTML Injection in @pdfme/schemas"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.