Introduction
A dual-sink command injection in nvm (Node Version Manager) lets an attacker who controls a configured mirror execute arbitrary commands on any machine that runs nvm install. All the attacker has to do is embed shell metacharacters in the version strings the mirror serves through its index.tab file. For the millions of developers and CI/CD pipelines that rely on nvm to manage Node.js installations, a routine version-management operation becomes a potential remote code execution vector whenever a non-default or non-TLS mirror is in use.
nvm is the de facto standard tool for Node.js version management on macOS and Linux. It has approximately 93,700 GitHub stars and 10,200 forks, which puts it among the most widely adopted developer tools in the JavaScript ecosystem. It is a single shell script, and it shows up in CI/CD pipelines, Docker images, and developer workstations everywhere, which makes it core infrastructure for Node.js development workflows worldwide.
Technical Information
CVE-2026-10796 is classified as CWE-78: Improper Neutralization of Special Elements used in an OS Command. It has a CVSS v3.1 base score of 7.5 (High) and the vector CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H. The vulnerability affects nvm through version 0.40.4.
Root Cause
When a user runs nvm install <version>, nvm fetches the configured mirror's index.tab file to list the available Node.js and io.js versions. nvm then uses the selected version string, with no sanitization, to build download URLs and to construct shell and awk commands. The core architectural flaw is the use of eval for command execution and of direct string interpolation into awk programs. Both treat untrusted mirror data as trusted code.
Dual Injection Sinks
Two separate code paths in nvm are injection sinks for the same untrusted input.
Sink 1: eval in nvm_download()
The nvm_download() function built a curl or wget command string and executed it with eval. The version string from the mirror was interpolated directly into that command string, so if a version field contained shell command substitution (for example, $(id)), the local shell would execute it before curl or wget ever ran. Double-quoting the arguments inside eval did not prevent command substitution, so the payload got past an earlier quoting hardening from commit 0ce8f5a.
Sink 2: awk interpolation in nvm_get_checksum()
The nvm_get_checksum() function interpolated the version-derived download slug (the tarball name) directly into the text of an awk program. A crafted version string such as:
v1"==$2){system("touch${IFS}/tmp/proof")}#
could break out of the awk string literal and invoke awk's system() function. That gives arbitrary command execution entirely through the awk interpreter, without the shell's eval being involved at all.
Attack Flow
Exploitation follows these steps:
-
The attacker gets control of the content a mirror serves. They can do this by operating a malicious mirror, compromising an existing corporate or internal mirror, running a man-in-the-middle attack on a non-TLS (HTTP) mirror connection, or poisoning DNS.
-
The attacker crafts a malicious
index.tabfile with version strings that contain shell metacharacters or awk escape sequences. To the user, the poisoned entry looks like a normal Node.js version. -
The victim runs
nvm install. nvm fetches theindex.tabfrom the configured mirror and parses the version list, and the user selects the poisoned version (or nvm resolves to it automatically). -
The unsanitized version string flows into either
nvm_download(), whereevalexecutes the embedded command substitution, ornvm_get_checksum(), where awk string interpolation triggerssystem(). Either way, the injected commands run with the full privileges of the user running nvm.
Attack Vector Summary
| Vector | Mechanism | Complexity | Affected Configurations |
|---|---|---|---|
| Malicious mirror operator | Mirror serves crafted index.tab with poisoned version strings | Low | Any user pointing to that mirror |
| Mirror content supply | Attacker provides or alters content served on a non-default mirror | Medium | Corporate/internal mirrors with weak access controls |
| MITM on non-TLS mirror | Attacker intercepts HTTP mirror connection and injects crafted index.tab | Medium | http:// mirror URLs without TLS |
| DNS poisoning | Attacker redirects mirror hostname to attacker-controlled server | High | Any mirror using DNS-resolvable hostnames |
The default mirror (https://nodejs.org over TLS) is not affected. TLS protects against MITM, and the Node.js project controls its own index.tab content.
Relationship to CVE-2026-1665
This vulnerability is directly related to CVE-2026-1665 (CVSS 5.4), which was published on January 29, 2026 and affected nvm versions 0.40.3 and below. That earlier CVE exploited the NVM_AUTH_HEADER environment variable, which was sanitized in the curl code path but not in the wget code path. It was patched in nvm v0.40.4.
The v0.40.4 patch was incomplete. It fixed the one unsanitized NVM_AUTH_HEADER variable and left the underlying eval mechanism in nvm_download() in place. CVE-2026-10796 exploits that same eval sink through a different input: version strings supplied by a remote mirror rather than a local environment variable. That makes CVE-2026-10796 more dangerous in network-facing scenarios, because the attacker does not need local access to set environment variables.
@DavidCarliez is credited with the discovery of CVE-2026-10796 for reporting the eval sink in nvm_download(). The awk injection sink in nvm_get_checksum() turned up during an internal audit prompted by that external report.
Affected Systems and Versions
Affected: nvm (Node Version Manager) versions up to and including 0.40.4.
Fixed in: nvm version 0.40.5.
Vulnerable configurations: Any nvm installation where one or more of the following conditions apply:
- The
NVM_NODEJS_ORG_MIRRORenvironment variable is set to a non-default mirror URL - The
NVM_IOJS_ORG_MIRRORenvironment variable is set to a non-default mirror URL - A mirror URL uses
http://rather thanhttps:// - The configured mirror is operated by an untrusted party or has weak access controls
Not affected: Installations using the default mirror (https://nodejs.org over TLS) with no custom mirror environment variables set.
Note: nvm for Windows (coreybutler/nvm-windows) is a separate project and is not affected by this CVE.
Vendor Security History
nvm's security record shows a recurring pattern of command injection vulnerabilities that come from building commands with eval:
| CVE | Date Published | CVSS | Affected Versions | Root Cause | Fix Version |
|---|---|---|---|---|---|
| CVE-2026-1665 | 2026-01-29 | 5.4 (v4.0) | 0.40.3 and below | Unsanitized NVM_AUTH_HEADER in wget path via eval | 0.40.4 |
| CVE-2026-10796 | 2026-06-04 | 7.5 (v3.1) | 0.40.4 and below | Unsanitized version strings from mirror index.tab via eval and awk interpolation | 0.40.5 |
The OpenCVE database listed only one nvm CVE before CVE-2026-10796. The path from CVE-2026-1665 to CVE-2026-10796 follows a common pattern. When several untrusted data flows converge on the same sink, patching the unsanitized inputs one at a time is not enough if the evaluating execution mechanism stays in place. The v0.40.5 fix goes further. It removes eval entirely, restructures the awk data flow, and adds input validation as defense in depth.
The nvm project accepts private vulnerability reports through GitHub, by email to ljharb, and through Tidelift.
References
- CVE-2026-10796 NVD Entry
- GHSA-3c52-35h2-gfmm: nvm executes commands from a malicious mirror's version strings
- Fix: nvm_download avoid eval so mirror supplied version strings cannot inject commands
- Fix: nvm_download_artifact reject version strings with disallowed characters
- Fix: nvm_get_checksum pass the tarball name to awk as data
- CVE-2026-1665 NVD Detail (related prior CVE)
- SentinelOne: CVE-2026-1665 Node Version Manager RCE
- nvm-sh/nvm Security Advisories
- nvm-sh/nvm Security Overview
- OpenCVE: nvm CVEs and Security Vulnerabilities



