diff --git a/.changeset/late-foxes-clean.md b/.changeset/late-foxes-clean.md new file mode 100644 index 000000000000..1ec0de03dc6b --- /dev/null +++ b/.changeset/late-foxes-clean.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fixes shadow hydration escaping diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 4d6f8ac71ff4..67891e389f6c 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -2,7 +2,7 @@ import devalue from 'devalue'; import { readable, writable } from 'svelte/store'; import { coalesce_to_error } from '../../../utils/error.js'; import { hash } from '../../hash.js'; -import { escape_html_attr } from '../../../utils/escape.js'; +import { escape_html_attr, escape_json_in_html } from '../../../utils/escape.js'; import { s } from '../../../utils/misc.js'; import { create_prerendering_url_proxy } from './utils.js'; import { Csp, csp_ready } from './csp.js'; @@ -261,7 +261,7 @@ export async function render_response({ if (shadow_props) { // prettier-ignore - body += ``; + body += ``; } } diff --git a/packages/kit/src/utils/escape.js b/packages/kit/src/utils/escape.js index 020972dff6ae..4a52cbf83fb8 100644 --- a/packages/kit/src/utils/escape.js +++ b/packages/kit/src/utils/escape.js @@ -1,6 +1,5 @@ /** @type {Record} */ -const escape_json_string_in_html_dict = { - '"': '\\"', +const escape_json_in_html_dict = { '<': '\\u003C', '>': '\\u003E', '/': '\\u002F', @@ -15,7 +14,24 @@ const escape_json_string_in_html_dict = { '\u2029': '\\u2029' }; -/** @param {string} str */ +/** @type {Record} */ +const escape_json_string_in_html_dict = { + '"': '\\"', + ...escape_json_in_html_dict +}; + +/** + * Escape a stringified JSON object that's going to be embedded in a `' + }; + return { + body: { user } + }; +} diff --git a/packages/kit/test/apps/basics/src/routes/xss/shadow.svelte b/packages/kit/test/apps/basics/src/routes/xss/shadow.svelte new file mode 100644 index 000000000000..8ad2b30f8e4f --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/xss/shadow.svelte @@ -0,0 +1,6 @@ + +

user.name is {user.name}

+ diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index 2114eb7656ac..bfc5e20aaeea 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -1992,4 +1992,14 @@ test.describe.parallel('XSS', () => { // @ts-expect-error - check global injected variable expect(await page.evaluate(() => window.pwned)).toBeUndefined(); }); + + test('no xss via shadow endpoint', async ({ page }) => { + await page.goto('/xss/shadow'); + + // @ts-expect-error - check global injected variable + expect(await page.evaluate(() => window.pwned)).toBeUndefined(); + expect(await page.textContent('h1')).toBe( + 'user.name is ' + ); + }); });