Listen to this Post
The vulnerability resides in the `acceptDataAttrs` function within Unhead’s `safe.ts` (lines 16-20). This function is designed to filter attributes for the `useHeadSafe()` composable, which is specifically recommended by Nuxt documentation for handling user-generated content safely . The code intends to allow only `id` attributes and those with a `data-` prefix, passing them through to the final HTML. However, it only performs a prefix check and fails to validate the overall syntax of the attribute key. Consequently, an attacker can inject a key like data-x onload=alert(document.domain) y. Because the filter does not reject keys containing spaces, the `propsToString` function (line 26) later interpolates this malicious string directly into the SSR output. The space breaks the attribute declaration, causing the browser to parse `onload=alert(document.domain)` as a separate, legitimate event handler attribute. When the tag (e.g., a <link rel="stylesheet">) loads, the injected JavaScript executes, leading to Cross-site Scripting (XSS) .
dailycve form:
Platform: Nuxt/Unhead
Version: before 2.1.11
Vulnerability: XSS Bypass
Severity: Critical
date: 12 March 2026
Prediction: Patched in 2.1.11
What Undercode Say:
Analytics
- Attack Vector: Network
- Complexity: Low
- Privileges: None
- User Interaction: Required
- Scope: Changed
- CVSS Score: 5.3 (Medium)
Bash/Code
Identify vulnerable versions (package.json) grep '"unhead":' package.json Vulnerable range: < 2.1.11 Upgrade command to patch npm install @unhead/[email protected] or yarn add @unhead/[email protected]
Vulnerable Code Snippet (safe.ts)
// Lines 16-20: Vulnerable filter
function acceptDataAttrs(value: Record<string, string>) {
return Object.fromEntries(
Object.entries(value || {}).filter(([bash]) => key === 'id' || key.startsWith('data-')),
)
}
// No regex validation on the 'key' allows spaces and special chars.
How Exploit
1. Vector: SSR-rendered `` tags (e.g., `link`, `meta`).
2. Method: Inject a malformed attribute key.
3. Payload Example:
useHeadSafe({
link: [{
rel: 'stylesheet',
href: '/valid.css',
'data-x onload=alert(document.domain) y': 'z'
}]
})
4. Resulting HTML:
<link data-x onload=alert(document.domain) y="z" rel="stylesheet" href="/valid.css">
The browser parses `onload=alert(document.domain)` as a valid event handler.
Protection from this CVE
- Patch: Upgrade to `@unhead/[email protected]` or later .
- Code Fix: Implement strict regex validation for attribute names.
const SAFE_ATTR_RE = /^[a-zA-Z][a-zA-Z0-9-]$/; function acceptDataAttrs(value: Record<string, string>) { return Object.fromEntries( Object.entries(value || {}).filter( ([bash]) => (key === 'id' || key.startsWith('data-')) && SAFE_ATTR_RE.test(key) ), ); } - Workaround: Avoid using `useHeadSafe()` with raw user input containing attributes; sanitize keys manually.
Impact
- Confidentiality: Low (DOM access)
- Integrity: Low (script execution)
- Availability: None
- Real-world Scenario: A Nuxt application accepting SEO metadata from a CMS or user profile. An attacker injects a `data-` key with spaces and an event handler into their input. The payload renders into the HTML on every page load, allowing session hijacking, defacement, or redirection to malicious sites.
🎯Let’s Practice Exploiting & Learn Patching For Free:
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

