TypeCellOS / TypeCellOS/BlockNote
Unescaped Text in `ReactEmailExporter` Causing HTML Injection / XSS and Output Corruption
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- TypeScript
- Star
- 10.2k
- Fork
- 772
- Merge trung bình
- 3 ngày 11 giờ
- Pull request đã merge (30 ngày)
- 17
Mô tả
What’s broken?
In @blocknote/xl-email-exporter, user text content is injected directly into dangerouslySetInnerHTML without HTML entity encoding.
Inside packages/xl-email-exporter/src/react-email/reactEmailExporter.tsx, the method transformStyledText converts newlines to <br /> but passes the raw unescaped styledText.text directly into the DOM:
public transformStyledText(styledText: StyledText<S>) {
const stylesArray = this.mapStyles(styledText.styles);
const styles = Object.assign({}, ...stylesArray);
return (
<span
style={styles}
dangerouslySetInnerHTML={{
__html: styledText.text.replace(/\n/g, "<br />"),
}}
/>
);
}
This causes two critical defects:
- Security (HTML / XSS Injection): If an application exports user-generated BlockNote documents into emails, malicious payloads (e.g. <img src=x onerror=...>, <script>, or phishing HTML structures) are rendered verbatim in the generated email.
- Rendering & Layout Corruption: Standard text containing mathematical or programming comparison operators (e.g., 5 < 10 && 10 > 5, array[i < 5], or <CustomComponent> in documentation) is parsed by the email engine as raw HTML elements. This causes the text to either be stripped/hidden or break the email's DOM layout.
### What did you expect to happen?
All raw text characters (such as <, >, &, ", and ') should be properly HTML-escaped before inserting <br /> tags into dangerouslySetInnerHTML, ensuring:
- Safe rendering of untrusted user content.
- Visual preservation of literal < and > characters in exported email clients.
### Steps to reproduce
import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core";
import { ReactEmailExporter, reactEmailDefaultSchemaMappings } from "@blocknote/xl-email-exporter";
const schema = BlockNoteSchema.create({ blockSpecs: defaultBlockSpecs });
const exporter = new ReactEmailExporter(schema, reactEmailDefaultSchemaMappings);
const blocks = [
{
id: "block-1",
type: "paragraph" as const,
props: {},
content: [
{
type: "text" as const,
text: "Condition check: x < 10 & y > 20, or <script>alert(1)</script>",
styles: {},
},
],
children: [],
},
];
const emailHtml = await exporter.toReactEmailDocument(blocks);
console.log(emailHtml);
### BlockNote version
Version: 0.54.0 (and main branch) Package: @blocknote/xl-email-exporter
### Environment
OS: Any (Windows / macOS / Linux) Node.js: >=18.0.0 React: 18.x / 19.x Browser/Runtime: Node.js, Next.js, or browser export environments
### Additional context
Proposed Fix
Add an escapeHtml utility function and sanitize styledText.text before newline substitution:
--- a/packages/xl-email-exporter/src/react-email/reactEmailExporter.tsx
+++ b/packages/xl-email-exporter/src/react-email/reactEmailExporter.tsx
@@ -24,6 +24,15 @@ import React, { CSSProperties } from "react";
+function escapeHtml(str: string): string {
- return str
- .replace(/&/g, "&")
- .replace(/</g, "<")
- .replace(/>/g, ">")
- .replace(/"/g, """)
- .replace(/'/g, "'");
+}
export class ReactEmailExporter<
B extends BlockSchema,
S extends StyleSchema,
@@ -66,7 +75,7 @@ export class ReactEmailExporter<
<span
style={styles}
dangerouslySetInnerHTML={{
-
__html: styledText.text.replace(/\n/g, "<br />"),
-
);__html: escapeHtml(styledText.text).replace(/\n/g, "<br />"), }} />
### Contribution
- [ ] I'd be interested in contributing a fix for this issue
### Sponsor
- [ ] I'm a [sponsor](https://www.blocknotejs.org/pricing) and would appreciate if you could look into this sooner than later 💖
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Bắt đầu tại packages/xl-email-exporter/src/react-email/reactEmailExporter.tsx, ở ReactEmailExporter.transformStyledText, và kiểm tra cách styledText.text được truyền vào dangerouslySetInnerHTML. Đảm bảo văn bản thô được escape trước khi chuyển đổi ký tự xuống dòng thành br, sau đó xác minh rằng bản tái hiện giữ nguyên các toán tử so sánh theo nghĩa đen và không render HTML hoặc script được chèn vào.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- react, typescript
- Lĩnh vực
- security
- Loại issue
- Lỗi
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức phù hợp với người mới
- 85/100