mirror of
https://github.com/moraroy/NonSteamLaunchers-On-Steam-Deck.git
synced 2026-08-03 13:27:30 +03:00
Merge pull request #943 from dadtronics/security/harden-downloads-and-deletes
feat: harden downloads, deletes, debug tracing, and sudo handling
This commit is contained in:
+9
-11
@@ -4,14 +4,12 @@
|
||||
logged_in_user=$(logname 2>/dev/null || whoami)
|
||||
logged_in_home=$(eval echo "~${logged_in_user}")
|
||||
|
||||
# Function to prompt for sudo password
|
||||
# Function to prompt for sudo using sudo's normal credential handling, instead of
|
||||
# capturing the password with zenity and piping it into `sudo -S` (which exposes it
|
||||
# via the environment/argv and process listings).
|
||||
prompt_for_sudo() {
|
||||
password=$(zenity --password --title="Authentication Required" --text="Please enter your password to proceed with installation/update.")
|
||||
|
||||
# Validate password
|
||||
echo "$password" | sudo -S -v >/dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
zenity --error --text="Incorrect password or sudo failed. Exiting."
|
||||
if ! sudo -v; then
|
||||
zenity --error --text="Sudo authentication failed. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
@@ -117,14 +115,14 @@ else
|
||||
|
||||
if $NSL_PLUGIN_EXISTS; then
|
||||
show_message "NSL Plugin detected. Deleting and updating..."
|
||||
echo "$password" | sudo -S rm -rf "$LOCAL_DIR"
|
||||
sudo rm -rf "$LOCAL_DIR"
|
||||
fi
|
||||
|
||||
show_message "Creating base directory and setting permissions..."
|
||||
|
||||
echo "$password" | sudo -S mkdir -p "$LOCAL_DIR"
|
||||
echo "$password" | sudo -S chmod -R u+rw "$LOCAL_DIR"
|
||||
echo "$password" | sudo -S chown -R "$logged_in_user:$logged_in_user" "$LOCAL_DIR"
|
||||
sudo mkdir -p "$LOCAL_DIR"
|
||||
sudo chmod -R u+rw "$LOCAL_DIR"
|
||||
sudo chown -R "$logged_in_user:$logged_in_user" "$LOCAL_DIR"
|
||||
|
||||
curl -L "$REPO_URL" -o /tmp/NonSteamLaunchersDecky.zip
|
||||
unzip -o /tmp/NonSteamLaunchersDecky.zip -d /tmp/
|
||||
|
||||
+161
-17
@@ -6,7 +6,10 @@ flock -n 200 || {
|
||||
exit 0
|
||||
}
|
||||
|
||||
set -x # activate debugging (execution shown)
|
||||
if [[ "${NSL_DEBUG:-0}" == "1" ]]; then
|
||||
set -x # activate debugging only when requested; otherwise `set -x`
|
||||
# echoes every expanded value (paths, tokens) into the log
|
||||
fi
|
||||
set -o pipefail # capture error from pipes
|
||||
|
||||
# ENVIRONMENT VARIABLES
|
||||
@@ -37,6 +40,87 @@ logged_in_home=$(eval echo "~${logged_in_user}")
|
||||
download_dir="${logged_in_home}/Downloads/NonSteamLaunchersInstallation"
|
||||
log_file="${logged_in_home}/Downloads/NonSteamLaunchers-install.log"
|
||||
|
||||
# --- Security helpers ---------------------------------------------------------
|
||||
# Set NSL_DRY_RUN=1 to preview destructive actions (deletes, Start Fresh) without
|
||||
# performing them.
|
||||
NSL_DRY_RUN="${NSL_DRY_RUN:-0}"
|
||||
|
||||
# Download over HTTPS only, with optional SHA-256 verification. Refuses non-HTTPS
|
||||
# URLs and removes the file on checksum mismatch so a tampered/partial download is
|
||||
# never executed or installed.
|
||||
nsl_download() {
|
||||
local url="$1"
|
||||
local output="$2"
|
||||
local expected_sha256="${3:-}"
|
||||
|
||||
if [[ "$url" != https://* ]]; then
|
||||
echo "Refusing to download non-HTTPS URL: $url"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl --fail --location --silent --show-error --proto '=https' --tlsv1.2 --output "$output" "$url" || return 1
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget --https-only -O "$output" "$url" || return 1
|
||||
else
|
||||
echo "Neither curl nor wget is installed."
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -n "$expected_sha256" ]]; then
|
||||
local actual_sha256
|
||||
actual_sha256=$(sha256sum "$output" | awk '{print $1}')
|
||||
if [[ "$actual_sha256" != "$expected_sha256" ]]; then
|
||||
echo "Checksum mismatch for $output"
|
||||
echo "Expected: $expected_sha256"
|
||||
echo "Actual: $actual_sha256"
|
||||
rm -f "$output"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Delete only paths inside known NSL locations. Refuses empty/"/"/$HOME and any
|
||||
# path outside the allowlist so a bad variable expansion can't wipe unrelated data.
|
||||
# Honours NSL_DRY_RUN.
|
||||
nsl_safe_rm() {
|
||||
local path="$1"
|
||||
|
||||
if [[ -z "$path" || "$path" == "/" || "$path" == "$logged_in_home" ]]; then
|
||||
echo "Refusing to delete unsafe path: ${path:-<empty>}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
case "$path" in
|
||||
"$download_dir"|"$download_dir"/*|\
|
||||
"${logged_in_home}/.config/systemd/user/NSLGameScanner.py"|\
|
||||
"${logged_in_home}/.config/systemd/user/descriptions.json"|\
|
||||
"${logged_in_home}/.config/systemd/user/nslgamescanner.service"|\
|
||||
"${logged_in_home}/.config/systemd/user/env_vars"|\
|
||||
"${logged_in_home}/.local/share/applications/RemotePlayWhatever"|\
|
||||
"${logged_in_home}/.local/share/applications/RemotePlayWhatever.desktop"|\
|
||||
/tmp/NonSteamLaunchersDecky|/tmp/NonSteamLaunchersDecky.zip|/tmp/NonSteamLaunchersDecky-main|\
|
||||
/tmp/hytale-launcher.flatpak)
|
||||
;;
|
||||
*)
|
||||
echo "Refusing to delete path outside expected NSL locations: $path"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$NSL_DRY_RUN" == "1" ]]; then
|
||||
echo "DRY RUN: would delete $path"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -L "$path" ]]; then
|
||||
unlink "$path"
|
||||
else
|
||||
rm -rf "$path"
|
||||
fi
|
||||
}
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
#Cleaning
|
||||
@@ -253,7 +337,7 @@ get_steam_user_info() {
|
||||
{
|
||||
set +x
|
||||
steamid3=$(get_steam_user_info | tail -n1)
|
||||
set -x
|
||||
[[ "${NSL_DEBUG:-0}" == "1" ]] && set -x
|
||||
} 2>/dev/null
|
||||
|
||||
# Get Proton compatibility tool name or fall back
|
||||
@@ -468,7 +552,7 @@ fi
|
||||
# Check if the NonSteamLaunchersInstallation subfolder exists in the Downloads folder
|
||||
if [ -d "$download_dir" ]; then
|
||||
# Delete the NonSteamLaunchersInstallation subfolder
|
||||
rm -rf "$download_dir"
|
||||
nsl_safe_rm "$download_dir"
|
||||
echo "Deleted NonSteamLaunchersInstallation subfolder"
|
||||
else
|
||||
echo "NonSteamLaunchersInstallation subfolder does not exist"
|
||||
@@ -1227,7 +1311,12 @@ echo "Options: $options"
|
||||
|
||||
# Define the StartFreshFunction
|
||||
StartFreshFunction() {
|
||||
sd_path=$(get_sd_path)
|
||||
if [[ "$NSL_DRY_RUN" == "1" ]]; then
|
||||
echo "DRY RUN: Start Fresh will report planned removals without deleting files or changing services."
|
||||
sd_path="${NSL_DRY_RUN_SD_PATH:-/run/media/${USER:-deck}/DRY_RUN_SD_CARD}"
|
||||
else
|
||||
sd_path=$(get_sd_path)
|
||||
fi
|
||||
|
||||
compatdata_dir="${logged_in_home}/.local/share/Steam/steamapps/compatdata"
|
||||
other_dir="${logged_in_home}/.local/share/Steam/steamapps/shadercache"
|
||||
@@ -1238,7 +1327,39 @@ StartFreshFunction() {
|
||||
|
||||
delete_path() {
|
||||
local path="$1"
|
||||
if [ -e "$path" ]; then
|
||||
|
||||
# Never delete empty / root / the home directory itself.
|
||||
if [[ -z "$path" || "$path" == "/" || "$path" == "$logged_in_home" ]]; then
|
||||
echo "Refusing to delete unsafe path: ${path:-<empty>}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Only delete paths inside the known NSL locations this function operates on,
|
||||
# so a bad variable expansion can't wipe unrelated data.
|
||||
case "$path" in
|
||||
"$compatdata_dir"/*|"$other_dir"/*|"$sd_path"/*|\
|
||||
"${logged_in_home}/Downloads/NonSteamLaunchersInstallation"|\
|
||||
"${logged_in_home}/.config/systemd/user/Modules"|\
|
||||
"${logged_in_home}/.config/systemd/user/shortcuts"|\
|
||||
"${logged_in_home}/.config/systemd/user/descriptions.json"|\
|
||||
"${logged_in_home}/.config/systemd/user/NSLGameScanner.py"|\
|
||||
"${logged_in_home}/.config/systemd/user/env_vars"|\
|
||||
"${logged_in_home}/.config/systemd/user/nslgamescanner.service"|\
|
||||
"${logged_in_home}/.local/share/applications/RemotePlayWhatever"|\
|
||||
"${logged_in_home}/.local/share/applications/RemotePlayWhatever.desktop"|\
|
||||
"${logged_in_home}/Downloads/NonSteamLaunchers-install.log")
|
||||
;;
|
||||
*)
|
||||
echo "Refusing to delete path outside expected NSL locations: $path"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
||||
if [[ "$NSL_DRY_RUN" == "1" ]]; then
|
||||
echo "DRY RUN: would delete $path"
|
||||
return 0
|
||||
fi
|
||||
if [ -L "$path" ]; then
|
||||
target=$(readlink -f "$path")
|
||||
rm -rf "$target"
|
||||
@@ -1254,6 +1375,11 @@ StartFreshFunction() {
|
||||
clean_envars_file() {
|
||||
local envars_file="${logged_in_home}/.config/systemd/user/env_vars"
|
||||
|
||||
if [[ "$NSL_DRY_RUN" == "1" ]]; then
|
||||
echo "DRY RUN: would clean ENVARS file at $envars_file"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -f "$envars_file" ]; then
|
||||
echo "Cleaning ENVARS file..."
|
||||
|
||||
@@ -1276,6 +1402,11 @@ StartFreshFunction() {
|
||||
run_nsl_scanner() {
|
||||
local scanner_path="${logged_in_home}/.config/systemd/user/NSLGameScanner.py"
|
||||
|
||||
if [[ "$NSL_DRY_RUN" == "1" ]]; then
|
||||
echo "DRY RUN: would run NSLGameScanner.py from $scanner_path"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -f "$scanner_path" ]; then
|
||||
echo "Running NSLGameScanner.py with cleaned ENVARS..."
|
||||
|
||||
@@ -1332,6 +1463,13 @@ StartFreshFunction() {
|
||||
delete_path "${logged_in_home}/.config/systemd/user/env_vars"
|
||||
|
||||
delete_path "${logged_in_home}/.config/systemd/user/nslgamescanner.service"
|
||||
|
||||
if [[ "$NSL_DRY_RUN" == "1" ]]; then
|
||||
echo "DRY RUN: would unlink nslgamescanner.service symlink and reload/reset systemd user units"
|
||||
show_message "DRY RUN complete — nothing was deleted."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
unlink "${logged_in_home}/.config/systemd/user/default.target.wants/nslgamescanner.service" 2>/dev/null || true
|
||||
|
||||
systemctl --user daemon-reload
|
||||
@@ -1361,7 +1499,13 @@ if [[ $options == "Start Fresh" ]] || [[ $selected_launchers == "Start Fresh" ]]
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
# Command line arguments were provided, so skip displaying the zenity window and directly perform any necessary actions to start fresh by calling the StartFreshFunction
|
||||
# Command line arguments were provided, so skip the zenity window. Require an
|
||||
# explicit opt-in so an accidental/automated "Start Fresh" arg can't silently
|
||||
# wipe everything without confirmation.
|
||||
if [[ "${NSL_CONFIRM_START_FRESH:-0}" != "1" ]]; then
|
||||
echo "Refusing CLI Start Fresh without NSL_CONFIRM_START_FRESH=1"
|
||||
exit 1
|
||||
fi
|
||||
StartFreshFunction
|
||||
fi
|
||||
fi
|
||||
@@ -2056,8 +2200,8 @@ process_uninstall_options() {
|
||||
fi
|
||||
|
||||
if [[ $uninstall_options == *"Uninstall RemotePlayWhatever"* ]]; then
|
||||
rm -rf "${logged_in_home}/.local/share/applications/RemotePlayWhatever"
|
||||
rm -rf "${logged_in_home}/.local/share/applications/RemotePlayWhatever.desktop"
|
||||
nsl_safe_rm "${logged_in_home}/.local/share/applications/RemotePlayWhatever"
|
||||
nsl_safe_rm "${logged_in_home}/.local/share/applications/RemotePlayWhatever.desktop"
|
||||
|
||||
zenity --info --text="RemotePlayWhatever has been uninstalled." --width=200 --height=150 &
|
||||
sleep 3
|
||||
@@ -2193,7 +2337,7 @@ if [ $# -gt 0 ]; then
|
||||
# Check the flag after the loop
|
||||
if $uninstalled_any_launcher; then
|
||||
echo "Uninstallation completed successfully."
|
||||
rm -rf "$download_dir"
|
||||
nsl_safe_rm "$download_dir"
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
@@ -2254,7 +2398,7 @@ else
|
||||
process_uninstall_options "Uninstall $launcher"
|
||||
done
|
||||
echo "Uninstallation completed successfully."
|
||||
rm -rf "$download_dir"
|
||||
nsl_safe_rm "$download_dir"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
@@ -2384,11 +2528,11 @@ function stop_service {
|
||||
systemctl --user stop nslgamescanner.service
|
||||
|
||||
# Delete the NSLGameScanner.py
|
||||
rm -rf ${logged_in_home}/.config/systemd/user/NSLGameScanner.py
|
||||
rm -rf ${logged_in_home}/.config/systemd/user/descriptions.json
|
||||
nsl_safe_rm "${logged_in_home}/.config/systemd/user/NSLGameScanner.py"
|
||||
nsl_safe_rm "${logged_in_home}/.config/systemd/user/descriptions.json"
|
||||
|
||||
# Delete the service file
|
||||
rm -rf ${logged_in_home}/.config/systemd/user/nslgamescanner.service
|
||||
nsl_safe_rm "${logged_in_home}/.config/systemd/user/nslgamescanner.service"
|
||||
|
||||
# Remove the symlink
|
||||
unlink ${logged_in_home}/.config/systemd/user/default.target.wants/nslgamescanner.service
|
||||
@@ -2467,7 +2611,7 @@ if [[ " ${args[@]} " =~ " 🔍 " ]] || [[ $options == "🔍" ]]; then
|
||||
|
||||
# If command line arguments were provided, exit the script
|
||||
if [ ${#args[@]} -ne 0 ]; then
|
||||
rm -rf ${logged_in_home}/.config/systemd/user/env_vars
|
||||
nsl_safe_rm "${logged_in_home}/.config/systemd/user/env_vars"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -2742,7 +2886,7 @@ function install_eaapp {
|
||||
mkdir -p "$eaapp_download_dir"
|
||||
|
||||
# Download the file
|
||||
wget "$eaapp_url" -O "${eaapp_download_dir}${eaapp_file_name}"
|
||||
nsl_download "$eaapp_url" "${eaapp_download_dir}${eaapp_file_name}" || exit 1
|
||||
}
|
||||
|
||||
|
||||
@@ -3127,7 +3271,7 @@ function install_launcher {
|
||||
# Download file
|
||||
if [ ! -f "$file_name" ]; then
|
||||
echo "Downloading ${file_name}"
|
||||
curl -L "$file_url" -o "$file_name"
|
||||
nsl_download "$file_url" "$file_name" || exit 1
|
||||
fi
|
||||
|
||||
# Execute the pre-installation command, if provided
|
||||
@@ -4418,7 +4562,7 @@ echo "export chrome_startdir=$chrome_startdir" >> ${logged_in_home}/.config/syst
|
||||
# TODO: might be better to relocate temp files to `/tmp` or even use `mktemp -d` since `rm -rf` is potentially dangerous without the `-i` flag
|
||||
|
||||
# Delete NonSteamLaunchersInstallation subfolder in Downloads folder
|
||||
rm -rf "$download_dir"
|
||||
nsl_safe_rm "$download_dir"
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user