mirror of
https://github.com/nvm-sh/nvm.git
synced 2026-10-26 00:14:32 +08:00
[Fix] nvm.sh: detect NVM_DIR without $0/$_; fall back to the default install location instead of the working directory
When `nvm.sh` is sourced with `NVM_DIR` unset,
it derived `NVM_DIR` from the sourced file in bash
(via `BASH_SOURCE`),
and otherwise from `dirname "${_:-$0}"`.
In shells that do not set `BASH_SOURCE`
(dash, sh, ksh, and zsh under `emulate sh` or without `FUNCTION_ARGZERO`),
`$0` is the shell's or the calling script's name and `$_` is whatever was inherited,
so `NVM_DIR` became an unrelated directory:
for `sh -c '. ~/.nvm/nvm.sh'`,
as run by `system(3)` or `popen(3)`,
that is the working directory,
whose `alias/default` and `versions/` nvm then used,
and from which `nvm cache clear` and friends would operate.
Detection now uses only reliable sources:
`BASH_SOURCE` in bash,
`${(%):-%x}` in zsh
(which names the sourced file regardless of options),
and `${.sh.file}` in ksh93
(probed in a subshell first, since other shells with `KSH_VERSION` reject the syntax)
A detected source that is not a regular file
(eg, `/dev/fd/63` from `. <(…)`)
is ignored.
Otherwise, nvm uses the default install location that `install.sh` uses
(`$XDG_CONFIG_HOME/nvm`, else `~/.nvm`)
when it contains `nvm.sh`, and if not,
it prints an error asking for `NVM_DIR` to be set and returns 1,
instead of guessing.
Setups that export `NVM_DIR` before sourcing `nvm.sh`,
as `install.sh` and the README do, are unaffected
Discovered by Anthropic's OSS Scanner, as ANT-2026-8RIT0MGO; the scanner reported it as a vulnerability, but it is not one.
This commit is contained in:
@@ -909,7 +909,7 @@ Nota bene: Avoid running nvm while the tests are running.
|
||||
|
||||
nvm exposes the following environment variables:
|
||||
|
||||
- `NVM_DIR` - nvm's installation directory.
|
||||
- `NVM_DIR` - nvm's installation directory. If it is not set when `nvm.sh` is sourced, bash, zsh, and ksh93 use the directory containing `nvm.sh`; other shells (like dash) cannot tell where a sourced file lives, so they use the default install location (`$XDG_CONFIG_HOME/nvm` when `XDG_CONFIG_HOME` is set, otherwise `~/.nvm`). If nvm is installed elsewhere, set `NVM_DIR` before sourcing `nvm.sh`.
|
||||
- `NVM_BIN` - where node, npm, and global packages for the active version of node are installed.
|
||||
- `NVM_INC` - node's include file directory (useful for building C/C++ addons for node).
|
||||
- `NVM_CD_FLAGS` - used to maintain compatibility with zsh.
|
||||
|
||||
35
nvm.sh
35
nvm.sh
@@ -10,9 +10,6 @@
|
||||
# shellcheck disable=SC2039,SC2016,SC2001,SC3043
|
||||
{ # this ensures the entire script is downloaded #
|
||||
|
||||
# shellcheck disable=SC3028
|
||||
NVM_SCRIPT_SOURCE="${_:-}"
|
||||
|
||||
nvm_is_zsh() {
|
||||
[ -n "${ZSH_VERSION-}" ]
|
||||
}
|
||||
@@ -589,12 +586,40 @@ fi
|
||||
|
||||
# Auto detect the NVM_DIR when not set
|
||||
if [ -z "${NVM_DIR-}" ]; then
|
||||
NVM_SCRIPT_SOURCE=''
|
||||
# shellcheck disable=SC2128
|
||||
if [ -n "${BASH_SOURCE-}" ]; then
|
||||
NVM_SCRIPT_SOURCE="${BASH_SOURCE}"
|
||||
elif nvm_is_zsh; then
|
||||
# `%x` names the file being sourced even when `$0` does not, eg under `emulate sh`
|
||||
eval 'NVM_SCRIPT_SOURCE="${(%):-%x}"'
|
||||
elif [ -n "${KSH_VERSION-}" ] && (eval ': "${.sh.file}"') 2>/dev/null; then
|
||||
# only ksh93 has `.sh.file`; other shells abort on the syntax, so it is probed in a subshell first
|
||||
eval 'NVM_SCRIPT_SOURCE="${.sh.file}"'
|
||||
fi
|
||||
if [ -n "${NVM_SCRIPT_SOURCE}" ] && [ -f "${NVM_SCRIPT_SOURCE}" ]; then
|
||||
# shellcheck disable=SC2086
|
||||
NVM_DIR="$(nvm_cd ${NVM_CD_FLAGS} "$(dirname "${NVM_SCRIPT_SOURCE}")" >/dev/null && \pwd)"
|
||||
else
|
||||
# other shells (eg, dash) cannot tell where a sourced file lives (`$0` names the shell or the calling script),
|
||||
# and a file sourced from a pipe (eg, `/dev/fd/63`) has no directory, so use the default install location, as `install.sh` does
|
||||
if [ -n "${XDG_CONFIG_HOME-}" ]; then
|
||||
NVM_DIR="${XDG_CONFIG_HOME}/nvm"
|
||||
else
|
||||
NVM_DIR="${HOME-}/.nvm"
|
||||
fi
|
||||
if [ -f "${NVM_DIR}/nvm.sh" ]; then
|
||||
# shellcheck disable=SC2086
|
||||
NVM_DIR="$(nvm_cd ${NVM_CD_FLAGS} "${NVM_DIR}" >/dev/null && \pwd)"
|
||||
else
|
||||
NVM_DIR=''
|
||||
fi
|
||||
fi
|
||||
if [ -z "${NVM_DIR}" ]; then
|
||||
unset NVM_DIR NVM_SCRIPT_SOURCE 2>/dev/null
|
||||
nvm_err 'nvm: unable to detect $NVM_DIR; set it to the directory containing nvm.sh before sourcing it'
|
||||
return 1
|
||||
fi
|
||||
# shellcheck disable=SC2086
|
||||
NVM_DIR="$(nvm_cd ${NVM_CD_FLAGS} "$(dirname "${NVM_SCRIPT_SOURCE:-$0}")" >/dev/null && \pwd)"
|
||||
export NVM_DIR
|
||||
else
|
||||
# https://unix.stackexchange.com/a/198289
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/bin/sh
|
||||
|
||||
\. ../common.sh
|
||||
|
||||
cleanup() {
|
||||
[ -n "${TMP_DIR-}" ] && rm -rf "${TMP_DIR}"
|
||||
}
|
||||
die () { echo "$@" ; cleanup ; exit 1; }
|
||||
|
||||
NVM_ROOT="$(cd ../.. && pwd)"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
[ -n "${TMP_DIR}" ] || die 'could not create a temporary directory'
|
||||
|
||||
# a working directory laid out like an nvm directory, with a `default` alias and that version installed
|
||||
mkdir -p "${TMP_DIR}/cwd/alias" "${TMP_DIR}/cwd/versions/node/v9.9.9/bin" || die 'could not create the working directory'
|
||||
echo 'v9.9.9' > "${TMP_DIR}/cwd/alias/default" || die 'could not create the default alias'
|
||||
make_echo "${TMP_DIR}/cwd/versions/node/v9.9.9/bin/node" 'v9.9.9'
|
||||
|
||||
# a HOME whose default install location is this checkout, and one with no install at all
|
||||
mkdir "${TMP_DIR}/home" "${TMP_DIR}/empty-home" "${TMP_DIR}/xdg" || die 'could not create home directories'
|
||||
ln -s "${NVM_ROOT}" "${TMP_DIR}/home/.nvm" || die 'could not link ~/.nvm'
|
||||
ln -s "${NVM_ROOT}" "${TMP_DIR}/xdg/nvm" || die 'could not link the XDG nvm directory'
|
||||
|
||||
# sources nvm.sh from the working directory, with NVM_DIR unset, and prints the detected NVM_DIR and node
|
||||
source_from_cwd() {
|
||||
(
|
||||
cd "${TMP_DIR}/cwd" || exit 1
|
||||
unset NVM_DIR XDG_CONFIG_HOME
|
||||
HOME="${1}"
|
||||
export HOME
|
||||
if [ -n "${2-}" ]; then
|
||||
XDG_CONFIG_HOME="${2}"
|
||||
export XDG_CONFIG_HOME
|
||||
fi
|
||||
set --
|
||||
\. "${NVM_ROOT}/nvm.sh" >/dev/null 2>&1 || exit $?
|
||||
echo "${NVM_DIR-}"
|
||||
command -v node || :
|
||||
)
|
||||
}
|
||||
|
||||
try source_from_cwd "${TMP_DIR}/home"
|
||||
[ "${CAPTURED_EXIT_CODE}" = 0 ] || die "sourcing nvm.sh without NVM_DIR failed with ${CAPTURED_EXIT_CODE}"
|
||||
DETECTED="$(echo "${CAPTURED_STDOUT}" | sed -n 1p)"
|
||||
NODE_PATH_FOUND="$(echo "${CAPTURED_STDOUT}" | sed -n 2p)"
|
||||
case "${NODE_PATH_FOUND}" in
|
||||
"${TMP_DIR}/cwd"/*) die "sourcing nvm.sh without NVM_DIR put the working directory's node on PATH: ${NODE_PATH_FOUND}" ;;
|
||||
esac
|
||||
case "${DETECTED}" in
|
||||
"${NVM_ROOT}" | "${TMP_DIR}/home/.nvm") ;;
|
||||
*) die "sourcing nvm.sh without NVM_DIR detected >${DETECTED}<; expected >${NVM_ROOT}< or >${TMP_DIR}/home/.nvm<" ;;
|
||||
esac
|
||||
|
||||
# shells that cannot locate a sourced file use the default install location, or refuse when there is none
|
||||
if [ "${DETECTED}" = "${TMP_DIR}/home/.nvm" ]; then
|
||||
try source_from_cwd "${TMP_DIR}/empty-home" "${TMP_DIR}/xdg"
|
||||
[ "${CAPTURED_EXIT_CODE}" = 0 ] || die "sourcing nvm.sh with XDG_CONFIG_HOME failed with ${CAPTURED_EXIT_CODE}"
|
||||
DETECTED="$(echo "${CAPTURED_STDOUT}" | sed -n 1p)"
|
||||
[ "${DETECTED}" = "${TMP_DIR}/xdg/nvm" ] || die "sourcing nvm.sh with XDG_CONFIG_HOME detected >${DETECTED}<; expected >${TMP_DIR}/xdg/nvm<"
|
||||
|
||||
try source_from_cwd "${TMP_DIR}/empty-home"
|
||||
[ "${CAPTURED_EXIT_CODE}" != 0 ] || die "sourcing nvm.sh with no default install location succeeded, with NVM_DIR >${CAPTURED_STDOUT}<"
|
||||
[ -z "${CAPTURED_STDOUT}" ] || die "sourcing nvm.sh with no default install location printed >${CAPTURED_STDOUT}<"
|
||||
fi
|
||||
|
||||
cleanup
|
||||
Reference in New Issue
Block a user