Svelte SSR attribute spreading includes inherited properties from prototype chain
Description
svelte performance oriented web framework. Prior to 5.51.5, in server-side rendering, attribute spreading on elements (e.g. <div {...attrs}>) enumerates inherited properties from the object's prototype chain rather than only own properties. In environments where Object.prototype has already been polluted — a precondition outside of Svelte's control — this can cause unexpected attributes to appear in SSR output or cause SSR to throw errors. Client-side rendering is not affected. This vulnerability is fixed in 5.51.5.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
sveltenpm | < 5.51.5 | 5.51.5 |
Affected products
1Patches
173098bb26c6fMerge commit from fork
9 files changed · +79 −6
.changeset/all-pandas-appear.md+5 −0 added@@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: check to make sure `svelte:element` tags are valid during SSR
documentation/docs/98-reference/.generated/server-errors.md+8 −0 modified@@ -16,6 +16,14 @@ Encountered asynchronous work while rendering synchronously. You (or the framework you're using) called [`render(...)`](svelte-server#render) with a component containing an `await` expression. Either `await` the result of `render` or wrap the `await` (or the component containing it) in a [`<svelte:boundary>`](svelte-boundary) with a `pending` snippet. +### dynamic_element_invalid_tag + +``` +`<svelte:element this="%tag%">` is not a valid element name — the element will not be rendered +``` + +The value passed to the `this` prop of `<svelte:element>` must be a valid HTML element, SVG element, MathML element, or custom element name. A value containing invalid characters (such as whitespace or special characters) was provided, which could be a security risk. Ensure only valid tag names are passed. + ### html_deprecated ```
packages/svelte/messages/server-errors/errors.md+6 −0 modified@@ -10,6 +10,12 @@ Some platforms require configuration flags to enable this API. Consult your plat You (or the framework you're using) called [`render(...)`](svelte-server#render) with a component containing an `await` expression. Either `await` the result of `render` or wrap the `await` (or the component containing it) in a [`<svelte:boundary>`](svelte-boundary) with a `pending` snippet. +## dynamic_element_invalid_tag + +> `<svelte:element this="%tag%">` is not a valid element name — the element will not be rendered + +The value passed to the `this` prop of `<svelte:element>` must be a valid HTML element, SVG element, MathML element, or custom element name. A value containing invalid characters (such as whitespace or special characters) was provided, which could be a security risk. Ensure only valid tag names are passed. + ## html_deprecated > The `html` property of server render results has been deprecated. Use `body` instead.
packages/svelte/src/compiler/phases/1-parse/state/element.js+11 −4 modified@@ -2,7 +2,7 @@ /** @import { Location } from 'locate-character' */ /** @import { AST } from '#compiler' */ /** @import { Parser } from '../index.js' */ -import { is_void } from '../../../../utils.js'; +import { is_void, REGEX_VALID_TAG_NAME } from '../../../../utils.js'; import read_expression from '../read/expression.js'; import { read_script } from '../read/script.js'; import read_style from '../read/style.js'; @@ -24,8 +24,15 @@ const regex_whitespace_or_slash_or_closing_tag = /(\s|\/|>)/; const regex_token_ending_character = /[\s=/>"']/; const regex_starts_with_quote_characters = /^["']/; const regex_attribute_value = /^(?:"([^"]*)"|'([^'])*'|([^>\s]+))/; -const regex_valid_element_name = - /^(?:![a-zA-Z]+|[a-zA-Z](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?|[a-zA-Z][a-zA-Z0-9]*:[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9])$/; +/** @param {string} name */ +function is_valid_element_name(name) { + // DOCTYPE (e.g. !DOCTYPE) + if (/^![a-zA-Z]+$/.test(name)) return true; + // svelte:* meta tags (e.g. svelte:element, svelte:head) + if (/^[a-zA-Z][a-zA-Z0-9]*:[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9]$/.test(name)) return true; + // standard HTML/SVG/MathML elements and custom elements + return REGEX_VALID_TAG_NAME.test(name); +} export const regex_valid_component_name = // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers adjusted for our needs // (must start with uppercase letter if no dots, can contain dots) @@ -134,7 +141,7 @@ export default function element(parser) { e.svelte_meta_invalid_tag(bounds, list(Array.from(meta_tags.keys()))); } - if (!regex_valid_element_name.test(tag.name) && !regex_valid_component_name.test(tag.name)) { + if (!is_valid_element_name(tag.name) && !regex_valid_component_name.test(tag.name)) { // <div. -> in the middle of typing -> allow in loose mode if (!parser.loose || !tag.name.endsWith('.')) { const bounds = { start: start + 1, end: start + 1 + tag.name.length };
packages/svelte/src/internal/server/errors.js+13 −0 modified@@ -14,6 +14,19 @@ export function async_local_storage_unavailable() { throw error; } +/** + * `<svelte:element this="%tag%">` is not a valid element name — the element will not be rendered + * @param {string} tag + * @returns {never} + */ +export function dynamic_element_invalid_tag(tag) { + const error = new Error(`dynamic_element_invalid_tag\n\`<svelte:element this="${tag}">\` is not a valid element name — the element will not be rendered\nhttps://svelte.dev/e/dynamic_element_invalid_tag`); + + error.name = 'Svelte error'; + + throw error; +} + /** * Encountered asynchronous work while rendering synchronously. * @returns {never}
packages/svelte/src/internal/server/index.js+10 −2 modified@@ -13,9 +13,14 @@ import { } from '../../constants.js'; import { escape_html } from '../../escaping.js'; import { DEV } from 'esm-env'; -import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN, BLOCK_OPEN_ELSE } from './hydration.js'; +import { EMPTY_COMMENT, BLOCK_OPEN, BLOCK_OPEN_ELSE } from './hydration.js'; import { validate_store } from '../shared/validate.js'; -import { is_boolean_attribute, is_raw_text_element, is_void } from '../../utils.js'; +import { + is_boolean_attribute, + is_raw_text_element, + is_void, + REGEX_VALID_TAG_NAME +} from '../../utils.js'; import { Renderer } from './renderer.js'; import * as e from './errors.js'; @@ -35,6 +40,9 @@ export function element(renderer, tag, attributes_fn = noop, children_fn = noop) renderer.push('<!---->'); if (tag) { + if (!REGEX_VALID_TAG_NAME.test(tag)) { + e.dynamic_element_invalid_tag(tag); + } renderer.push(`<${tag}`); attributes_fn(); renderer.push(`>`);
packages/svelte/src/utils.js+13 −0 modified@@ -480,6 +480,19 @@ export function is_raw_text_element(name) { return RAW_TEXT_ELEMENTS.includes(/** @type {typeof RAW_TEXT_ELEMENTS[number]} */ (name)); } +// Matches valid HTML/SVG/MathML element names and custom element names. +// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name +// +// Standard elements: ASCII alpha start, followed by ASCII alphanumerics. +// Custom elements: ASCII alpha start, followed by any mix of PCENChar (which +// includes ASCII alphanumerics, `-`, `.`, `_`, and specified Unicode ranges), +// with at least one hyphen required somewhere after the first character. +// +// Rejects strings containing whitespace, quotes, angle brackets, slashes, equals, +// or other characters that could break out of a tag-name token and enable markup injection. +export const REGEX_VALID_TAG_NAME = + /^[a-zA-Z][a-zA-Z0-9]*(-[a-zA-Z0-9.\-_\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}]+)*$/u; + /** * Prevent devtools trying to make `location` a clickable link by inserting a zero-width space * @template {string | undefined} T
packages/svelte/tests/server-side-rendering/samples/dynamic-element-xss-prevention/_config.js+8 −0 added@@ -0,0 +1,8 @@ +import { test } from '../../test'; + +export default test({ + props: { + tag: 'svg onload=alert(1)' + }, + error: 'dynamic_element_invalid_tag' +});
packages/svelte/tests/server-side-rendering/samples/dynamic-element-xss-prevention/main.svelte+5 −0 added@@ -0,0 +1,5 @@ +<script> + let { tag } = $props(); +</script> + +<svelte:element this={tag}>ok</svelte:element>
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-crpf-4hrx-3jrpghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-27125ghsaADVISORY
- github.com/sveltejs/svelte/commit/73098bb26c6f06e7fd1b0746d817d2c5ee90755fghsax_refsource_MISCWEB
- github.com/sveltejs/svelte/releases/tag/svelte@5.51.5ghsax_refsource_MISCWEB
- github.com/sveltejs/svelte/security/advisories/GHSA-crpf-4hrx-3jrpghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.