From 0df5abcae223058ceb95491470372065fb87951d Mon Sep 17 00:00:00 2001 From: Paolo Ricciuti Date: Wed, 25 Feb 2026 17:14:55 +0100 Subject: [PATCH] Merge commit from fork * fix: escape `innerText` and `textContent` bindings of `contenteditable` * fix: better if else structure --- .changeset/bindings-xss-fix.md | 5 +++++ .../phases/3-transform/server/visitors/shared/element.js | 8 ++++++-- .../contenteditable-bindings-escaped/_expected.html | 1 + .../samples/contenteditable-bindings-escaped/main.svelte | 7 +++++++ 4 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 .changeset/bindings-xss-fix.md create mode 100644 packages/svelte/tests/server-side-rendering/samples/contenteditable-bindings-escaped/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/contenteditable-bindings-escaped/main.svelte diff --git a/.changeset/bindings-xss-fix.md b/.changeset/bindings-xss-fix.md new file mode 100644 index 0000000000..b2b58e5869 --- /dev/null +++ b/.changeset/bindings-xss-fix.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: escape `innerText` and `textContent` bindings of `contenteditable` diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js index ac02a6b918..9c37b2fffe 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js @@ -123,9 +123,13 @@ export function build_element_attributes(node, context, transform) { expression = transform(expression, attribute.metadata.expression); - if (is_content_editable_binding(attribute.name)) { + if (attribute.name === 'innerHTML') { + // innerHTML is the only binding we don't escape content = expression; - } else if (attribute.name === 'value' && node.name === 'textarea') { + } else if ( + is_content_editable_binding(attribute.name) || + (attribute.name === 'value' && node.name === 'textarea') + ) { content = b.call('$.escape', expression); } else if (attribute.name === 'group' && attribute.expression.type !== 'SequenceExpression') { const value_attribute = /** @type {AST.Attribute | undefined} */ ( diff --git a/packages/svelte/tests/server-side-rendering/samples/contenteditable-bindings-escaped/_expected.html b/packages/svelte/tests/server-side-rendering/samples/contenteditable-bindings-escaped/_expected.html new file mode 100644 index 0000000000..648895aca6 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/contenteditable-bindings-escaped/_expected.html @@ -0,0 +1 @@ +
<script>alert('pwnd')</script>
<script>alert('pwnd')</script>
\ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/contenteditable-bindings-escaped/main.svelte b/packages/svelte/tests/server-side-rendering/samples/contenteditable-bindings-escaped/main.svelte new file mode 100644 index 0000000000..e5ccb4b6db --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/contenteditable-bindings-escaped/main.svelte @@ -0,0 +1,7 @@ + + +
+
+