diff --git a/install.sh b/install.sh index 443c038..4dc56c6 100755 --- a/install.sh +++ b/install.sh @@ -119,6 +119,37 @@ need() { fi } +normalize_update_cache_path() { + case "$1" in + /*) printf '%s\n' "$1" ;; + *) printf '%s\n' "$(pwd)/$1" ;; + esac | sed 's#//*#/#g; s#/$##' +} + +safe_update_cache_path() { + raw="$1" + if [ -z "$raw" ]; then + echo "refusing unsafe update cache path: $raw" >&2 + exit 2 + fi + case "$raw" in + "."|".."|./*|../*|*/.|*/..|*/./*|*/../*) + echo "refusing unsafe update cache path: $raw" >&2 + exit 2 + ;; + esac + normalized="$(normalize_update_cache_path "$raw")" + home_norm="$(normalize_update_cache_path "$HOME")" + home_cache_norm="$(normalize_update_cache_path "$HOME/.cache")" + case "$normalized" in + ""|"/"|"$home_norm"|"$home_cache_norm") + echo "refusing unsafe update cache path: $raw" >&2 + exit 2 + ;; + esac + printf '%s\n' "$normalized" +} + ensure_cargo() { if command -v cargo >/dev/null 2>&1; then return @@ -466,12 +497,7 @@ install_from_source() { echo "DOSH_REPO or --repo is required when running the installer from curl" >&2 exit 2 fi - case "$update_cache" in - ""|"/"|"$HOME"|"$HOME/"|"$HOME/.cache") - echo "refusing unsafe update cache path: $update_cache" >&2 - exit 2 - ;; - esac + update_cache="$(safe_update_cache_path "$update_cache")" need git mkdir -p "$(dirname "$update_cache")" if [ -d "$update_cache/.git" ]; then diff --git a/tests/release_scripts.rs b/tests/release_scripts.rs index 603401a..ff5fdda 100644 --- a/tests/release_scripts.rs +++ b/tests/release_scripts.rs @@ -109,6 +109,27 @@ fn windows_installer_reuses_persistent_source_update_cache() { ); } +#[test] +fn unix_installer_rejects_unsafe_source_update_cache_paths() { + let install = include_str!("../install.sh"); + assert!(install.contains("normalize_update_cache_path()")); + assert!(install.contains("safe_update_cache_path()")); + assert!(install.contains("update_cache=\"$(safe_update_cache_path \"$update_cache\")\"")); + for pattern in [ + "\"\"|\"/\"|\"$home_norm\"|\"$home_cache_norm\"", + "\".\"|\"..\"|./*|../*|*/.|*/..|*/./*|*/../*", + ] { + assert!( + install.contains(pattern), + "Unix installer must reject unsafe update cache pattern {pattern}" + ); + } + assert!( + install.contains("rm -rf \"$update_cache\""), + "source cache replacement remains the destructive operation guarded by safe_update_cache_path" + ); +} + #[test] fn package_check_verifies_only_artifacts_it_builds() { let makefile = include_str!("../Makefile");