Files
nvm/test/fast/Unit tests/nvm_is_cached_artifact
T
Jordan Harband 37c578d973 [Fix] nvm_install_binary, nvm_install_source: only use a tarball that nvm_download_artifact put in the cache
`nvm_install_binary` and `nvm_install_source` took the tarball path from the last line of `nvm_download_artifact`'s output via `| tail -1`.
That pipeline's exit status is `tail`'s,
so a failed download went unnoticed,
and the downloader's own stdout was not separated from the path:
anything it printed
(eg, from a curl `write-out` setting)
could be the last line.
If that line happened to name an existing file relative to the current directory,
it was extracted and installed as if it were the downloaded tarball,
instead of the install failing.

`nvm_download_artifact` now sends the downloader's output to stderr,
so its stdout is only ever the tarball path.
Both install functions capture its output directly and honor its exit status,
and accept the result only if it is an existing file inside nvm's cache directory,
via a new `nvm_is_cached_artifact` helper

Discovered by Anthropic's OSS Scanner, as ANT-2026-DESJ2ESZ; the scanner reported it as a vulnerability, but it is not one.
2026-10-23 08:53:58 -07:00

85 lines
2.8 KiB
Bash
Executable File

#!/bin/sh
WORK="${PWD}/nvm-is-cached-artifact-work.$$"
cleanup() {
command rm -rf "${WORK}"
NVM_DIR="${OLD_NVM_DIR}"
unset -f cleanup die check
}
die () { echo "$@" ; cleanup ; exit 1; }
\. ../../../nvm.sh
OLD_NVM_DIR="${NVM_DIR}"
check() {
EXPECTED="${1}"
shift
ACTUAL=0
nvm_is_cached_artifact "$@" || ACTUAL=$?
[ "${ACTUAL}" = "${EXPECTED}" ] || die "nvm_is_cached_artifact '$*' (NVM_DIR '${NVM_DIR}'): expected exit code ${EXPECTED}, got ${ACTUAL}"
}
NVM_DIR="${WORK}/nvm"
CACHE="$(nvm_cache_dir)"
[ "${CACHE}" = "${NVM_DIR}/.cache" ] || die "expected cache dir '${NVM_DIR}/.cache', got '${CACHE}'"
command mkdir -p "${CACHE}/bin/node-v8.0.0-linux-x64" "${CACHE}/src/node-v8.0.0/files" "${WORK}/project" "${WORK}/nvm/.cache-other" "${WORK}/outside"
echo tarball > "${CACHE}/bin/node-v8.0.0-linux-x64/node-v8.0.0-linux-x64.tar.xz"
echo tarball > "${CACHE}/top-level.tar.gz"
echo not-a-cached-tarball > "${WORK}/project/404"
echo not-a-cached-tarball > "${WORK}/outside/node-v8.0.0.tar.xz"
echo not-a-cached-tarball > "${WORK}/nvm/.cache-other/node-v8.0.0.tar.xz"
echo not-a-cached-tarball > "${WORK}/nvm/.cachefile"
# existing files inside the cache are accepted, at any depth
check 0 "${CACHE}/bin/node-v8.0.0-linux-x64/node-v8.0.0-linux-x64.tar.xz"
check 0 "${CACHE}/top-level.tar.gz"
# no argument, or an empty one, is rejected
check 1
check 1 ''
# paths in the cache that are not existing regular files are rejected
check 1 "${CACHE}"
check 1 "${CACHE}/"
check 1 "${CACHE}/bin"
check 1 "${CACHE}/src/node-v8.0.0/files"
check 1 "${CACHE}/bin/node-v8.0.0-linux-x64/missing.tar.xz"
# existing files outside the cache are rejected
check 1 "${WORK}/outside/node-v8.0.0.tar.xz"
check 1 "${NVM_DIR}/.cachefile"
check 1 "${NVM_DIR}/.cache-other/node-v8.0.0.tar.xz"
# a downloader's stray output that names a file relative to the current directory is rejected
(
cd "${WORK}/project" || exit 2
[ -f 404 ] || exit 2
nvm_is_cached_artifact 404 && exit 3
exit 0
)
RESULT=$?
[ "${RESULT}" = 0 ] || die "nvm_is_cached_artifact '404' in a directory containing '404': expected rejection, got subshell exit code ${RESULT}"
# a relative path that happens to reach a cached file is still rejected, since it is not under the cache dir
(
cd "${CACHE}" || exit 2
[ -f top-level.tar.gz ] || exit 2
nvm_is_cached_artifact top-level.tar.gz && exit 3
exit 0
)
RESULT=$?
[ "${RESULT}" = 0 ] || die "nvm_is_cached_artifact 'top-level.tar.gz' from inside the cache dir: expected rejection, got subshell exit code ${RESULT}"
# glob characters in NVM_DIR are matched literally, not as a pattern
NVM_DIR="${WORK}/n*"
command mkdir -p "$(nvm_cache_dir)"
echo tarball > "$(nvm_cache_dir)/glob.tar.xz"
echo not-a-cached-tarball > "${WORK}/nvm/.cache/glob.tar.xz"
check 0 "${WORK}/n*/.cache/glob.tar.xz"
check 1 "${WORK}/nvm/.cache/glob.tar.xz"
cleanup