core-jmp core-jmpdeath of core jump

CVE-2026-71320: Nuxt Server Island Template Injection Leads to Server-Side RCE

CVE-2026-71320 lets an attacker inject a template key into Nuxt server island props and have Vue's runtime compiler execute it inside the Nitro process - server-side RCE, CVSS 8.1. Three preconditions must all hold, and the third one, attribute fallthrough onto a polymorphic root component, is the part that catches people out. A walkthrough of the real mechanism verified against the upstream advisory, the source's detection rules reproduced in full, a structural detection that actually matches the primitive, and the fixes in 3.21.10 and 4.5.1.

oxfemale August 13, 2026 19 min read 146 reads
Export PDF
CVE-2026-71320: Nuxt Server Island Template Injection Leads to Server-Side RCE
Original text: “CVE-2026-71320 Vulnerability in Nuxt”Valters IT Hub (Valters Capital, SIA), published 5 August 2026. The detection rules and Shodan queries below are reproduced verbatim from that page with attribution captions. Vulnerability mechanics have been verified against the upstream GitHub Security Advisory GHSA-9473-5f9j-94wq; where the two accounts differ, the advisory is followed and the difference is flagged.
nuxt logo
Nuxt — the affected framework. Source: original article.

Executive Summary

CVE-2026-71320 is a high-severity server-side code execution flaw in Nuxt, the Vue.js meta-framework. It affects the server islands feature: components rendered in isolation on the server and addressed over an internal HTTP endpoint at /__nuxt_island/. When an application enables Vue’s runtime template compiler, an attacker who can reach that endpoint may place a template key inside the island props. If those props reach a dynamic-component resolution path, Vue’s runtime compiler compiles the attacker’s string inside the Nitro server process, yielding remote code execution. GitHub rates it CVSS 8.1 High with the vector AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H, classified under CWE-74 and CWE-94. Affected ranges are nuxt >=3.4.0 <3.21.10 and nuxt >=4.0.0 <4.5.1; fixes shipped in 3.21.10 and 4.5.1.

The severity deserves immediate context, because this is not a bug that fires against a default install. Three conditions must hold at once: an affected version, vue.runtimeCompiler: true (off by default, and the reason attack complexity is rated High), and a server island whose props actually reach a dynamic-component sink. That third condition is the one worth reading twice. Because undeclared island props fall through as ordinary attributes onto the component’s single root element, an island whose root is a polymorphic component — the kind exposing an as or asChild prop, as @nuxt/ui does through reka-ui — can hand the attacker’s value to the dynamic-component path without the island author ever writing a forwarding binding. The population at risk is therefore small but not self-evident: if you have enabled the runtime compiler, you cannot rule yourself out by reading your island components for explicit prop forwarding. This post walks the real mechanism, reproduces the source’s detection content, and sets out what to actually do about it.

Vulnerability at a Glance

FieldValue
CVECVE-2026-71320
AdvisoryGHSA-9473-5f9j-94wq (GitHub-reviewed)
ProductNuxt (npm package nuxt)
Affected>=3.4.0 <3.21.10 and >=4.0.0 <4.5.1
Fixed in3.21.10 and 4.5.1
CVSS v3.18.1 High — AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CWECWE-74, CWE-94
MITRE ATT&CKT1190 — Exploit Public-Facing Application
EPSS0.00438 (as shown on the source page)
Published2026-08-05
Exploitation statusNo public exploit code or in-the-wild activity reported at time of writing
Metadata as presented on the source page, cross-checked against the GitHub Security Advisory and the OSV record.

Technical Analysis

Nuxt server islands let a developer render a component entirely on the server and drop the result into an otherwise client-rendered page. Nuxt exposes each island at a URL of the form /__nuxt_island/<Name>_<hash>.json, and the props for that render travel in the request. It is worth stating plainly what that hash is and is not: it is a deterministic, unsalted content hash over the component name and props. It provides integrity relative to the URL, but it is not an authentication token and not a security boundary. Anyone who knows the component name and the props they want can compute a valid hash themselves. Treating the island endpoint as “internal” because the URLs look opaque is a mistake.

The second ingredient is vue.runtimeCompiler. Vue can either pre-compile templates at build time or carry a compiler at runtime and compile template strings on demand. Nuxt disables the runtime compiler by default, and prior to 3.4.0 the compiler dependencies were mock-aliased out of the server bundle entirely — which is precisely why the affected range starts there rather than at the beginning of the islands feature. Turning the option on is a deliberate act, usually taken to support genuinely dynamic templates such as CMS-authored fragments.

Put the two together and the primitive appears. Island props are deserialised and passed into the component. If any of those props find their way into Vue’s dynamic component resolution — <component :is>, resolveDynamicComponent, or a direct h() call — then an object carrying a template key is treated by Vue as a component definition with an inline template. With the runtime compiler present, Vue compiles that string into a render function and executes it. The payload shape is minimal:

{ "as": { "template": "<attacker-controlled>" } }

The injection primitive, as documented in the upstream advisory GHSA-9473-5f9j-94wq.

Compilation happens inside the Nitro process — Nuxt’s server runtime — so the resulting code runs with the privileges of the Node.js server, not in a visitor’s browser. That is the distinction that moves this from cross-site scripting into remote code execution: the attacker gains the ability to read environment variables and application secrets, touch the filesystem, and reach whatever internal network the server can reach. The same primitive also works client-side when the runtime compiler is active there, but the server path is the one that matters.

Diagram of CVE-2026-71320 preconditions and the exploitation path from island request to RCE in the Nitro process
The three preconditions and the four-step path from island request to code execution, plus what the patch changes. Diagram produced for this article from the upstream advisory.

The precondition that is easy to miss

The reason this advisory is more interesting than a routine “enable an unsafe option and get burned” note is attribute fallthrough. In Vue, props passed to a component that the component does not declare are applied as attributes to its single root element. That is ordinary, documented behaviour that nobody thinks of as a security surface. Here it means island props the island never declared land on whatever component sits at its root. If that root is a polymorphic primitive exposing an as or asChild prop — the pattern used across reka-ui and therefore @nuxt/ui — the attacker’s object is handed straight to dynamic-component resolution. An island as unremarkable as this is sufficient:

<!-- components/MyWidget.server.vue -->
<template>
  <UButton>Save</UButton>
</template>

Example vulnerable island from the upstream advisory: the attacker-supplied as value falls through to UButton, with no explicit forwarding written by the author.

To be unambiguous about blame: @nuxt/ui and reka-ui are not vulnerable. The flaw is in Nuxt core. Those libraries are named only because they are the most common suppliers of the dynamic-component sink the primitive needs. Installing them registers nothing as a server island by itself; the application still has to define a .server.vue island whose rendered output puts the attacker’s value on such a component. The practical consequence is that auditing by grep for <component :is> in your island components is not sufficient. You also have to look at what sits at each island’s root.

Where the source page and the advisory diverge

The source article frames this as a classic server-side template injection and illustrates it with the familiar SSTI repertoire: {{7*7}} probes rendering as 49, and constructor-chain escapes of the this.constructor.constructor('return process')() variety used to reach Node globals. That framing is a reasonable-sounding generalisation, but it does not match the mechanism the maintainers describe, and anyone testing from it will get the wrong answer. Three corrections matter:

  1. The injection is a specific key, not free-form template syntax in an arbitrary parameter. The primitive is an object carrying template that reaches dynamic-component resolution — not mustache expressions sprayed into any prop. A {{7*7}} probe in an unrelated prop is not a valid test for this CVE.
  2. The second precondition is absent from the source’s account. The source presents the runtime compiler as the only gate. In reality the application must also have an island that routes props to a dynamic-component sink. Without that, enabling the compiler is not sufficient.
  3. The exploitation walkthrough is hypothetical. The source says as much — it notes no public exploit exists and the chain is “derived from the vulnerability mechanics” — but the step-by-step presentation reads as though it were tested. It was not, and its reconnaissance and probe steps rest on the incorrect mechanism above.

Two smaller inconsistencies are worth noting for anyone using the page as a reference. Its header reports an EPSS score of 0.00438 while its own References section states that FIRST.org has not yet assigned one. And its patch guidance is labelled “Patch Confidence: 2/4 (CLAIMED — single source claims patch, not independently confirmed)” — a caveat that is unnecessary here, since the fix is confirmed by the vendor advisory, two linked commits, and the 3.21.10 and 4.5.1 release tags. The patch is real; upgrade with confidence.

Exploitation and Impact

A realistic assessment path, for defenders auditing their own estate, runs in the opposite direction to the source’s attacker-first narrative — and is far cheaper. Start from configuration rather than from the network. Check whether vue.runtimeCompiler is set to true anywhere in nuxt.config.ts or an environment-specific override. If it is not, the application is not affected and no further work is needed. If it is, enumerate the .server.vue island components and examine each one’s root element, looking for polymorphic components as well as explicit dynamic-component usage. That two-step check answers the question definitively in minutes, without a single request.

Where exploitation succeeds, the impact is the full CIA triad rated High, and the CVSS scope is Unchanged only because the compromised component and the impacted component are the same server runtime. Code executes as the Nitro process user. From there an attacker reads environment variables and whatever secrets the deployment injects that way — database credentials, API keys, signing material — enumerates application source and configuration on disk, and uses the server’s network position to probe internal services that were never meant to be reachable from outside. In a containerised deployment the blast radius is bounded by how well that container was built: a read-only root filesystem, dropped capabilities and a non-root user materially limit what follows, while a privileged container with a mounted socket does not.

Two mitigating factors are worth weighing before treating this as an emergency. Statically generated deployments are largely out of reach, because there is no long-lived server process to compromise. And island component names are constrained to the build-time component registry, so an attacker cannot resolve arbitrary components — only ones the application actually ships. Neither factor helps a server-rendered application that has the runtime compiler on, but together they explain why the realistic affected population is a small fraction of Nuxt deployments.

Detection Guidance

The source page ships a set of detection rules covering the common SOC tooling. They are reproduced verbatim below, in the original order and with the original author metadata intact. One caveat applies to all of them, and it follows directly from the mechanism correction above: these rules key on generic SSTI and Node.js indicators — {{ braces, constructor.constructor, child_process, process.env — rather than on the actual primitive, which is a template key inside decoded island props. A real exploit attempt for this CVE need contain none of those strings. Treat the rules as broad-spectrum coverage for template-injection attempts against the island endpoint, and pair them with the property-based check described after them. Rule identifiers, the date: 2026/01/01 stamps and the “SOC Threat Intel” authorship are the source’s own; adjust them to your environment before deployment.

Sigma Rule

Matches POST requests to the island path whose content carries template-injection markers alongside Node.js runtime references.

title: Nuxt Island SSTI Exploitation Attempt
id: 9f8f2b6e-3d5a-4c80-9d0e-7a5128eab110
status: experimental
description: Detects POST requests to /__nuxt_island/ containing template injection payloads referencing Node.js process objects.
references:
    - https://nvd.nist.gov/vuln/detail/CVE-2026-71320
author: SOC Threat Intel
date: 2026/01/01
logsource:
    category: webserver
    product: nginx
detection:
    selection:
        cs-method: 'POST'
        cs-uri-path|startswith: '/__nuxt_island/'
    keywords_any:
        - '*constructor.constructor*'
        - '*process.mainModule*'
        - '*require(''child_process'')*'
        - '*{{5*5}}*'
        - '*{{7*7}}*'
        - '*process.env*'
    condition: selection and keywords_any
falsepositives:
    - Legitimate development tools accessing islands with template content
    - Admin panels with dynamic rendering features
level: high

Sigma rule reproduced verbatim. Source: original article.

YARA Rule

Intended for scanning captured HTTP bodies or log files rather than files on disk; it requires the endpoint string and both brace pairs, then any one of the Node.js indicators.

rule Nuxt_Island_SSTI_CVE_2026_71320 {
    meta:
        author = "SOC Threat Intel"
        description = "Detects template injection payloads targeting Nuxt island props"
        reference = "CVE-2026-71320"
        date = "2026-01-01"
    strings:
        $legit_endpoint = "/__nuxt_island/" ascii nocase
        $ssti_brace = "{{" ascii
        $ssti_brace_close = "}}" ascii
        $node_process = "process.mainModule" ascii nocase
        $node_require = "child_process" ascii nocase
        $node_env = "process.env" ascii nocase
        $constructor_chain = "constructor.constructor" ascii nocase
        $exec_func = "execSync" ascii nocase
        $json_prop = "\"props\"" ascii
    condition:
        $legit_endpoint and $ssti_brace and $ssti_brace_close and
        (
            $node_process or
            $node_require or
            $node_env or
            $constructor_chain or
            ($exec_func and $json_prop)
        )
}

YARA rule reproduced verbatim. Source: original article.

Suricata IDS Rule

Inspects request bodies in flight for the island path combined with brace syntax and Node.js globals.

alert http any any -> $HOME_NET any (
    msg:"CVE-2026-71320 Nuxt Island SSTI RCE Attempt";
    flow:to_server,established;
    http.method; content:"POST"; http.uri; content:"/__nuxt_island/";
    http.request_body; content:"{{";
    content:"constructor"; nocase;
    content:"process"; nocase; distance:0;
    content:"child_process"; nocase; distance:0;
    classtype:attempted-admin;
    sid:2026007132; rev:1; priority:1;
)

Suricata rule reproduced verbatim. Source: original article. Note that the rule as written alerts rather than drops, despite the source’s description; add drop and run IPS mode if blocking is intended.

Elastic Detection

An EQL sequence correlating an island request with a subsequent process execution on the same host.

sequence by host.id with maxspan=5m
  [ network where event.category == "web" and http.request.method == "POST" and
    http.request.uri as uri and starts_with(uri, "/__nuxt_island/") ]
  [ process where event.action == "exec-command" and
    process.command_line like "*constructor*" or
    process.command_line like "*child_process*" or
    process.command_line like "*process.env*" ]

Elastic EQL query reproduced verbatim. Source: original article. Review the operator precedence in the second stage before deploying — the unparenthesised and/or mix will not bind as the author’s description implies.

Splunk SPL Query

Searches combined access logs for the island endpoint and flags raw events carrying injection signatures, grouped by source address.

index=web sourcetype=access_combined
| search method=POST uri=/__nuxt_island/*
| rex field=_raw "POST (?<uri>/__nuxt_island/[^ ]+)"
| search uri="/__nuxt_island/*"
| regex _raw="(\{\{|\}\}|constructor\.constructor|child_process|process\.env|process\.mainModule)"
| stats count by src_ip, uri, user_agent, _time
| sort - _time

Splunk SPL reproduced verbatim. Source: original article.

Wazuh / OSSEC Rule

Decodes web server access logs and raises a level 15 alert on island requests containing the signature strings, tagged to ATT&CK T1190.

<rule id="100713" level="15">
  <if_sid>30200, 30201</if_sid> <!-- Adjust to your web server rule IDs -->
  <field name="url">/__nuxt_island/</field>
  <regex>(\{\{|constructor\.constructor|child_process|process\.env)</regex>
  <description>CVE-2026-71320: Nuxt island SSTI exploitation attempt detected (POST to /__nuxt_island/).</description>
  <group>ssrf,rce,</group>
  <mitre>
    <id>T1190</id>
  </mitre>
</rule>

Wazuh rule reproduced verbatim. Source: original article. The if_sid values and the ssrf group tag will need adjusting for your ruleset.

A detection that matches the actual primitive

Because the exploit is defined by a property name rather than by a payload string, the highest-fidelity detection is structural: URL-decode and JSON-parse the props value on requests to /__nuxt_island/, then alert on the presence of a property named template at any depth. This is exactly what the upstream patch does server-side, and it is what the advisory recommends as a defence-in-depth WAF rule. It produces almost no false positives outside applications that legitimately pass CMS content through a field of that name, and unlike the string-matching rules above it cannot be evaded by changing the payload. Inspect both query string and body, and cover all methods rather than POST alone. One important limitation the advisory calls out: internal island renders performed during initial server-side rendering never transit the edge, so a WAF rule alone does not close the vector — it supplements the upgrade rather than substituting for it.

Mitigation and Patch Guidance

The fix is confirmed and the upgrade is the answer. Nuxt 3.21.10 and 4.5.1 add a guard that rejects island requests whose decoded props contain a template key at any depth, returning HTTP 400 with a diagnostic suggesting the author rename the prop or turn the compiler off. The guard is deliberately gated on the runtime compiler being enabled, so default configurations are untouched and applications that legitimately carry a template field in their props continue to render normally. A render key is not rejected, and the reasoning is worth understanding: island props arrive as JSON, so a render value can only ever be an inert string, which Vue ignores rather than executing.

# 3.x line
npm install nuxt@^3.21.10

# 4.x line
npm install nuxt@^4.5.1

# verify what actually resolved
npm ls nuxt

Upgrade commands. Substitute pnpm or yarn as appropriate for the project.

If an upgrade cannot ship immediately, the ranked interim measures are: set vue.runtimeCompiler to false, which removes the vulnerable path outright and is the single most effective action; stop forwarding island props into <component :is>, resolveDynamicComponent or h() without sanitisation, remembering that attribute fallthrough can do this for you; and deploy the property-based WAF rule described above as defence in depth, with the caveat that it does not cover internal SSR island renders. Restricting or authenticating the /__nuxt_island/ path at the reverse proxy is possible but needs care, since the endpoint is used legitimately by the application itself.

Shodan Queries

The source page lists queries for locating internet-facing Nuxt deployments. They are reproduced here for asset-discovery use against your own address space. They fingerprint Nuxt itself and cannot indicate vulnerability: neither the Nuxt version, nor the vue.runtimeCompiler setting, nor the presence of a vulnerable island is externally observable, and all three are required. Treat any result as “worth a configuration check”, never as a target list.

QueryFinds
http.component:"Nuxt" http.title:"Nuxt"Hosts fingerprinted as Nuxt with a matching page title
http.component:"Nuxt.js"Alternate component fingerprint string
http.title:"Nuxt" port:443HTTPS services with a Nuxt title
http.component:"Nuxt" http.component:"Vue"Hosts fingerprinted as both Nuxt and Vue
Shodan queries reproduced verbatim. Source: original article.

Key Takeaways

  • Three preconditions, all required. An affected version, vue.runtimeCompiler: true, and an island whose props reach a dynamic-component sink. Break any one and the flaw is unreachable.
  • Attribute fallthrough is the trap. Undeclared island props land on the root element, so an island can expose the sink without its author writing any forwarding code. Audit island roots, not just explicit dynamic-component usage.
  • The island URL hash is not a secret. It is a deterministic content hash, computable by anyone who knows the component name and props — integrity, not authentication.
  • This is not classic mustache SSTI. The primitive is a template property inside island props; {{7*7}} probes against arbitrary parameters neither confirm nor exclude the vulnerability.
  • Configuration review beats scanning. Two checks against your own source answer the exposure question definitively and faster than any external probe.
  • Detect on structure, not strings. Alerting on a template property in decoded island props matches the actual primitive and cannot be evaded by rewriting the payload.
  • Verify aggregator content against the vendor advisory. The CVE hub page had the severity and version ranges right, but its mechanism, its missing second precondition, and its hypothetical-presented-as-tested exploit chain would each mislead a tester.

Defensive Recommendations

  • Upgrade to nuxt@3.21.10 or nuxt@4.5.1 or later, then confirm the resolved version with npm ls nuxt rather than trusting the manifest — a transitive pin can silently hold you on a vulnerable build.
  • Audit for vue.runtimeCompiler across every configuration layer, including environment-specific overrides and layer or module configs that may enable it outside the main nuxt.config.ts.
  • Inventory your .server.vue islands and inspect each root element, treating any polymorphic as / asChild component at the root as an exposed sink even with no explicit binding.
  • Deploy the property-based edge rule — decode and JSON-parse island props, reject any template or render property at any depth — as defence in depth, understanding it misses internal SSR renders.
  • Run Nitro as an unprivileged user in a hardened container: read-only root filesystem, dropped capabilities, no docker socket, so that code execution does not become host compromise.
  • Move secrets out of process environment variables where practical, toward a broker with short-lived credentials, so that reading /proc/self/environ yields less of value.
  • Apply egress filtering to the application tier so a compromised Nitro process cannot freely reach internal services or exfiltrate to arbitrary destinations.
  • Track advisories at the source — the GitHub Security Advisory database and OSV carry npm ecosystem detail earlier and more accurately than downstream CVE aggregators, and this CVE is a good demonstration of the gap.

References

Conclusion

CVE-2026-71320 is a narrow vulnerability with a severe payoff, and the interesting part is not the runtime compiler — anyone who enables a template compiler on server-controlled input has accepted a known risk — but the way ordinary Vue attribute fallthrough quietly supplies the second half of the exploit. A developer can enable the compiler for one legitimate reason, build an island that forwards nothing, and still be exposed because the island’s root component happens to be polymorphic. That is the kind of interaction between two individually reasonable behaviours that static review rarely catches. The remediation is unambiguous and cheap: upgrade to 3.21.10 or 4.5.1, and if you cannot, turn the runtime compiler off. The wider lesson is about sourcing — the aggregated CVE page that prompted this write-up got the metadata right and the mechanism wrong, and only the vendor advisory would have told a tester what to actually look for.

Original text: “CVE-2026-71320 Vulnerability in Nuxt” by Valters IT Hub at valtersit.com, 5 August 2026.

oxfemale Vulnerability research, reverse engineering, and exploit development.
// Discussion