CVE
CVE-2026-94185
CWE
CWE-22, CWE-200
Affected Surface
- nvm versions 0.0.1 through 0.40.7
- Developer workstations that automatically run nvm when changing into a repository directory
- CI runners, onboarding scripts, and shell profiles that invoke nvm use, nvm install, or nvm which in repositories with attacker-controlled .nvmrc files
- Teams whose home-directory credential files sit near $NVM_DIR, including ~/.npmrc, ~/.aws/credentials, ~/.git-credentials, ~/.netrc, and other token-bearing plaintext files
nvm has another trust-boundary bug, but this one is not about mirrors, checksums, or install-time command execution. CVE-2026-94185 is a read-path traversal in alias resolution. If a developer enters an attacker-controlled repository and nvm reads a hostile .nvmrc, the requested version string can be interpreted as a path under $NVM_DIR/alias, escape that directory with .., and disclose local file contents.
That makes the bug operationally different from the earlier CVE-2026-10796 mirror-command-injection issue. The old bug needed a malicious or intercepted mirror. This one needs no network control at all. A repo checkout is enough.
GitHub published the advisory on 21 September 2026, NVD listed the issue the same day, and nvm shipped v0.40.8 with a focused fix: reject .. path components in both alias resolution and version-path construction.
What is affected
The affected range is nvm <= 0.40.7. In practice, that means any host that both:
- uses
nvm - runs
nvm use,nvm install,nvm which, ornvm aliason attacker-controlled input
The highest-risk cases are easy to picture:
- developers who clone or review untrusted repositories
- shell setups that automatically run
nvm useoncd - CI jobs that trust repository
.nvmrcfiles during bootstrap - shared workstation images where
$HOME/.nvmlives next to real credential files such as~/.npmrcor~/.aws/credentials
The advisory notes that the realistic targets are not just demonstration files like /etc/passwd. They are the first plaintext secrets a developer home directory usually exposes:
~/.npmrc~/.git-credentials~/.netrc~/.aws/credentials- token-bearing
.envfiles
The vulnerable path is short and direct
The core of the bug is in nvm_alias(). In v0.40.7, the function accepts an alias name, normalizes LTS names, and then composes a filesystem path by simple concatenation:
nvm_alias() {
local ALIAS
ALIAS="${1-}"
if ! ALIAS="$(nvm_normalize_lts "${ALIAS}")"; then
return $?
fi
local NVM_ALIAS_PATH
NVM_ALIAS_PATH="$(nvm_alias_path)/${ALIAS}"
if [ ! -f "${NVM_ALIAS_PATH}" ]; then
nvm_err 'Alias does not exist.'
return 2
fi
nvm_print_alias_file "${NVM_ALIAS_PATH}"
}
There is no check here that ALIAS stays inside $NVM_DIR/alias. If the alias contains a .. segment, the path escapes the alias directory and nvm_print_alias_file() reads whatever file lands at the resulting location.
The repo-controlled path is just as important as the sink:
nvm_process_nvmrc
-> nvm_rc_version
-> nvm_version
-> nvm_ls
-> nvm_resolve_alias
-> nvm_alias
That means a plain .nvmrc file can supply the traversing string. The advisory’s proof of concept uses:
printf '../../../../etc/passwd\n' > .nvmrc
nvm use
The result is not code execution. It is disclosure. nvm use resolves the alias, takes the first non-comment line returned from the target file, and includes that line in the familiar “is not yet installed” error path. The more explicit nvm alias '../../../../etc/passwd' form prints every non-empty, non-comment line that nvm_print_alias_file() reads.
Why .npmrc is a better target than /etc/passwd
The most realistic target is not /etc/passwd. It is a credential file that sits close to $NVM_DIR under the default layout.
If $NVM_DIR is ~/.nvm, then this traversal:
../../.npmrc
walks from:
$HOME/.nvm/alias/../../.npmrc
to:
$HOME/.npmrc
That is exactly the kind of file developers keep on machines that also run nvm. For many teams, the first non-comment line of ~/.npmrc is an auth token. The advisory also points out that the lts/ namespace does not save you. A name such as:
lts/../../../.npmrc
still reaches the same sink because nvm_normalize_lts() passes lowercase lts/* names through unchanged.
This is why the issue matters for application security teams even though the CVSS score is moderate. The bug turns routine repository navigation into local secret disclosure on the exact workstations and runners that already hold registry tokens, cloud credentials, and deployment access.
The fix is simple and in the right place
The v0.40.8 patch does not try to sanitize after resolution. It blocks traversal before the path is used.
On the alias read path, nvm now rejects any alias name whose slash-delimited components include ..:
case "/${ALIAS}/" in
*/../*)
nvm_err "invalid alias name: ${ALIAS}"
return 3
;;
esac
The same release adds an equivalent guard in nvm_version_path():
case "/${VERSION}/" in
*/../*)
nvm_err "invalid version: ${VERSION}"
return 3
;;
esac
That second change matters because the bug is bigger than one helper. nvm_version_path() also builds filesystem paths from user-influenced version strings, so the fix closes both the explicit alias path and the generic version-path composition that feeds other nvm flows.
The release notes summarize the security change in one line:
`nvm_alias`, `nvm_version_path`: reject `..` path components
That is a good sign. The remediation boundary is clear and easy to verify: anything below 0.40.8 should be treated as vulnerable.
What exploitation looks like in practice
There are two realistic operator paths.
1. Repository-triggered disclosure
An attacker commits a malicious .nvmrc to a repository:
../../.npmrc
or:
lts/../../../.aws/credentials
Then the victim clones the repo and either:
- runs
nvm use - runs
nvm install - runs
nvm which - changes into the directory while using an auto-
nvm useshell hook
At that point, nvm reads outside its alias directory and prints the first meaningful line of the target file into the terminal or CI logs.
2. Direct alias abuse
If a user or script runs:
nvm alias '../../../../etc/hosts'
the output path is even noisier. nvm_print_alias_file() prints each non-empty, non-comment line it reads. That turns the command into a full-file disclosure primitive for any readable plaintext target.
Neither path requires network control. Neither path requires write access to the victim’s home directory. The attacker needs only a path to get a traversing string into nvm’s alias resolver.
Detection and scoping
Start with version inventory:
command -v nvm >/dev/null 2>&1 && nvm --version
Then look for automatic nvm execution on directory change:
rg -n 'load-nvmrc|cdnvm|nvm use --silent|chpwd.*nvm|auto.*nvm' \
~/.bashrc ~/.bash_profile ~/.profile ~/.zshrc ~/.zprofile ~/.config/fish/config.fish
Search trusted repositories and CI bootstrap scripts for nvm commands that consume .nvmrc values:
rg -n 'nvm (use|install|which)' .github/workflows .gitlab-ci.yml .circleci scripts
Search checked-out repositories for suspicious .nvmrc content:
rg -n '(^|/)\.\./|^lts/.*/\.\./' --glob '.nvmrc' . "$HOME/src" "$HOME/work"
If you suspect exposure, review shell history and CI logs for the failure shape described in the advisory:
N/A: version "../../.npmrc -> //registry.npmjs.org/:_authToken=..." is not yet installed.
That string is high-signal evidence that alias resolution already crossed the boundary and printed secret material.
Response guidance
If any developer workstation or runner executed vulnerable nvm commands against an untrusted repo:
- Upgrade to
nvm 0.40.8immediately. - Treat the host as having disclosed local secrets, even if you did not see obvious failure output at the time.
- Rotate any credentials that could have been present in first-line plaintext form, especially npm tokens, Git credentials, cloud credentials, and
.envsecrets. - Review CI logs, terminal transcripts, and shell histories for the exact disclosure pattern above.
- Remove or harden auto-
nvm usehooks if your developers regularly enter untrusted repositories.
The main constraint on the attacker is file format. nvm use only exposes the first non-comment line through the error message path. But that still covers many real-world secret files. nvm alias <traversal> is stronger because it prints the full file, line by line.
Why this still belongs in the supply-chain conversation
This is not a registry compromise. It is still a supply-chain problem.
The vulnerable boundary is developer tooling that consumes repository content and then reads local secrets from the machine doing the build or review. That is the same class of failure that keeps showing up in package managers, version managers, IDE extensions, and CI bootstrap helpers: a tool meant to smooth developer workflows quietly becomes part of the trust boundary around credentials and release infrastructure.
For teams that use .nvmrc files throughout their repos, CVE-2026-94185 is a reminder to treat repository metadata as hostile until the tool that parses it proves otherwise. In nvm, that proof now starts at 0.40.8.
From research to remediation
Check whether this pattern exists in your codebase
Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.