Skip to content

feat(tldraw): sanitize SVG content with DOMPurify#7880

Merged
MitjaBezensek merged 9 commits intomainfrom
mitja/sanitize
Feb 11, 2026
Merged

feat(tldraw): sanitize SVG content with DOMPurify#7880
MitjaBezensek merged 9 commits intomainfrom
mitja/sanitize

Conversation

@MitjaBezensek
Copy link
Copy Markdown
Contributor

@MitjaBezensek MitjaBezensek commented Feb 10, 2026

Closes #7876

SVG files pasted/dropped into tldraw had no sanitization. The main risk is defaultHandleExternalSvgTextContent() which parses SVG with DOMParser, temporarily appends to document.body for measurement (potential script execution), and stores raw SVG text as an asset. While <img> rendering sandboxes scripts, SVGs served from the same origin as the host app (e.g. via a same-domain asset store) can bypass this and execute arbitrary code. This PR adds defense-in-depth sanitization using DOMPurify — the industry-standard XSS sanitizer with first-class SVG support.

Sanitization is applied unconditionally in all SVG entry points:

  • defaultHandleExternalSvgTextContent — SVG text pasting
  • defaultHandleExternalFileAsset — SVG file drops
  • defaultHandleExternalFileContent — multi-file drops
  • defaultHandleExternalFileReplaceContent — image replacement

DOMPurify config uses USE_PROFILES: { svg: true, svgFilters: true } which allows safe SVG elements/attributes (including filter primitives like feGaussianBlur, feDropShadow, etc.) while stripping scripts, event handlers, javascript: URLs, foreignObject, etc. <use> and xlink:href are preserved for valid SVG features.

If sanitization strips all content (fully malicious SVG), the file is rejected with a clear error rather than silently creating a broken empty asset.

Bundle size impact: ~9 KB gzipped additional transfer for end users (DOMPurify is 62 KB raw / 17 KB gzipped).

Change type

  • improvement

Test plan

  1. yarn dev, paste SVG with <script>alert(1)</script> — should render without script
  2. Paste SVG with onload="alert(1)" attribute — should render without attribute
  3. Paste normal SVG with gradients/filters — should render correctly
  4. Drop SVG file onto canvas — should sanitize
  5. Paste a fully malicious SVG (only script content) — should get a clear error, not a blank asset
  • Unit tests
  • End to end tests

Release notes

  • Sanitize SVG content on paste and file drop using DOMPurify to prevent XSS

Note

Medium Risk
Touches external content ingestion and asset upload/replace flows, so regressions could affect SVG handling or asset hashing/identity. Security logic changes are beneficial but must be correct to avoid bypasses or false positives that reject valid SVGs.

Overview
Adds SVG sanitization to default external content ingestion paths so pasted SVG text and dropped/replaced SVG files are cleaned before parsing, hashing, previewing, uploading, or being written to the store; SVGs that sanitize down to no usable content are rejected.

Introduces a new internal sanitizer (utils/svg/sanitizeSvg.ts) with DOMPurify-like allowlists, URI protocol filtering, and basic CSS url()/@import stripping, and updates the Assets docs with a new Security section recommending separate-domain asset hosting for defense in depth.

Written by Cursor Bugbot for commit 4cb539b. This will update automatically on new commits. Configure here.

@huppy-bot huppy-bot bot added the improvement Product improvement label Feb 10, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Feb 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
examples Ready Ready Preview Feb 11, 2026 1:12pm
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
analytics Ignored Ignored Preview Feb 11, 2026 1:12pm
chat-template Ignored Ignored Preview Feb 11, 2026 1:12pm
tldraw-docs Ignored Ignored Preview Feb 11, 2026 1:12pm
tldraw-shader Ignored Ignored Preview Feb 11, 2026 1:12pm
workflow-template Ignored Ignored Preview Feb 11, 2026 1:12pm

Request Review

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Feb 10, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
image-pipeline-template 4cb539b Feb 11 2026, 01:09 PM

@tldraw tldraw deleted a comment from huppy-bot bot Feb 11, 2026
Replace the DOMPurify dependency with an in-house SVG sanitizer and lazy-load it where needed. dompurify was removed from packages/tldraw/package.json (and corresponding yarn.lock entries). The sanitizer implementation (packages/tldraw/src/lib/utils/svg/sanitizeSvg.ts) now parses SVG via DOMParser, whitelists allowed tags and attributes, validates URI attributes (blocking javascript:, data:, etc.), strips invisible whitespace used to bypass checks, and removes parse-error results. Call sites in defaultExternalContentHandlers now dynamically import defaultSanitizeSvg (reducing initial bundle size) and the file upload sanitizer was updated to use the new function.
@steveruizok steveruizok disabled auto-merge February 11, 2026 11:56
@vercel vercel bot temporarily deployed to Preview – workflow-template February 11, 2026 12:01 Inactive
@vercel vercel bot temporarily deployed to Preview – tldraw-shader February 11, 2026 12:01 Inactive
@vercel vercel bot temporarily deployed to Preview – chat-template February 11, 2026 12:01 Inactive
steveruizok and others added 2 commits February 11, 2026 12:11
Sanitize root SVG element attributes to prevent XSS via onload handlers.
Strip url() and @import from CSS in style elements and attributes.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Prevents fully-malicious SVGs like <svg><script>alert(1)</script></svg>
from passing through as empty <svg/> shells.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

'fespotlight',
'fetile',
'feturbulence',
])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing animate tag from SVG allowlist

Medium Severity

ALLOWED_TAGS includes animatecolor, animatemotion, and animatetransform but omits the base animate element — the most commonly used SVG animation primitive (e.g., <animate attributeName="opacity" ...>). The comment states this list is "Based on DOMPurify's SVG + SVG filter profiles," and DOMPurify does include animate. Valid SVGs using <animate> will silently have their animations stripped.

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

animate is included in the disallow list. bad bot 😡

@MitjaBezensek MitjaBezensek added this pull request to the merge queue Feb 11, 2026
Merged via the queue into main with commit fc141c1 Feb 11, 2026
19 of 20 checks passed
@MitjaBezensek MitjaBezensek deleted the mitja/sanitize branch February 11, 2026 13:36
github-merge-queue bot pushed a commit that referenced this pull request Feb 11, 2026
Reverts #7880 (SVG sanitization). Our SVG export relies on
`<foreignObject>` for text rendering (used by `RichTextSVG` in text,
geo, note, arrow shapes + the fallback HTML renderer in `getSvgJsx`).
The sanitizer correctly strips `foreignObject` (DOMPurify does the same
— it's in their `svgDisallowed` list), but this breaks copy/paste of our
own SVGs since all text content is lost.

Keeps the docs recommendation to host assets on a separate domain —
that's the primary defense against same-origin SVG attacks.

Closes #7876

### Change type

- [x] `improvement`

### Test plan

1. Copy as SVG, paste into tldraw — text should render correctly with
fonts
2. Drop SVG files onto canvas — should work as before

- [ ] Unit tests
- [ ] End to end tests

### Release notes

- Revert SVG sanitization that broke copy/paste of SVGs containing text
Uzay-G added a commit to fulcrumresearch/tldraw that referenced this pull request Feb 24, 2026
Add SVG sanitization using an allowlist derived from DOMPurify's SVG
profile. Strips disallowed elements and attributes, blocks javascript:
and data: URIs, and returns clean SVG markup.

Applied automatically through default external content handlers on paste,
drop, and file insert.

Upstream: tldraw#7880
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Product improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sanitize SVG content in the SDK to prevent XSS

2 participants