GHSA-M3Q2-P4FW-W38M

Vulnerability from github – Published: 2026-06-16 23:38 – Updated: 2026-06-16 23:38
VLAI
Summary
Cross-site scripting via <NoScript> slot content in Nuxt's head components
Details

Impact

Nuxt's globally registered <NoScript> component (from @unhead/vue head components, re-exported by Nuxt) wrote its default-slot content to the innerHTML of the <noscript> head tag, bypassing the HTML escaping that {{ }} interpolation normally applies in Vue templates.

Applications that placed untrusted, attacker-controllable data inside a <NoScript> slot, for example:

<NoScript>{{ route.query.banner }}</NoScript>

would emit that value unescaped inside <noscript> in the server-rendered HTML. With scripting enabled, the HTML parser treats <noscript> content in <head> under the "in head noscript" insertion mode: any tag other than link, meta, noframes, or style implicitly closes <noscript> and is re-processed in the head. A payload such as <script>...</script> therefore escapes the element and executes in the document context.

Sibling head components (<Style>, <Title>) were not affected because they already routed slot text through the safe textContent path.

Affected versions

All currently supported versions of nuxt that ship the <NoScript> global component.

Patches

Fixed in nuxt@4.4.7 (commit 4b054e9d) and backported to nuxt@3.21.7 (commit 7fea9fd6). The fix escapes <NoScript> slot content with escapeHtml from @vue/shared and writes it to textContent rather than innerHTML. Slot content is now rendered as text; intentional markup inside <NoScript> is no longer parsed as HTML.

Workarounds

Until you can upgrade:

  • Do not interpolate untrusted input into <NoScript> slots. Replace <NoScript>{{ x }}</NoScript> with a static string, or sanitise / HTML-escape x at the source.
  • If you must render dynamic noscript content, write the tag yourself via useHead({ noscript: [{ textContent: escapedValue }] }) after escaping escapedValue.

Credit

Reported to Anthropic's coordinated vulnerability disclosure pipeline by Claude (Anthropic's AI assistant) and triaged by the Anthropic security team. Reference: ANT-2026-4NJYDFFM.

Independently reported by @alcls01111 via GitHub's coordinated disclosure flow (GHSA-8grp-wcq9-925q), closed as a duplicate of this advisory.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "nuxt"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.0.0"
            },
            {
              "fixed": "4.4.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "nuxt"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.21.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-79"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-16T23:38:47Z",
    "nvd_published_at": null,
    "severity": "LOW"
  },
  "details": "### Impact\n\nNuxt\u0027s globally registered `\u003cNoScript\u003e` component (from `@unhead/vue` head components, re-exported by Nuxt) wrote its default-slot content to the `innerHTML` of the `\u003cnoscript\u003e` head tag, bypassing the HTML escaping that `{{ }}` interpolation normally applies in Vue templates.\n\nApplications that placed untrusted, attacker-controllable data inside a `\u003cNoScript\u003e` slot, for example:\n\n```vue\n\u003cNoScript\u003e{{ route.query.banner }}\u003c/NoScript\u003e\n```\n\nwould emit that value unescaped inside `\u003cnoscript\u003e` in the server-rendered HTML. With scripting enabled, the HTML parser treats `\u003cnoscript\u003e` content in `\u003chead\u003e` under the \"in head noscript\" insertion mode: any tag other than `link`, `meta`, `noframes`, or `style` implicitly closes `\u003cnoscript\u003e` and is re-processed in the head. A payload such as `\u003cscript\u003e...\u003c/script\u003e` therefore escapes the element and executes in the document context.\n\nSibling head components (`\u003cStyle\u003e`, `\u003cTitle\u003e`) were not affected because they already routed slot text through the safe `textContent` path.\n\n### Affected versions\n\nAll currently supported versions of `nuxt` that ship the `\u003cNoScript\u003e` global component.\n\n### Patches\n\nFixed in `nuxt@4.4.7` (commit [`4b054e9d`](https://github.com/nuxt/nuxt/commit/4b054e9d95f8daf366cb144b52782047c511a66e)) and backported to `nuxt@3.21.7` (commit [`7fea9fd6`](https://github.com/nuxt/nuxt/commit/7fea9fd687f1dacbfb63db5fae5839896b017a0e)). The fix escapes `\u003cNoScript\u003e` slot content with `escapeHtml` from `@vue/shared` and writes it to `textContent` rather than `innerHTML`. Slot content is now rendered as text; intentional markup inside `\u003cNoScript\u003e` is no longer parsed as HTML.\n\n### Workarounds\n\nUntil you can upgrade:\n\n- Do not interpolate untrusted input into `\u003cNoScript\u003e` slots. Replace `\u003cNoScript\u003e{{ x }}\u003c/NoScript\u003e` with a static string, or sanitise / HTML-escape `x` at the source.\n- If you must render dynamic noscript content, write the tag yourself via `useHead({ noscript: [{ textContent: escapedValue }] })` after escaping `escapedValue`.\n\n### Credit\n\nReported to Anthropic\u0027s coordinated vulnerability disclosure pipeline by Claude (Anthropic\u0027s AI assistant) and triaged by the Anthropic security team. Reference: ANT-2026-4NJYDFFM.\n\nIndependently reported by [@alcls01111](https://github.com/alcls01111) via GitHub\u0027s coordinated disclosure flow (`GHSA-8grp-wcq9-925q`), closed as a duplicate of this advisory.",
  "id": "GHSA-m3q2-p4fw-w38m",
  "modified": "2026-06-16T23:38:47Z",
  "published": "2026-06-16T23:38:47Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/nuxt/nuxt/security/advisories/GHSA-m3q2-p4fw-w38m"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nuxt/nuxt/commit/4b054e9d95f8daf366cb144b52782047c511a66e"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nuxt/nuxt/commit/7fea9fd687f1dacbfb63db5fae5839896b017a0e"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/nuxt/nuxt"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Cross-site scripting via \u003cNoScript\u003e slot content in Nuxt\u0027s head components"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…