CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-39315
6.1

CVE-2026-39315: Cross-Site Scripting Filter Bypass in Unhead useHeadSafe()

Alon Barad
Alon Barad
Software Engineer

Apr 10, 2026·6 min read·3 visits

PoC Available

Executive Summary (TL;DR)

A regular expression limitation in Unhead versions prior to 2.1.13 allows attackers to bypass XSS filters by padding HTML entities with leading zeros. This enables the execution of malicious javascript: URIs via the useHeadSafe() API.

The Unhead document head manager contains a security bypass vulnerability in the useHeadSafe() composable. An incomplete regular expression fails to decode HTML numeric character references padded with excessive leading zeros, allowing attackers to bypass protocol blocklists and achieve Cross-Site Scripting (XSS).

Vulnerability Overview

The unhead package is a document head manager widely utilized within the Vue.js and Nuxt.js ecosystems. The useHeadSafe() composable restricts document <head> injections to safe tags and attributes to prevent Cross-Site Scripting (XSS) vulnerabilities. This vulnerability, tracked as CVE-2026-39315, breaks this safety guarantee by allowing attackers to bypass internal protocol blocklists.

The underlying bug class is CWE-184 (Incomplete List of Disallowed Inputs). The sanitization logic fails to account for arbitrary padding in HTML numeric character references. By injecting excessive leading zeros, an attacker ensures the malicious input evades the intermediate sanitization phase while remaining perfectly valid for the downstream browser HTML parser.

The primary impact is Cross-Site Scripting (XSS) in applications leveraging Server-Side Rendering (SSR). An attacker can inject arbitrary JavaScript execution vectors through attributes that accept URIs, fundamentally undermining the stated purpose of the useHeadSafe() security API.

Root Cause Analysis

The flaw originates in the hasDangerousProtocol() function located within packages/unhead/src/plugins/safe.ts. This function evaluates strings for blocked URI schemes such as javascript:, data:, and vbscript:. To prevent simple obfuscation, the function attempts to decode HTML entities before executing the protocol string matching logic.

The decoding operation relies on two regular expressions: /&#x([0-9a-f]{1,6});?/gi for hexadecimal entities and /&#(\d{1,7});?/g for decimal entities. These patterns explicitly restrict the maximum length of the captured digits to 6 and 7 characters, respectively. The author likely derived these limits from standard Unicode codepoint limits without considering the HTML5 specification's handling of numeric entity padding.

According to the HTML5 specification, numeric character references can contain an arbitrary number of leading zeros. The browser parser seamlessly strips these zeros during the decoding phase. Because the unhead regular expressions enforce strict length caps, any padded entity exceeding the maximum character limit completely fails to match the regex. Consequently, the input string retains its encoded form during the sanitization phase, successfully evading the subsequent startsWith('javascript:') validation check.

Code Analysis

The vulnerability stems directly from the hardcoded quantifiers in the entity decoding logic. Prior to version 2.1.13, the string evaluation explicitly enforced maximum lengths. The function makeTagSafe() utilizes these evaluations to determine if a tag attribute contains a dangerous protocol.

When an attacker submits a payload containing &#0000000058;, the string length of the numeric segment is 10 digits. The HtmlEntityDec regex evaluates this input and returns no match due to the {1,7} constraint. The text remains unchanged, and the protocol check evaluates javascript&#0000000058;, returning false. The commit 961ea781e091853812ffe17f8cda17105d2d2299 addresses this by modifying the regular expression quantifiers.

// packages/unhead/src/plugins/safe.ts
 
- const HtmlEntityHex = /&#x([0-9a-f]{1,6});?/gi
- const HtmlEntityDec = /&#(\d{1,7});?/g
+ const HtmlEntityHex = /&#x([0-9a-f]+);?/gi
+ const HtmlEntityDec = /&#(\d+);?/g

By utilizing the + quantifier, the regex captures one or more digits indefinitely. This ensures that any arbitrarily padded entity correctly matches the expression, triggering the internal decode operation. The subsequent protocol validation check therefore operates on the fully normalized string, appropriately identifying and blocking restricted URI protocols.

Exploitation Methodology

Exploitation requires an application configuration where user-controlled input flows directly into the useHeadSafe() composable. Attackers target attributes mapped to URL inputs, such as the href attribute in a <link> tag. The payload constructs a malicious URI scheme but obscures the required colon character using a padded numeric character reference.

The proof-of-concept payload javascript&#0000000058;alert('XSS') demonstrates this bypass sequence. The string passes through the unhead validation logic unmodified, as the colon obfuscation exceeds the seven-digit regex limit. The makeTagSafe() function accepts the input as benign and incorporates it into the final Server-Side Rendered (SSR) HTML payload.

Upon receiving the HTML response, the victim's browser begins parsing the document <head>. The browser's native HTML parser processes the numeric entity according to the HTML5 specification, discarding the leading zeros and resolving &#0000000058; to the colon character :. The browser immediately executes the reconstructed javascript:alert('XSS') payload within the security context of the vulnerable application.

Impact Assessment

The exploitation of CVE-2026-39315 results in a standard Cross-Site Scripting (XSS) vulnerability. An attacker successfully executes arbitrary JavaScript within the victim's browser session. The vulnerability holds a CVSS v3.1 base score of 6.1, reflecting a medium severity rating due to the requirements for user interaction and specific input configurations.

The impact scope heavily depends on the privileges of the compromised user session and the implementation details of the targeted application. Executed scripts can access document.cookie, intercept session tokens, exfiltrate sensitive data displayed on the page, or perform unauthorized actions on behalf of the victim. In environments without strict Content Security Policy (CSP) enforcement, the attacker maintains complete control over the execution context.

Because useHeadSafe() is explicitly designed and advertised as a security mechanism for rendering untrusted user data, this bypass undermines the core trust model of the component. Developers relying on this composable to handle arbitrary data streams implicitly introduce an XSS vector into their Server-Side Rendered applications.

Mitigation and Remediation

The primary remediation strategy is upgrading the unhead package to version 2.1.13 or later. This release contains the updated regular expressions that correctly parse and normalize arbitrarily padded numeric character references. Developers should audit application dependency trees, particularly within Nuxt.js projects, to ensure transitive dependencies are updated accordingly.

If immediate patching is unfeasible, developers must implement secondary input validation before passing data into useHeadSafe(). Application logic can enforce strict allowlists for acceptable URI schemes or explicitly reject inputs containing the &# string sequence within URL-destined attributes.

Defense-in-depth measures provide critical fallback protection against this class of vulnerability. Implementing a robust Content Security Policy (CSP) that explicitly prohibits unsafe-inline and restricts script execution to trusted domains effectively neutralizes the javascript: URI attack vector, even if the application logic fails to filter the payload.

Official Patches

unjsCommit applying the regex fix to unhead
unjsUnhead v2.1.13 Release Notes

Fix Analysis (1)

Technical Appendix

CVSS Score
6.1/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N

Affected Systems

unhead (npm package)Nuxt.js applications utilizing the unhead module

Affected Versions Detail

Product
Affected Versions
Fixed Version
unhead
unjs
< 2.1.132.1.13
AttributeDetail
CWE IDCWE-184
CVSS Score6.1 (Medium)
Attack VectorNetwork
Exploit StatusPoC Available
CISA KEVNot Listed
ImpactCross-Site Scripting (XSS)

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1059.007Command and Scripting Interpreter: JavaScript
Execution
CWE-184
Incomplete List of Disallowed Inputs

The software does not maintain a complete list of disallowed inputs, enabling bypass of protection mechanisms.

Known Exploits & Detection

Vulnerability ReportProof of Concept demonstrating the bypass using excessive leading zeros in HTML numeric character references.

Vulnerability Timeline

Vulnerability identified and reported by cybe4sent1nel (Fahad Khan)
2026-03-25
Fix committed to the unhead repository
2026-04-05
GitHub Advisory GHSA-95h2-gj7x-gx9w published
2026-04-09
CVE-2026-39315 officially published in NVD
2026-04-09

References & Sources

  • [1]GitHub Advisory GHSA-95h2-gj7x-gx9w
  • [2]Fix Commit 961ea78
  • [3]Unhead v2.1.13 Release Notes
  • [4]CVE Record (CVE.org)

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.