Serialize JavaScript: Cross-site scripting (XSS) via unescaped </script> in serialized function bodies
Impact
serialize-javascript escapes its output so it is safe to embed inside a
<script> element. In 7.1.1 that guarantee does not hold for function
values: a crafted function body can carry a literal, unescaped </script>
into the output, terminating the script element early so the remainder is
parsed as HTML.
SCRIPT_CLOSE_REGEXP used <\/script[^>]*> as its first alternative. The
character class excludes only >, so a single match could run from one
</script all the way to the next > anywhere in the source — swallowing a
second, complete </script> along the way. Only one replacement is emitted
per match, and the plain-code branch neutralizes just the leading <
('< ' + match.slice(1)), so the swallowed tag was re-emitted verbatim.
Reaching that shape requires </script in code position, which is legal
JavaScript: x</script=+/ parses as x < /script=+/, a comparison against a
regex literal.
const serialize = require('serialize-javascript');
const src = "function f(x){ return x</script=+/ + '</script><img src=x onerror=alert(1)>' }";
const out = serialize({ h: new Function('return ' + src)() });
// {"h":function f(x){ return x< /script=+/ + '</script><img src=x onerror=alert(1)>' }}
Embedded as the README documents (<script>window.S = <%= serialize(state) %></script>)
and parsed by Chromium, the script element ends at the injected tag and the
<img> becomes a live DOM node with its onerror handler executing in the
page origin.
Only the function path is affected. The same payload passed as data is
escaped correctly, and options.isJSON / non-function values are unaffected.
Patches
Fixed in 7.1.2. The wildcard now excludes < as well as >
([^<>]*), so a match can never reach past a second <. Every </script
in the source therefore either begins its own match or is followed by a
character the HTML tokenizer does not accept as ending a tag name — it ends
the tag name only on TAB, LF, FF, CR, SPACE, / or >, and emits anything
else as text.
Workarounds
Upgrade to 7.1.2. If you cannot upgrade, 7.1.0 and earlier are unaffected, or avoid serializing functions whose source text is attacker-influenced.
Regression note
This is a regression specific to 7.1.1, not a long-standing issue. 7.1.0 and
earlier applied the same wildcard but escaped the entire match, so no tag
survived. Downstream scanners defaulting to a >= 7.1.0 range would be
overly broad.
Affected software
Click a technology to see all vulnerabilities affecting it.
| Platform | Package | Vulnerable versions | Fixed in |
|---|---|---|---|
| npm package | serialize-javascript | >= 7.1.1, < 7.1.2 | 7.1.2 |
References
- https://github.com/yahoo/serialize-javascript/security/advisories/GHSA-gfhx-hw2g-v5hg
- https://nvd.nist.gov/vuln/detail/CVE-2026-97711
- https://github.com/yahoo/serialize-javascript/commit/2bdbaaff9cb8a4639135eb24cfcd383d3fefb534
- https://github.com/yahoo/serialize-javascript
- https://github.com/yahoo/serialize-javascript/releases/tag/v7.1.2
- CVSS vector
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N- Weakness (CWE)
CWE-79, CWE-80- Platform
- npm
- Package
serialize-javascript- Disclosed
- 30.09.2026
- Updated
- 11.10.2026 10:22
Sources and licenses
OSV / GitHub Advisory Database - GHSA-gfhx-hw2g-v5hg - CC BY 4.0
Vulnerability history: serialize-javascript
All published vulnerabilities of this package (npm package), newest first. Shows whether this is a one-off slip or a package that needs watching.
- Total entries
- 6
- First → latest
- 12.2019 09.2026
- Without a fix
- 0
- Exploited in attacks
- 0
Severity breakdown
- High 2
- Medium 3
- Low 1
Disclosures per year
- 2019: 1
- 2020: 1
- 2021: 0
- 2022: 0
- 2023: 0
- 2024: 0
- 2025: 1
- 2026: 3
-
Serialize JavaScript: Cross-site scripting (XSS) via unescaped </script> in serialized function bodies this entry
Know which vulnerabilities affect your application
With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.