diff --git a/.changeset/fix-that-thing.md b/.changeset/fix-that-thing.md
new file mode 100644
index 0000000000..aa32c4dce8
--- /dev/null
+++ b/.changeset/fix-that-thing.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: always escape option body in SSR
\ No newline at end of file
diff --git a/packages/svelte/src/internal/server/renderer.js b/packages/svelte/src/internal/server/renderer.js
index 9df914b35a..7f9a922f33 100644
--- a/packages/svelte/src/internal/server/renderer.js
+++ b/packages/svelte/src/internal/server/renderer.js
@@ -12,6 +12,7 @@ import { get_render_context, with_render_context, init_render_context } from './
import { sha256 } from './crypto.js';
import * as devalue from 'devalue';
import { noop } from '../shared/utils.js';
+import { escape_html } from '../../escaping.js';
/** @typedef {'head' | 'body'} RendererType */
/** @typedef {{ [key in RendererType]: string }} AccumulatedContent */
@@ -275,7 +276,7 @@ export class Renderer {
renderer.#out.push(' selected=""');
}
- renderer.#out.push(`>${body}${is_rich ? '' : ''}`);
+ renderer.#out.push(`>${escape_html(body)}${is_rich ? '' : ''}`);
// super edge case, but may as well handle it
if (head) {
diff --git a/packages/svelte/tests/server-side-rendering/samples/option-body-escaped/_expected.html b/packages/svelte/tests/server-side-rendering/samples/option-body-escaped/_expected.html
new file mode 100644
index 0000000000..f1f609d095
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/option-body-escaped/_expected.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/svelte/tests/server-side-rendering/samples/option-body-escaped/main.svelte b/packages/svelte/tests/server-side-rendering/samples/option-body-escaped/main.svelte
new file mode 100644
index 0000000000..ab28f8bda5
--- /dev/null
+++ b/packages/svelte/tests/server-side-rendering/samples/option-body-escaped/main.svelte
@@ -0,0 +1,6 @@
+
+