1
0
mirror of https://github.com/nvm-sh/nvm.git synced 2026-10-26 00:14:32 +08:00

[Fix] nvm_remote_versions: place io.js versions by major version, not at the literal v4.0.0

io.js versions were inserted into node's version list immediately before the `v4.0.0` line.
Whenever node's filtered list did not contain that exact line,
every io.js version was appended after all node versions,
including newer ones,
so `nvm_remote_version`'s `tail -1` could pick an io.js version over the latest matching node version.
Node's list is sorted,
so it is now split at the first version with a nonzero major,
which keeps io.js versions between node v0.x and node v4+ regardless of which node versions the pattern matched

Discovered by Anthropic's OSS Scanner, as ANT-2026-AKH0HYWD; the scanner reported it as a vulnerability, but it is not one.
This commit is contained in:
Jordan Harband
2026-10-18 23:16:40 -07:00
parent 35d6e5a000
commit 59e008fd62
3 changed files with 70 additions and 4 deletions

13
nvm.sh
View File

@@ -1119,8 +1119,13 @@ nvm_remote_versions() {
# extra space is needed here to avoid weird behavior when `nvm_ls_remote` ends in a `*`
NVM_LS_REMOTE_OUTPUT="$(NVM_LTS="${NVM_LTS-}" nvm_ls_remote "${PATTERN-}") " &&:
NVM_LS_REMOTE_EXIT_CODE=$?
# split output into two
NVM_LS_REMOTE_PRE_MERGED_OUTPUT="${NVM_LS_REMOTE_OUTPUT%%v4\.0\.0*}"
# split output into two: node v0.x sorts before io.js, and node v4+ after it.
# the list is sorted, so cut at the first line whose major is nonzero.
case "${NVM_LS_REMOTE_OUTPUT}" in
v[1-9]*) NVM_LS_REMOTE_PRE_MERGED_OUTPUT='' ;;
*) NVM_LS_REMOTE_PRE_MERGED_OUTPUT="${NVM_LS_REMOTE_OUTPUT%%
v[1-9]*}" ;;
esac
NVM_LS_REMOTE_POST_MERGED_OUTPUT="${NVM_LS_REMOTE_OUTPUT#"$NVM_LS_REMOTE_PRE_MERGED_OUTPUT"}"
fi
@@ -1135,7 +1140,7 @@ nvm_remote_versions() {
NVM_LS_REMOTE_IOJS_EXIT_CODE=$?
fi
# the `sed` removes both blank lines, and only-whitespace lines (see "weird behavior" ~19 lines up)
# the `sed` removes both blank lines, and only-whitespace lines (see "weird behavior" ~24 lines up)
VERSIONS="$(nvm_echo "${NVM_LS_REMOTE_PRE_MERGED_OUTPUT}
${NVM_LS_REMOTE_IOJS_OUTPUT}
${NVM_LS_REMOTE_POST_MERGED_OUTPUT}" | nvm_grep -v "N/A" | command sed '/^ *$/d')"
@@ -1144,7 +1149,7 @@ ${NVM_LS_REMOTE_POST_MERGED_OUTPUT}" | nvm_grep -v "N/A" | command sed '/^ *$/d'
nvm_echo 'N/A'
return 3
fi
# the `sed` is to remove trailing whitespaces (see "weird behavior" ~25 lines up)
# the `sed` is to remove trailing whitespaces (see "weird behavior" ~33 lines up)
nvm_echo "${VERSIONS}" | command sed 's/ *$//g'
# shellcheck disable=SC2317
if [ "${NVM_LS_REMOTE_EXIT_CODE}" != '0' ]; then

View File

@@ -70,6 +70,22 @@ try nvm_remote_version node
|| die "nvm_remote_version node did not return contents of nvm_ls_remote node; got $CAPTURED_STDOUT"
[ "_$CAPTURED_EXIT_CODE" = "_0" ] || die "nvm_remote_version node did not exit with 0, got $CAPTURED_EXIT_CODE"
# io.js versions do not sort after newer node versions, even when node's list does not include v4.0.0
nvm_ls_remote() {
case "${1-}" in
20 | v20.0.0) echo "v20.0.0" ;;
*) echo "N/A"; return 3 ;;
esac
}
nvm_ls_remote_iojs() {
echo "iojs-v3.3.1"
}
for PATTERN in 20 v20.0.0; do
try nvm_remote_version "${PATTERN}"
[ "_$CAPTURED_STDOUT" = "_v20.0.0" ] || die "nvm_remote_version ${PATTERN} should return v20.0.0, got $CAPTURED_STDOUT"
[ "_$CAPTURED_EXIT_CODE" = "_0" ] || die "nvm_remote_version ${PATTERN} should exit with code 0, got $CAPTURED_EXIT_CODE"
done
# Test LTS name rejection (Issue #3474)
# When nvm_remote_versions returns a line with LTS name in description,
# nvm_remote_version should reject it if the pattern doesn't match the version number

View File

@@ -69,4 +69,49 @@ try nvm_remote_versions iojs
|| die "nvm_remote_versions iojs did not return contents of nvm_ls_remote_iojs; got $CAPTURED_STDOUT"
[ "_$CAPTURED_EXIT_CODE" = "_0" ] || die "nvm_remote_versions iojs did not exit with 0, got $CAPTURED_EXIT_CODE"
# io.js versions sort after node v0.x and before node v4+, even when node's list has no v4.0.0
nvm_ls_remote() {
echo "v0.10.48"
echo "v0.12.18"
echo "v4.1.0"
echo "v20.0.0 Iron *"
}
nvm_ls_remote_iojs() {
echo "iojs-v1.0.0"
echo "iojs-v3.3.1"
}
try nvm_remote_versions foo
[ "_$CAPTURED_STDOUT" = "_v0.10.48
v0.12.18
iojs-v1.0.0
iojs-v3.3.1
v4.1.0
v20.0.0 Iron *" ] \
|| die "nvm_remote_versions foo did not put io.js versions between node v0.x and node v4+; got $CAPTURED_STDOUT"
[ "_$CAPTURED_EXIT_CODE" = "_0" ] || die "nvm_remote_versions foo did not exit with 0, got $CAPTURED_EXIT_CODE"
nvm_ls_remote() {
echo "v4.1.0"
echo "v20.0.0 Iron *"
}
try nvm_remote_versions foo
[ "_$CAPTURED_STDOUT" = "_iojs-v1.0.0
iojs-v3.3.1
v4.1.0
v20.0.0 Iron *" ] \
|| die "nvm_remote_versions foo did not put io.js versions before node v4+ when node has no v0.x; got $CAPTURED_STDOUT"
[ "_$CAPTURED_EXIT_CODE" = "_0" ] || die "nvm_remote_versions foo did not exit with 0, got $CAPTURED_EXIT_CODE"
nvm_ls_remote() {
echo "v0.10.48"
echo "v0.12.18"
}
try nvm_remote_versions foo
[ "_$CAPTURED_STDOUT" = "_v0.10.48
v0.12.18
iojs-v1.0.0
iojs-v3.3.1" ] \
|| die "nvm_remote_versions foo did not put io.js versions after node v0.x when node has no v4+; got $CAPTURED_STDOUT"
[ "_$CAPTURED_EXIT_CODE" = "_0" ] || die "nvm_remote_versions foo did not exit with 0, got $CAPTURED_EXIT_CODE"
cleanup