Unescaped Text in `ReactEmailExporter` Causing HTML Injection / XSS and Output Corruption
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 85/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- react, typescript
- Ambito
- security
Direzione di ricerca
Inizia in packages/xl-email-exporter/src/react-email/reactEmailExporter.tsx, all’interno di ReactEmailExporter.transformStyledText, e verifica come styledText.text viene passato a dangerouslySetInnerHTML. Assicurati che il testo grezzo venga sottoposto a escape prima della conversione dei caratteri di nuova riga in br, quindi verifica che la riproduzione conservi gli operatori di confronto letterali e non esegua il rendering di HTML o script iniettati.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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 💖
- Lingua principale
- TypeScript
- Stelle
- 10.2k
- Fork
- 772
- Merge medio
- 3g 11h
- PR unite (30g)
- 17
Guida per i contributori
Apri la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di TypeCellOS/BlockNote
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
TypeCellOS/BlockNote#3098 ·
-
Off-By-One `RangeError` Crash and Text Duplication in `StyleManager.editLink` and `deleteLink` Apertaneeds-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
TypeCellOS/BlockNote#3073 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
TypeCellOS/BlockNote#2949 · 1 commento ·
-
a11y
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
TypeCellOS/BlockNote#2855 ·
-
a11y
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
TypeCellOS/BlockNote#2829 · 1 commento ·
Tutte le issue di TypeCellOS/BlockNote
Issue simili
-
Type/Bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
OpenNSW/nsw-srilanka#497 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
0xMiden/bridge-portal#132 ·
-
react-doctor severity:warning tech-debt
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 88/100
digidem/comapeo-cloud-app#403 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100