VYPR
Moderate severityOSV Advisory· Published Jan 15, 2026· Updated Jan 15, 2026

Svelte 5.46.0 - Hydratable Key Script-Breakout XSS (SSR)

CVE-2025-15265

Description

An SSR XSS exists in async hydration when attacker‑controlled keys are passed to hydratable. The key is embedded inside a <script> block without HTML‑safe escaping, allowing </script> to terminate the script and inject arbitrary JavaScript. This enables remote script execution in users' browsers, with potential for session theft and account compromise. This issue affects Svelte: from 5.46.0 before 5.46.3.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
sveltenpm
>= 5.46.0, < 5.46.45.46.4

Affected products

1

Patches

1
ef81048e2388

Merge commit from fork

https://github.com/sveltejs/svelteElliott JohnsonJan 15, 2026via ghsa
4 files changed · +30 1
  • .changeset/tasty-vans-repeat.md+5 0 added
    @@ -0,0 +1,5 @@
    +---
    +'svelte': patch
    +---
    +
    +fix: use `devalue.uneval` to serialize `hydratable` keys
    
  • packages/svelte/src/internal/server/renderer.js+2 1 modified
    @@ -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();`;
    
  • packages/svelte/tests/runtime-runes/samples/hydratable-script-escape/_config.js+14 0 added
    @@ -0,0 +1,14 @@
    +import { test } from '../../test';
    +
    +export default test({
    +	skip_no_async: true,
    +	mode: ['hydrate'],
    +
    +	props: {
    +		key: '</script><script>throw new Error("pwned")</script>'
    +	},
    +
    +	async test() {
    +		// this test will fail when evaluating the `head` script if the vulnerability is present
    +	}
    +});
    
  • packages/svelte/tests/runtime-runes/samples/hydratable-script-escape/main.svelte+9 0 added
    @@ -0,0 +1,9 @@
    +<script>
    +	import { hydratable } from "svelte";
    +
    +	const { key } = $props();
    +
    +	const value = await hydratable(key, () => Promise.resolve('safe'));
    +</script>
    +
    +<p>{value}</p>
    

Vulnerability mechanics

Synthesis attempt was rejected by the grounding validator. Re-run pending.

References

6

News mentions

0

No linked articles in our index yet.