[Fix] nvm_ls_remote_index_tab, nvm_remote_version, nvm_ls_cached: pass the version pattern to grep with -e
The requested version pattern was passed to grep as a bare argument, so a pattern starting with `-` was parsed as grep options instead. That pattern can come from a `.nvmrc` in a repository the user cloned, or, for implicit aliases like `node` and `stable`, from the version column of the mirror's index.tab. A pattern like `-Rohenpm_[A-Za-z0-9]*` made GNU grep ignore the version list on stdin and search the current directory recursively, following symlinks, so a string from a local file (such as an npm token in a `.npmrc`) was taken as the "version", printed, and requested from the mirror in the download URLs; since the version list was never read, this also worked with `--offline`. Pass the pattern after `-e` in `nvm_ls_remote_index_tab` (which also covers `nvm_ls_remote`, `nvm_ls_remote_iojs`, and `nvm_remote_versions`), in the post-check in `nvm_remote_version`, and in `nvm_ls_cached`, so a leading `-` is just part of a pattern that matches no version. Discovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-EN9DM7WC.
This commit is contained in:
2 files changed
+69
-3
No files matched your search
@@ -1324,7 +1324,7 @@ nvm_remote_version() {
|
||||
local VERSION_NUM
|
||||
VERSION_NUM="$(nvm_echo "${VERSION}" | command awk '{print $1}')"
|
||||
# the `v` prefix and the trailing non-digit keep `20` from matching `v10.20.0` or `v200.0.0`
|
||||
if ! nvm_echo "${VERSION_NUM} " | nvm_grep -q "$(nvm_ensure_version_prefix "${PATTERN%.}")[^0-9]"; then
|
||||
if ! nvm_echo "${VERSION_NUM} " | nvm_grep -q -e "$(nvm_ensure_version_prefix "${PATTERN%.}")[^0-9]"; then
|
||||
VERSION='N/A'
|
||||
fi
|
||||
fi
|
||||
@@ -2813,7 +2813,7 @@ EOF
|
||||
}
|
||||
prev=$10;
|
||||
}' \
|
||||
| nvm_grep -w "${PATTERN:-.*}" \
|
||||
| nvm_grep -w -e "${PATTERN:-.*}" \
|
||||
| $SORT_COMMAND; } << EOF
|
||||
$VERSION_LIST
|
||||
EOF
|
||||
@@ -4600,7 +4600,7 @@ nvm_ls_cached() {
|
||||
fi
|
||||
} \
|
||||
| command sed 's/^node-//' \
|
||||
| nvm_grep -w "${NVM_CACHED_PATTERN}" \
|
||||
| nvm_grep -w -e "${NVM_CACHED_PATTERN}" \
|
||||
| nvm_grep -v "^${NVM_CACHED_RELEASE}-" \
|
||||
| nvm_filter_installed_versions \
|
||||
| nvm_sort_versions
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/bin/sh
|
||||
|
||||
WORK="$PWD/remote_version-dash-pattern-work.$$"
|
||||
export NVM_DIR="$WORK/.nvm"
|
||||
SECRET='npm_0123456789abcdef'
|
||||
|
||||
cleanup() {
|
||||
cd "$WORK/.." || true
|
||||
unset -f nvm_download nvm_remote_versions
|
||||
rm -rf "$WORK"
|
||||
}
|
||||
die () { echo "$@" ; cleanup ; exit 1; }
|
||||
|
||||
mkdir -p "$NVM_DIR/alias" "$WORK/project"
|
||||
|
||||
\. ../../../nvm.sh
|
||||
|
||||
\. ../../common.sh
|
||||
|
||||
# a file in the working directory that nvm has no business reading
|
||||
echo "//registry.npmjs.org/:_authToken=${SECRET}" > "$WORK/project/.npmrc"
|
||||
cd "$WORK/project" || die 'could not cd to the project dir'
|
||||
|
||||
# with `-r`/`-R` and no file operand, grep ignores stdin and searches the working directory
|
||||
DASH_PATTERN='-Rohenpm_[A-Za-z0-9]*'
|
||||
|
||||
nvm_download() {
|
||||
printf 'version\tdate\tfiles\tnpm\tv8\tuv\tzlib\topenssl\tmodules\tlts\tsecurity\n'
|
||||
printf 'v22.1.0\t2024-05-02\tlinux-x64\t-\t-\t-\t-\t-\t-\t-\t-\n'
|
||||
printf 'v20.0.0\t2023-04-18\tlinux-x64\t-\t-\t-\t-\t-\t-\tIron\t-\n'
|
||||
}
|
||||
|
||||
# a version pattern (eg, from a `.nvmrc`) that starts with a dash is a pattern, not grep options
|
||||
for COMMAND in nvm_ls_remote nvm_ls_remote_iojs nvm_remote_versions nvm_remote_version nvm_ls_cached nvm_offline_version; do
|
||||
OUTPUT="$("${COMMAND}" "${DASH_PATTERN}" 2>&1)"
|
||||
case "${OUTPUT}" in
|
||||
*"${SECRET}"*) die "${COMMAND} parsed its pattern as grep options and read a local file; got >${OUTPUT}<" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
try nvm_remote_version "${DASH_PATTERN}"
|
||||
[ "_${CAPTURED_STDOUT}" = '_N/A' ] || die "nvm_remote_version '${DASH_PATTERN}' did not output N/A; got >${CAPTURED_STDOUT}<"
|
||||
|
||||
try nvm_ls_remote 20
|
||||
[ "_${CAPTURED_STDOUT}" = '_v20.0.0 Iron *' ] || die "nvm_ls_remote 20 did not find v20.0.0; got >${CAPTURED_STDOUT}<"
|
||||
|
||||
# for implicit aliases, the pattern comes from the version column of the mirror's index.tab
|
||||
nvm_download() {
|
||||
printf 'version\tdate\tfiles\tnpm\tv8\tuv\tzlib\topenssl\tmodules\tlts\tsecurity\n'
|
||||
printf '%s\t2026-01-01\tlinux-x64\t-\t-\t-\t-\t-\t-\t-\t-\n' 'v9.9.\|QQQ'
|
||||
printf '%s\t2026-01-01\tlinux-x64\t-\t-\t-\t-\t-\t-\t-\t-\n' '-99rhoe\(QQQ\)\?npm_[A-Za-z0-9]*'
|
||||
}
|
||||
|
||||
for ALIAS in node stable; do
|
||||
OUTPUT="$(nvm_remote_version "${ALIAS}" 2>&1)"
|
||||
case "${OUTPUT}" in
|
||||
*"${SECRET}"*) die "nvm_remote_version ${ALIAS} parsed a mirror-supplied version as grep options; got >${OUTPUT}<" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# the check that the result matches the requested pattern must look only at the result
|
||||
nvm_remote_versions() { nvm_echo 'v1.2.3'; }
|
||||
try nvm_remote_version '-Rqenpm_'
|
||||
[ "_${CAPTURED_STDOUT}" = '_N/A' ] || die "nvm_remote_version '-Rqenpm_' did not output N/A; got >${CAPTURED_STDOUT}<"
|
||||
|
||||
cleanup
|
||||
Reference in new issue
Block a user