# [CRIT] [GHSA / CRITICAL] CVE-2026-47140: NodeVM builtin denylist bypass via process and inspector/promises allows host code execution

**Source:** GitHub Security Advisories
**Published:** 2026-05-29
**Article:** https://github.com/advisories/GHSA-rp36-8xq3-r6c4

## Threat Profile

NodeVM builtin denylist bypass via process and inspector/promises allows host code execution

## Summary

`NodeVM` blocks several dangerous Node.js builtins such as `module`, `worker_threads`, `cluster`, `vm`, `repl`, and `inspector`.

However, the denylist misses `process` and `inspector/promises`. Both can be used from sandboxed code to reach host-side execution primitives.

This allows sandboxed code to bypass the intended builtin restrictions and execute code in the host process.

## Details…

## Indicators of Compromise (high-fidelity only)

- **CVE:** `CVE-2026-47140`

## MITRE ATT&CK Techniques

- **T1190** — Exploit Public-Facing Application
- **T1204.002** — User Execution: Malicious File
- **T1059.007** — Command and Scripting Interpreter: JavaScript
- **T1059.003** — Command and Scripting Interpreter: Windows Command Shell
- **T1059.001** — Command and Scripting Interpreter: PowerShell
- **T1480** — Execution Guardrails
- **T1211** — Exploitation for Defense Evasion

## Kill chain phases observed

_(none detected from narrative keywords)_

## Recommended hunts

### vm2 vulnerable version inventory (CVE-2026-47140) — NodeVM denylist bypass surface

`UC_173_2` · phase: **weapon** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats summariesonly=t count min(_time) as firstTime max(_time) as lastTime from datamodel=Vulnerabilities.Vulnerabilities where Vulnerabilities.signature="CVE-2026-47140" OR (Vulnerabilities.cve="CVE-2026-47140") by Vulnerabilities.dest Vulnerabilities.signature Vulnerabilities.severity Vulnerabilities.cve | `drop_dm_object_name(Vulnerabilities)` | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`
```

**Defender KQL:**
```kql
DeviceTvmSoftwareVulnerabilities
| where CveId == "CVE-2026-47140"
   or (SoftwareName =~ "vm2" and SoftwareVendor =~ "npm" and SoftwareVersion matches regex @"^3\.(?:[0-9]|10|11\.[0-3])(?:\.|$)")
| join kind=leftouter (DeviceInfo | summarize arg_max(Timestamp, PublicIP, IsInternetFacing, OSPlatform) by DeviceId) on DeviceId
| project Timestamp, DeviceName, OSPlatform1, SoftwareName, SoftwareVersion, CveId, VulnerabilitySeverityLevel, RecommendedSecurityUpdate, IsInternetFacing
| order by VulnerabilitySeverityLevel desc, IsInternetFacing desc
```

### Node.js process spawning native shell / interpreter — post-vm2-escape host execution

`UC_173_3` · phase: **exploit** · confidence: **Medium** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats summariesonly=t count min(_time) as firstTime max(_time) as lastTime values(Processes.process) as cmdline values(Processes.process_path) as child_path from datamodel=Endpoint.Processes where Processes.parent_process_name="node.exe" AND Processes.process_name IN ("cmd.exe","powershell.exe","pwsh.exe","wscript.exe","cscript.exe","mshta.exe","rundll32.exe","regsvr32.exe","wmic.exe","bitsadmin.exe","certutil.exe","curl.exe","wget.exe","bash.exe","sh.exe","whoami.exe") by Processes.dest Processes.user Processes.parent_process Processes.parent_process_name Processes.process_name Processes.process_id | `drop_dm_object_name(Processes)` | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`
```

**Defender KQL:**
```kql
DeviceProcessEvents
| where Timestamp > ago(7d)
| where InitiatingProcessFileName =~ "node.exe"
| where FileName in~ ("cmd.exe","powershell.exe","pwsh.exe","wscript.exe","cscript.exe","mshta.exe","rundll32.exe","regsvr32.exe","wmic.exe","bitsadmin.exe","certutil.exe","curl.exe","wget.exe","bash.exe","sh.exe","whoami.exe","net.exe","net1.exe","hostname.exe","ipconfig.exe")
| where AccountName !endswith "$"
| project Timestamp, DeviceName, AccountName,
          ParentNodeCmd = InitiatingProcessCommandLine,
          ParentNodeFolder = InitiatingProcessFolderPath,
          GrandparentImage = InitiatingProcessParentFileName,
          ChildImage = FolderPath,
          ChildCmd = ProcessCommandLine,
          ChildSHA256 = SHA256
| order by Timestamp desc
```

### vm2 NodeVM denylist bypass PoC strings — getBuiltinModule + inspector/promises

`UC_173_4` · phase: **exploit** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats summariesonly=t count min(_time) as firstTime max(_time) as lastTime values(Processes.process) as cmdline values(Processes.parent_process) as parent_cmdline from datamodel=Endpoint.Processes where (Processes.process_name="node.exe" OR Processes.parent_process_name="node.exe") AND (Processes.process="*getBuiltinModule*" OR Processes.process="*inspector/promises*" OR Processes.process="*Runtime.evaluate*" OR Processes.process="*require('process').getBuiltinModule*" OR Processes.process="*require(\"process\").getBuiltinModule*") by Processes.dest Processes.user Processes.process_name Processes.parent_process_name Processes.process_id | `drop_dm_object_name(Processes)` | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | append [| tstats summariesonly=t count min(_time) as firstTime max(_time) as lastTime values(Filesystem.file_path) as paths from datamodel=Endpoint.Filesystem where Filesystem.file_name="*.js" AND Filesystem.action="created" by Filesystem.dest Filesystem.user Filesystem.file_name Filesystem.process_name | `drop_dm_object_name(Filesystem)` | search process_name="node.exe"]
```

**Defender KQL:**
```kql
let bypass_tokens = dynamic(["getBuiltinModule","inspector/promises","Runtime.evaluate","require('process').getBuiltinModule","require(\"process\").getBuiltinModule"]);
let proc_hits = DeviceProcessEvents
    | where Timestamp > ago(30d)
    | where FileName =~ "node.exe" or InitiatingProcessFileName =~ "node.exe"
    | where ProcessCommandLine has_any (bypass_tokens) or InitiatingProcessCommandLine has_any (bypass_tokens)
    | project Timestamp, DeviceName, AccountName, Source="ProcessEvents", Image=FolderPath, Cmd=ProcessCommandLine, ParentCmd=InitiatingProcessCommandLine, SHA256;
let file_hits = DeviceFileEvents
    | where Timestamp > ago(30d)
    | where ActionType in ("FileCreated","FileModified")
    | where FileName endswith ".js" or FileName endswith ".mjs" or FileName endswith ".cjs"
    | where InitiatingProcessFileName !in~ ("npm.cmd","yarn.cmd","pnpm.cmd","git.exe")
    | where FolderPath !contains @"\node_modules\"
    | project Timestamp, DeviceName, AccountName=InitiatingProcessAccountName, Source="FileEvents", Image=FolderPath, Cmd=tostring(strcat(FolderPath,"\\",FileName)), ParentCmd=InitiatingProcessCommandLine, SHA256;
proc_hits
| union file_hits
| order by Timestamp desc
```

### Article-specific behavioural hunt — [GHSA / CRITICAL] CVE-2026-47140: NodeVM builtin denylist bypass via process and

`UC_173_1` · phase: **exploit** · confidence: **High**

**Splunk SPL (CIM):**
```spl
``` Article-specific bespoke detection — [GHSA / CRITICAL] CVE-2026-47140: NodeVM builtin denylist bypass via process and ```
| tstats `summariesonly` count earliest(_time) AS firstTime latest(_time) AS lastTime
    from datamodel=Endpoint.Processes
    where (Processes.process_name IN ("node.js","builtin-denylist-rce.js","dangerous-builtin-denylist-rce.js"))
    by Processes.dest, Processes.user, Processes.process_name,
       Processes.process, Processes.parent_process_name, Processes.process_path
| `drop_dm_object_name(Processes)`
| `security_content_ctime(firstTime)`
| append [
| tstats `summariesonly` count
    from datamodel=Endpoint.Filesystem
    where Filesystem.action IN ("created","modified")
      AND (Filesystem.file_name IN ("node.js","builtin-denylist-rce.js","dangerous-builtin-denylist-rce.js"))
    by Filesystem.dest, Filesystem.user, Filesystem.process_name,
       Filesystem.file_path, Filesystem.file_name
| `drop_dm_object_name(Filesystem)`
]
```

**Defender KQL:**
```kql
// Article-specific bespoke detection — [GHSA / CRITICAL] CVE-2026-47140: NodeVM builtin denylist bypass via process and
// Hunts the actual binaries / paths / commandline fragments named
// in the article instead of a generic technique-class template.
DeviceProcessEvents
| where Timestamp > ago(30d)
| where (FileName in~ ("node.js", "builtin-denylist-rce.js", "dangerous-builtin-denylist-rce.js"))
| project Timestamp, DeviceName, AccountName, FileName,
          FolderPath, ProcessCommandLine,
          InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc

// File-creation events for the named binaries / paths
DeviceFileEvents
| where Timestamp > ago(30d)
| where ActionType in ("FileCreated","FileModified")
| where (FileName in~ ("node.js", "builtin-denylist-rce.js", "dangerous-builtin-denylist-rce.js"))
| project Timestamp, DeviceName, AccountName, FolderPath,
          FileName, ActionType, InitiatingProcessFileName,
          InitiatingProcessCommandLine
| order by Timestamp desc
```

### IOC-driven hunts (use shared templates)

These are standard IOC-substitution hunts — the canonical SPL and KQL live once in [`_TEMPLATES.md`](../_TEMPLATES.md), so we don't repeat the same boilerplate on every CVE / hash / network-IOC briefing.

- **Asset exposure — vulnerability matches article CVE(s)** ([template](../_TEMPLATES.md#asset-exposure)) — phase: **recon**, confidence: **High**
  - CVE(s): `CVE-2026-47140`


## Why this matters

Severity classified as **CRIT** based on: CVE present, 5 use case(s) fired, 7 technique(s) inferred. Read the full article for actor attribution, tooling details, and any defanged IOCs in the body that aren't visible in the RSS summary.
