medium

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:

  1. uses nvm
  2. runs nvm use, nvm install, nvm which, or nvm alias on attacker-controlled input

The highest-risk cases are easy to picture:

  • developers who clone or review untrusted repositories
  • shell setups that automatically run nvm use on cd
  • CI jobs that trust repository .nvmrc files during bootstrap
  • shared workstation images where $HOME/.nvm lives next to real credential files such as ~/.npmrc or ~/.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 .env files

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 use shell 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:

  1. Upgrade to nvm 0.40.8 immediately.
  2. Treat the host as having disclosed local secrets, even if you did not see obvious failure output at the time.
  3. Rotate any credentials that could have been present in first-line plaintext form, especially npm tokens, Git credentials, cloud credentials, and .env secrets.
  4. Review CI logs, terminal transcripts, and shell histories for the exact disclosure pattern above.
  5. Remove or harden auto-nvm use hooks 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.

References