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

Abierto Apto para principiantes
#3,072 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
85/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
react, typescript
Área
security

Línea de trabajo

Empieza en packages/xl-email-exporter/src/react-email/reactEmailExporter.tsx, en ReactEmailExporter.transformStyledText, e inspecciona cómo se pasa styledText.text a dangerouslySetInnerHTML. Asegúrate de que el texto sin procesar se escape antes de la conversión de saltos de línea a br y, después, verifica que la reproducción conserve los operadores de comparación literales y no renderice HTML ni scripts inyectados.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

needs-triage
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 💖
Lenguaje dominante
TypeScript
Estrellas
10.2k
Forks
772
Merge medio
7 d 21 h
PR fusionados (30 d)
19

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de TypeCellOS/BlockNote

Todos los issues de TypeCellOS/BlockNote

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.