Skip to content

Commit 7b6e7c0

Browse files
1 parent 883096e commit 7b6e7c0

1 file changed

Lines changed: 58 additions & 0 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"schema_version": "1.4.0",
3+
"id": "GHSA-87v3-4cfp-cm76",
4+
"modified": "2026-03-18T16:10:26Z",
5+
"published": "2026-03-18T16:10:26Z",
6+
"aliases": [],
7+
"summary": "Cross-Site Scripting (XSS) via SVG Schema innerHTML Injection in @pdfme/schemas",
8+
"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's browser.\n\n## Details\n\nIn `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`:\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; // <-- VULNERABLE: unsanitized SVG injected into DOM\n const svgElement = container.childNodes[0];\n if (svgElement instanceof SVGElement) {\n svgElement.setAttribute('width', '100%');\n svgElement.setAttribute('height', '100%');\n rootElement.appendChild(container);\n }\n}\n```\n\nThe `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:\n- `<script>` tags embedded in SVG\n- Event handler attributes (`onload`, `onerror`, `onclick`, etc.)\n- `<foreignObject>` elements containing HTML with event handlers\n- `<animate>` / `<set>` elements with `onbegin` / `onend` handlers\n- SVG `<use>` 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's 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\": \"<svg xmlns='http://www.w3.org/2000/svg' onload='alert(document.domain)'><rect width='100' height='100' fill='red'/></svg>\",\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<!-- Via foreignObject -->\n<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>\n\n<!-- Via animate onbegin -->\n<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>\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 'dompurify';\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.",
9+
"severity": [
10+
{
11+
"type": "CVSS_V3",
12+
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"
13+
}
14+
],
15+
"affected": [
16+
{
17+
"package": {
18+
"ecosystem": "npm",
19+
"name": "@pdfme/schemas"
20+
},
21+
"ranges": [
22+
{
23+
"type": "ECOSYSTEM",
24+
"events": [
25+
{
26+
"introduced": "0"
27+
},
28+
{
29+
"fixed": "5.5.9"
30+
}
31+
]
32+
}
33+
],
34+
"database_specific": {
35+
"last_known_affected_version_range": "<= 5.5.8"
36+
}
37+
}
38+
],
39+
"references": [
40+
{
41+
"type": "WEB",
42+
"url": "https://github.com/pdfme/pdfme/security/advisories/GHSA-87v3-4cfp-cm76"
43+
},
44+
{
45+
"type": "PACKAGE",
46+
"url": "https://github.com/pdfme/pdfme"
47+
}
48+
],
49+
"database_specific": {
50+
"cwe_ids": [
51+
"CWE-79"
52+
],
53+
"severity": "MODERATE",
54+
"github_reviewed": true,
55+
"github_reviewed_at": "2026-03-18T16:10:26Z",
56+
"nvd_published_at": null
57+
}
58+
}

0 commit comments

Comments
 (0)