diff --git a/blocksuite/affine/blocks/root/package.json b/blocksuite/affine/blocks/root/package.json
index 2ad087624c..53dabe76d8 100644
--- a/blocksuite/affine/blocks/root/package.json
+++ b/blocksuite/affine/blocks/root/package.json
@@ -43,7 +43,7 @@
"@blocksuite/store": "workspace:*",
"@preact/signals-core": "^1.8.0",
"@types/lodash-es": "^4.17.12",
- "dompurify": "^3.3.0",
+ "dompurify": "^3.4.11",
"html2canvas": "^1.4.1",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
diff --git a/blocksuite/affine/shared/package.json b/blocksuite/affine/shared/package.json
index d712d7d708..0c845fc069 100644
--- a/blocksuite/affine/shared/package.json
+++ b/blocksuite/affine/shared/package.json
@@ -23,7 +23,7 @@
"@types/lodash-es": "^4.17.12",
"@types/mdast": "^4.0.4",
"bytes": "^3.1.2",
- "dompurify": "^3.3.0",
+ "dompurify": "^3.4.11",
"fractional-indexing": "^3.2.0",
"lit": "^3.2.0",
"lodash-es": "^4.17.23",
@@ -46,6 +46,7 @@
"remark-parse": "^11.0.0",
"remark-stringify": "^11.0.0",
"rxjs": "^7.8.2",
+ "tldts": "^7.0.19",
"ts-pattern": "^5.1.0",
"unified": "^11.0.5",
"unist-util-visit": "^5.0.0",
diff --git a/blocksuite/affine/shared/src/__tests__/utils/svg.unit.spec.ts b/blocksuite/affine/shared/src/__tests__/utils/svg.unit.spec.ts
new file mode 100644
index 0000000000..f6ad9de0d3
--- /dev/null
+++ b/blocksuite/affine/shared/src/__tests__/utils/svg.unit.spec.ts
@@ -0,0 +1,185 @@
+/**
+ * @vitest-environment happy-dom
+ */
+import { describe, expect, test } from 'vitest';
+
+import { sanitizeSvg } from '../../utils/svg.js';
+
+type HappyDOMWindow = Window & {
+ happyDOM: {
+ setURL: (url: string) => void;
+ };
+};
+
+function setLocation(url: string) {
+ (window as unknown as HappyDOMWindow).happyDOM.setURL(url);
+}
+
+function svgDataUrl(svg: string) {
+ const bytes = new TextEncoder().encode(svg);
+ let binary = '';
+ bytes.forEach(byte => {
+ binary += String.fromCharCode(byte);
+ });
+ return `data:image/svg+xml;base64,${btoa(binary)}`;
+}
+
+function decodeSvgDataUrl(dataUrl: string) {
+ const base64 = dataUrl.split(',')[1];
+ return new TextDecoder().decode(
+ Uint8Array.from(atob(base64), char => char.charCodeAt(0))
+ );
+}
+
+describe('sanitizeSvg', () => {
+ test('wraps DOMPurify svg fragments back into an svg root', () => {
+ const sanitized = sanitizeSvg(
+ ''
+ );
+
+ expect(sanitized).toContain('