TypeCellOS / TypeCellOS/BlockNote

Unescaped Text in `ReactEmailExporter` Causing HTML Injection / XSS and Output Corruption

Đang mở Phù hợp với người mới
#3,072 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

needs-triage
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

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. 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

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.