{"id":"GHSA-87v3-4cfp-cm76","summary":"Cross-Site Scripting (XSS) via SVG Schema innerHTML Injection in @pdfme/schemas","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;  // \u003c-- 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 `\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'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\": \"\u003csvg xmlns='http://www.w3.org/2000/svg' onload='alert(document.domain)'\u003e\u003crect width='100' height='100' fill='red'/\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 '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.","modified":"2026-03-18T16:19:40.209806Z","published":"2026-03-18T16:10:26Z","database_specific":{"nvd_published_at":null,"github_reviewed":true,"github_reviewed_at":"2026-03-18T16:10:26Z","cwe_ids":["CWE-79"],"severity":"MODERATE"},"references":[{"type":"WEB","url":"https://github.com/pdfme/pdfme/security/advisories/GHSA-87v3-4cfp-cm76"},{"type":"PACKAGE","url":"https://github.com/pdfme/pdfme"}],"affected":[{"package":{"name":"@pdfme/schemas","ecosystem":"npm","purl":"pkg:npm/%40pdfme/schemas"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"5.5.9"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-87v3-4cfp-cm76/GHSA-87v3-4cfp-cm76.json","last_known_affected_version_range":"\u003c= 5.5.8"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"}]}