Skip to content

Commit 9fee861

Browse files
authored
fix(security): sanitize DOCX HTML output with DOMPurify to prevent XSS (#514)
Malicious DOCX files with embedded script/event-handler tags rendered via mammoth.convertToHtml() + dangerouslySetInnerHTML without sanitization could execute arbitrary JavaScript in the user's browser. Fix: Install DOMPurify and sanitize mammoth's HTML output before rendering, allowing only safe tags and attributes used by Word documents. FCE: Cross-site scripting (XSS) via uploaded DOCX files
1 parent bbf1320 commit 9fee861

3 files changed

Lines changed: 38 additions & 1 deletion

File tree

frontend/package-lock.json

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
},
1313
"dependencies": {
1414
"@reduxjs/toolkit": "^2.11.2",
15+
"@types/dompurify": "^3.0.5",
1516
"@xyflow/react": "^12.8.3",
1617
"antd": "^5.27.0",
18+
"dompurify": "^3.4.10",
1719
"i18next": "^25.8.0",
1820
"jssha": "^3.3.1",
1921
"jszip": "^3.10.1",

frontend/src/components/file-preview/DocxPreview.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useEffect, useState } from 'react';
2+
import DOMPurify from 'dompurify';
23
import mammoth from 'mammoth';
34

45
export interface DocxPreviewProps {
@@ -28,7 +29,15 @@ export const DocxPreview: React.FC<DocxPreviewProps> = ({
2829

2930
const arrayBuffer = await blob.arrayBuffer();
3031
const result = await mammoth.convertToHtml({ arrayBuffer });
31-
setHtml(result.value);
32+
// Sanitize HTML to prevent XSS from malicious DOCX files (FCE)
33+
const sanitized = DOMPurify.sanitize(result.value, {
34+
ALLOWED_TAGS: ['h1','h2','h3','h4','h5','h6','p','br','hr','ul','ol','li',
35+
'table','thead','tbody','tr','th','td','strong','em','b','i','u','s',
36+
'a','img','sup','sub','pre','code','blockquote','span','div'],
37+
ALLOWED_ATTR: ['href','target','src','alt','width','height','colspan',
38+
'rowspan','style','class','id','data-*'],
39+
});
40+
setHtml(sanitized);
3241
} catch (err) {
3342
setError('Failed to convert Word document');
3443
} finally {

0 commit comments

Comments
 (0)