From ef81048e238844b729942441541d6dcfe6c8ccca Mon Sep 17 00:00:00 2001 From: Elliott Johnson Date: Thu, 15 Jan 2026 09:13:33 -0700 Subject: [PATCH] Merge commit from fork * fix: Use `devalue.uneval` to serialize `hydratable` keys * oops --- .changeset/tasty-vans-repeat.md | 5 +++++ packages/svelte/src/internal/server/renderer.js | 3 ++- .../samples/hydratable-script-escape/_config.js | 14 ++++++++++++++ .../samples/hydratable-script-escape/main.svelte | 9 +++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 .changeset/tasty-vans-repeat.md create mode 100644 packages/svelte/tests/runtime-runes/samples/hydratable-script-escape/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/hydratable-script-escape/main.svelte diff --git a/.changeset/tasty-vans-repeat.md b/.changeset/tasty-vans-repeat.md new file mode 100644 index 0000000000..d95e8de7e7 --- /dev/null +++ b/.changeset/tasty-vans-repeat.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: use `devalue.uneval` to serialize `hydratable` keys diff --git a/packages/svelte/src/internal/server/renderer.js b/packages/svelte/src/internal/server/renderer.js index 1ecc90ba1f..82f8025759 100644 --- a/packages/svelte/src/internal/server/renderer.js +++ b/packages/svelte/src/internal/server/renderer.js @@ -10,6 +10,7 @@ import { BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js'; import { attributes } from './index.js'; import { get_render_context, with_render_context, init_render_context } from './render-context.js'; import { sha256 } from './crypto.js'; +import * as devalue from 'devalue'; /** @typedef {'head' | 'body'} RendererType */ /** @typedef {{ [key in RendererType]: string }} AccumulatedContent */ @@ -669,7 +670,7 @@ export class Renderer { for (const p of v.promises) await p; } - entries.push(`[${JSON.stringify(k)},${v.serialized}]`); + entries.push(`[${devalue.uneval(k)},${v.serialized}]`); } let prelude = `const h = (window.__svelte ??= {}).h ??= new Map();`; diff --git a/packages/svelte/tests/runtime-runes/samples/hydratable-script-escape/_config.js b/packages/svelte/tests/runtime-runes/samples/hydratable-script-escape/_config.js new file mode 100644 index 0000000000..bc51795565 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/hydratable-script-escape/_config.js @@ -0,0 +1,14 @@ +import { test } from '../../test'; + +export default test({ + skip_no_async: true, + mode: ['hydrate'], + + props: { + key: '' + }, + + async test() { + // this test will fail when evaluating the `head` script if the vulnerability is present + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/hydratable-script-escape/main.svelte b/packages/svelte/tests/runtime-runes/samples/hydratable-script-escape/main.svelte new file mode 100644 index 0000000000..1327492190 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/hydratable-script-escape/main.svelte @@ -0,0 +1,9 @@ + + +

{value}