Unescaped Text in `ReactEmailExporter` Causing HTML Injection / XSS and Output Corruption
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 85/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- react, typescript
- Lĩnh vực
- security
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.
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 💖
- Ngôn ngữ chính
- TypeScript
- Star
- 10.2k
- Fork
- 772
- Merge trung bình
- 7 ngày 21 giờ
- Pull request đã merge (30 ngày)
- 19
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.
Issue khác của TypeCellOS/BlockNote
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
TypeCellOS/BlockNote#3098 ·
-
Off-By-One `RangeError` Crash and Text Duplication in `StyleManager.editLink` and `deleteLink` Đang mởneeds-triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
TypeCellOS/BlockNote#3073 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
TypeCellOS/BlockNote#2949 · 1 bình luận ·
-
a11y
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
TypeCellOS/BlockNote#2855 ·
-
a11y
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
TypeCellOS/BlockNote#2829 · 1 bình luận ·
Tất cả issue của TypeCellOS/BlockNote
Issue tương tự
-
S: triage
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 85/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
-
fix(errors): EHOSTUNREACH from a happy-eyeballs connect is reported as a resolver error (STAMP-80) Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 90/100
snapshot-labs/stamp#666 ·
-
fix(api): prevent leaderboard SSE heartbeat from starting after disconnect during initial load Đang mởbug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
GauravKarakoti/SecureFlow#1070 · 1 bình luận ·
-
feature:Languages/Translations good first issue ready Web
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
digitalfabrik/integreat-app#4394 ·