[Fix] install.sh: a failed script-method download no longer clobbers the install or reports success
With the script install method, `install_nvm_as_script` downloaded `nvm.sh`, `nvm-exec`, and `bash_completion` directly over the installed files. A dropped connection with curl left a truncated `nvm.sh`, and any wget failure (even just being offline) left all three files empty. Its failure was also ignored by `nvm_do_install`, which went on to edit the profile, source the broken `nvm.sh`, print the success message, and exit 0. Now each file is downloaded to a temporary `.download` name, every download is waited on (by PID, rather than returning while other downloads may still be writing), and the files are moved into place only once all of them have succeeded; on failure, the partial downloads are removed and the existing install is left untouched. `nvm_do_install` now exits with `install_nvm_as_script`'s failure status, matching the git install method Discovered by Anthropic's OSS Scanner, as bug ANT-2026-8TH8YGY5; the scanner correctly classified it as not a vulnerability.
This commit is contained in:
2 files changed
+103
-8
No files matched your search
+32
-8
@@ -272,26 +272,50 @@ install_nvm_as_script() {
|
||||
else
|
||||
nvm_echo "=> Downloading nvm as script to '$INSTALL_DIR'"
|
||||
fi
|
||||
nvm_download -s "$NVM_SOURCE_LOCAL" -o "$INSTALL_DIR/nvm.sh" || {
|
||||
# download to temporary names, and only move them into place once every download has succeeded,
|
||||
# so that a failed download never replaces a working install with a partial or empty file
|
||||
nvm_download -s "$NVM_SOURCE_LOCAL" -o "$INSTALL_DIR/nvm.sh.download" || {
|
||||
nvm_echo >&2 "Failed to download '$NVM_SOURCE_LOCAL'"
|
||||
return 1
|
||||
} &
|
||||
nvm_download -s "$NVM_EXEC_SOURCE" -o "$INSTALL_DIR/nvm-exec" || {
|
||||
local NVM_SH_PID
|
||||
NVM_SH_PID=$!
|
||||
nvm_download -s "$NVM_EXEC_SOURCE" -o "$INSTALL_DIR/nvm-exec.download" || {
|
||||
nvm_echo >&2 "Failed to download '$NVM_EXEC_SOURCE'"
|
||||
return 2
|
||||
} &
|
||||
nvm_download -s "$NVM_BASH_COMPLETION_SOURCE" -o "$INSTALL_DIR/bash_completion" || {
|
||||
local NVM_EXEC_PID
|
||||
NVM_EXEC_PID=$!
|
||||
nvm_download -s "$NVM_BASH_COMPLETION_SOURCE" -o "$INSTALL_DIR/bash_completion.download" || {
|
||||
nvm_echo >&2 "Failed to download '$NVM_BASH_COMPLETION_SOURCE'"
|
||||
return 2
|
||||
} &
|
||||
for job in $(jobs -p | command sort)
|
||||
local NVM_BASH_COMPLETION_PID
|
||||
NVM_BASH_COMPLETION_PID=$!
|
||||
# wait for every download, so that none is still writing when the partial files are removed
|
||||
local NVM_DOWNLOAD_STATUS
|
||||
NVM_DOWNLOAD_STATUS=0
|
||||
local job
|
||||
for job in "$NVM_SH_PID" "$NVM_EXEC_PID" "$NVM_BASH_COMPLETION_PID"
|
||||
do
|
||||
wait "$job" || return $?
|
||||
wait "$job" || NVM_DOWNLOAD_STATUS=$?
|
||||
done
|
||||
chmod a+x "$INSTALL_DIR/nvm-exec" || {
|
||||
if [ "$NVM_DOWNLOAD_STATUS" -ne 0 ]; then
|
||||
command rm -f "$INSTALL_DIR/nvm.sh.download" "$INSTALL_DIR/nvm-exec.download" "$INSTALL_DIR/bash_completion.download"
|
||||
return "$NVM_DOWNLOAD_STATUS"
|
||||
fi
|
||||
chmod a+x "$INSTALL_DIR/nvm-exec.download" || {
|
||||
nvm_echo >&2 "Failed to mark '$INSTALL_DIR/nvm-exec' as executable"
|
||||
return 3
|
||||
}
|
||||
local NVM_SCRIPT_FILE
|
||||
for NVM_SCRIPT_FILE in nvm-exec bash_completion nvm.sh
|
||||
do
|
||||
command mv -f "$INSTALL_DIR/$NVM_SCRIPT_FILE.download" "$INSTALL_DIR/$NVM_SCRIPT_FILE" || {
|
||||
nvm_echo >&2 "Failed to move '$INSTALL_DIR/$NVM_SCRIPT_FILE.download' into place"
|
||||
return 3
|
||||
}
|
||||
done
|
||||
}
|
||||
|
||||
nvm_try_profile() {
|
||||
@@ -428,7 +452,7 @@ nvm_do_install() {
|
||||
if nvm_has git; then
|
||||
install_nvm_from_git
|
||||
elif nvm_has_executable curl || nvm_has_executable wget; then
|
||||
install_nvm_as_script
|
||||
install_nvm_as_script || exit $?
|
||||
else
|
||||
nvm_echo >&2 'You need git, curl, or wget to install nvm'
|
||||
exit 1
|
||||
@@ -444,7 +468,7 @@ nvm_do_install() {
|
||||
nvm_echo >&2 "You need curl or wget to install nvm"
|
||||
exit 1
|
||||
fi
|
||||
install_nvm_as_script
|
||||
install_nvm_as_script || exit $?
|
||||
else
|
||||
nvm_echo >&2 "The environment variable \$METHOD is set to \"${METHOD}\", which is not recognized as a valid installation method."
|
||||
exit 1
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
|
||||
cleanup () {
|
||||
NVM_DIR="$SAVE_NVM_DIR"
|
||||
if [ -n "${TEST_DIR-}" ]; then
|
||||
rm -rf "${TEST_DIR}"
|
||||
fi
|
||||
unset SAVE_NVM_DIR TEST_DIR OUTPUT EXIT_CODE FILE
|
||||
unset -f die cleanup nvm_download install_nvm_as_script
|
||||
}
|
||||
die () { echo "$@" ; cleanup ; exit 1; }
|
||||
|
||||
SAVE_NVM_DIR="$NVM_DIR"
|
||||
|
||||
NVM_ENV=testing \. ../../install.sh
|
||||
|
||||
type install_nvm_as_script > /dev/null 2>&1 || die 'install_nvm_as_script is not available'
|
||||
|
||||
TEST_DIR="$(mktemp -d)"
|
||||
NVM_DIR="${TEST_DIR}/nvm"
|
||||
mkdir -p "${NVM_DIR}"
|
||||
echo 'working nvm.sh' > "${NVM_DIR}/nvm.sh"
|
||||
|
||||
# like curl, write part of the file and then fail, as when the connection drops part-way through nvm.sh
|
||||
nvm_download() {
|
||||
case "${2}" in
|
||||
*/nvm.sh)
|
||||
echo 'partial' > "${4}"
|
||||
return 56
|
||||
;;
|
||||
esac
|
||||
echo "${2}" > "${4}"
|
||||
}
|
||||
|
||||
## install_nvm_as_script: a failed download fails, and leaves the existing install untouched
|
||||
OUTPUT="$(install_nvm_as_script 2>&1)"
|
||||
EXIT_CODE=$?
|
||||
[ "${EXIT_CODE}" != '0' ] || die "install_nvm_as_script should fail when a download fails, got ${EXIT_CODE}"
|
||||
echo "${OUTPUT}" | grep -q "Failed to download" || die "install_nvm_as_script should print the download error, got: ${OUTPUT}"
|
||||
[ "$(cat "${NVM_DIR}/nvm.sh")" = 'working nvm.sh' ] || die "a failed download replaced the working nvm.sh with: $(cat "${NVM_DIR}/nvm.sh")"
|
||||
[ ! -e "${NVM_DIR}/nvm-exec" ] || die 'nvm-exec was installed although the nvm.sh download failed'
|
||||
[ ! -e "${NVM_DIR}/bash_completion" ] || die 'bash_completion was installed although the nvm.sh download failed'
|
||||
for FILE in nvm.sh nvm-exec bash_completion; do
|
||||
[ ! -e "${NVM_DIR}/${FILE}.download" ] || die "a failed download left '${FILE}.download' behind"
|
||||
done
|
||||
|
||||
## install_nvm_as_script: when every download succeeds, all three files are installed
|
||||
nvm_download() {
|
||||
echo "${2}" > "${4}"
|
||||
}
|
||||
OUTPUT="$(install_nvm_as_script 2>&1)"
|
||||
EXIT_CODE=$?
|
||||
[ "${EXIT_CODE}" = '0' ] || die "install_nvm_as_script should succeed when every download succeeds, got ${EXIT_CODE}: ${OUTPUT}"
|
||||
[ "$(cat "${NVM_DIR}/nvm.sh")" = "$(nvm_source script)" ] || die "nvm.sh was not updated, got: $(cat "${NVM_DIR}/nvm.sh")"
|
||||
[ "$(cat "${NVM_DIR}/nvm-exec")" = "$(nvm_source script-nvm-exec)" ] || die "nvm-exec was not installed, got: $(cat "${NVM_DIR}/nvm-exec")"
|
||||
[ "$(cat "${NVM_DIR}/bash_completion")" = "$(nvm_source script-nvm-bash-completion)" ] || die "bash_completion was not installed, got: $(cat "${NVM_DIR}/bash_completion")"
|
||||
[ -x "${NVM_DIR}/nvm-exec" ] || die 'nvm-exec was not marked executable'
|
||||
for FILE in nvm.sh nvm-exec bash_completion; do
|
||||
[ ! -e "${NVM_DIR}/${FILE}.download" ] || die "a successful download left '${FILE}.download' behind"
|
||||
done
|
||||
|
||||
## nvm_do_install: a failed script install exits non-zero, before touching the profile or sourcing nvm.sh
|
||||
install_nvm_as_script() {
|
||||
return 2
|
||||
}
|
||||
OUTPUT="$(METHOD=script PROFILE=/dev/null nvm_do_install 2>&1)"
|
||||
EXIT_CODE=$?
|
||||
[ "${EXIT_CODE}" = '2' ] || die "nvm_do_install should exit 2 when install_nvm_as_script fails, got ${EXIT_CODE}: ${OUTPUT}"
|
||||
! echo "${OUTPUT}" | grep -q 'Close and reopen your terminal' || die "nvm_do_install reported success although install_nvm_as_script failed: ${OUTPUT}"
|
||||
|
||||
cleanup
|
||||
Reference in new issue
Block a user