From bf7137c5a4e6d3794ac90012c812a18fd5388310 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:40:51 +0000 Subject: [PATCH 1/6] Initial plan From 59bbab2afccb1f4dd6513e406becf7a3143d65f5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:43:23 +0000 Subject: [PATCH 2/6] fix: address security and robustness issues in update functionality - Use mktemp for secure temporary file creation - Add robust version extraction with format validation - Save and restore IFS variable properly - Validate backup creation success before proceeding - Add comprehensive error handling for mv and chmod operations - Implement automatic backup restoration on failure - Download from tagged version instead of main branch Co-authored-by: prettyleaf <81024464+prettyleaf@users.noreply.github.com> --- ruleset-fetcher.sh | 88 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 9 deletions(-) diff --git a/ruleset-fetcher.sh b/ruleset-fetcher.sh index 004bffa..3bc8bf7 100644 --- a/ruleset-fetcher.sh +++ b/ruleset-fetcher.sh @@ -666,7 +666,29 @@ show_status() { get_remote_version() { local remote_script remote_script=$(curl -fsSL --connect-timeout 10 --max-time 30 "${GITHUB_RAW_URL}" 2>/dev/null) || return 1 - local version=$(echo "$remote_script" | grep -m1 '^VERSION=' | cut -d'"' -f2) + + # Extract VERSION= line robustly, allowing spaces and single/double quotes. + # Examples handled: + # VERSION="1.2.3" + # VERSION = '1.2.3' + # VERSION=1.2.3 + local version + version=$(printf '%s\n' "$remote_script" \ + | sed -nE "s/^[[:space:]]*VERSION[[:space:]]*=[[:space:]]*['\"]?([^'\"[:space:]]+)['\"]?.*/\1/p" \ + | head -n1) + + # Validate that a version was found + if [[ -z "$version" ]]; then + print_error "Failed to parse remote version information from ${GITHUB_RAW_URL}" + return 1 + fi + + # Validate version format (optionally leading 'v', then numeric dot-separated parts) + if ! [[ "$version" =~ ^v?[0-9]+(\.[0-9]+)*$ ]]; then + print_error "Invalid version format in remote script: '$version'" + return 1 + fi + echo "${version#v}" } @@ -678,9 +700,15 @@ compare_versions() { return 0 fi - local IFS=. - local i v1_parts=($v1) v2_parts=($v2) + local IFS_SAVE="$IFS" + IFS='.' + local v1_parts=() + local v2_parts=() + read -ra v1_parts <<< "$v1" + read -ra v2_parts <<< "$v2" + IFS="$IFS_SAVE" + local i for ((i=0; i<${#v1_parts[@]} || i<${#v2_parts[@]}; i++)); do local p1=${v1_parts[i]:-0} local p2=${v2_parts[i]:-0} @@ -765,9 +793,16 @@ self_update() { print_info "Downloading update..." - local temp_file="/tmp/ruleset-fetcher-update.sh" + local temp_file + temp_file="$(mktemp -t ruleset-fetcher-update.XXXXXX)" || { + print_error "Failed to create temporary file for update" + return 1 + } + + local download_ref="${remote_version}" + local download_url="https://raw.githubusercontent.com/${GITHUB_REPO}/${download_ref}/ruleset-fetcher.sh" - if ! curl -fsSL --connect-timeout 30 --max-time 120 -o "${temp_file}" "${GITHUB_RAW_URL}" 2>/dev/null; then + if ! curl -fsSL --connect-timeout 30 --max-time 120 -o "${temp_file}" "${download_url}" 2>/dev/null; then print_error "Failed to download update" rm -f "${temp_file}" return 1 @@ -779,16 +814,51 @@ self_update() { return 1 fi + local had_backup=false if [[ -f "${SCRIPT_PATH}" ]]; then - cp "${SCRIPT_PATH}" "${SCRIPT_PATH}.backup" + if [[ ! -r "${SCRIPT_PATH}" ]]; then + print_error "Current script is not readable; aborting update to avoid data loss." + rm -f "${temp_file}" + return 1 + fi + if ! cp "${SCRIPT_PATH}" "${SCRIPT_PATH}.backup"; then + print_error "Failed to create backup; aborting update to avoid data loss." + rm -f "${temp_file}" + return 1 + fi + had_backup=true fi - mv "${temp_file}" "${SCRIPT_PATH}" - chmod +x "${SCRIPT_PATH}" + if ! mv "${temp_file}" "${SCRIPT_PATH}"; then + print_error "Failed to install update (move operation failed)" + if [[ "${had_backup}" == true ]]; then + if mv "${SCRIPT_PATH}.backup" "${SCRIPT_PATH}"; then + print_info "Restored previous version from backup." + else + print_error "Failed to restore previous version from backup at ${SCRIPT_PATH}.backup" + fi + fi + rm -f "${temp_file}" + return 1 + fi + + if ! chmod +x "${SCRIPT_PATH}"; then + print_error "Failed to make updated script executable (chmod failed)" + if [[ "${had_backup}" == true ]]; then + if mv "${SCRIPT_PATH}.backup" "${SCRIPT_PATH}"; then + print_info "Restored previous version from backup." + else + print_error "Failed to restore previous version from backup at ${SCRIPT_PATH}.backup" + fi + fi + return 1 + fi print_success "Updated successfully to v${remote_version}!" echo "" - print_info "Backup saved to: ${SCRIPT_PATH}.backup" + if [[ -f "${SCRIPT_PATH}.backup" ]]; then + print_info "Backup saved to: ${SCRIPT_PATH}.backup" + fi log_message "INFO" "Updated from v${VERSION} to v${remote_version}" } From 77a6e6b2a19063c171946f60e1ca09bff9397680 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 09:44:40 +0000 Subject: [PATCH 3/6] fix: add 'v' prefix to download ref for proper tag resolution Co-authored-by: prettyleaf <81024464+prettyleaf@users.noreply.github.com> --- ruleset-fetcher.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ruleset-fetcher.sh b/ruleset-fetcher.sh index 3bc8bf7..81628b0 100644 --- a/ruleset-fetcher.sh +++ b/ruleset-fetcher.sh @@ -674,7 +674,7 @@ get_remote_version() { # VERSION=1.2.3 local version version=$(printf '%s\n' "$remote_script" \ - | sed -nE "s/^[[:space:]]*VERSION[[:space:]]*=[[:space:]]*['\"]?([^'\"[:space:]]+)['\"]?.*/\1/p" \ + | sed -nE "s/^[[:space:]]*VERSION[[:space:]]*=[[:space:]]*['\"]?([^[:space:]'\"]+)['\"]?.*/\1/p" \ | head -n1) # Validate that a version was found @@ -799,7 +799,7 @@ self_update() { return 1 } - local download_ref="${remote_version}" + local download_ref="v${remote_version}" local download_url="https://raw.githubusercontent.com/${GITHUB_REPO}/${download_ref}/ruleset-fetcher.sh" if ! curl -fsSL --connect-timeout 30 --max-time 120 -o "${temp_file}" "${download_url}" 2>/dev/null; then From dd3581fa5543e8164e4ebea734e144edc8e187fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:06:26 +0000 Subject: [PATCH 4/6] feat: add automatic cleanup of backup files older than 1 day - Check for existing backup file at start of self_update - Calculate backup file age using stat command (supports both Linux and BSD) - Delete backup if older than 86400 seconds (1 day) - Log cleanup action for audit trail Co-authored-by: prettyleaf <81024464+prettyleaf@users.noreply.github.com> --- ruleset-fetcher.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ruleset-fetcher.sh b/ruleset-fetcher.sh index 81628b0..4860803 100644 --- a/ruleset-fetcher.sh +++ b/ruleset-fetcher.sh @@ -758,6 +758,18 @@ self_update() { echo "" print_info "Checking for updates..." + # Clean up old backup files (older than 1 day) + if [[ -f "${SCRIPT_PATH}.backup" ]]; then + local backup_age_seconds + backup_age_seconds=$(( $(date +%s) - $(stat -c %Y "${SCRIPT_PATH}.backup" 2>/dev/null || stat -f %m "${SCRIPT_PATH}.backup" 2>/dev/null) )) + local one_day_seconds=86400 + + if [[ $backup_age_seconds -gt $one_day_seconds ]]; then + rm -f "${SCRIPT_PATH}.backup" + log_message "INFO" "Removed old backup file (${backup_age_seconds}s old)" + fi + fi + local remote_version remote_version=$(get_remote_version) From 796ac4c0cee0c0fdf45f6dc559a83414426db467 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:07:32 +0000 Subject: [PATCH 5/6] refactor: improve backup cleanup code readability - Extract timestamp retrieval into separate variable - Use explicit calculation for one_day_seconds (24 * 60 * 60) Co-authored-by: prettyleaf <81024464+prettyleaf@users.noreply.github.com> --- ruleset-fetcher.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ruleset-fetcher.sh b/ruleset-fetcher.sh index 4860803..e7f8446 100644 --- a/ruleset-fetcher.sh +++ b/ruleset-fetcher.sh @@ -760,9 +760,10 @@ self_update() { # Clean up old backup files (older than 1 day) if [[ -f "${SCRIPT_PATH}.backup" ]]; then - local backup_age_seconds - backup_age_seconds=$(( $(date +%s) - $(stat -c %Y "${SCRIPT_PATH}.backup" 2>/dev/null || stat -f %m "${SCRIPT_PATH}.backup" 2>/dev/null) )) - local one_day_seconds=86400 + local backup_timestamp + backup_timestamp=$(stat -c %Y "${SCRIPT_PATH}.backup" 2>/dev/null || stat -f %m "${SCRIPT_PATH}.backup" 2>/dev/null) + local backup_age_seconds=$(( $(date +%s) - backup_timestamp )) + local one_day_seconds=$((24 * 60 * 60)) # 86400 seconds if [[ $backup_age_seconds -gt $one_day_seconds ]]; then rm -f "${SCRIPT_PATH}.backup" From 89c9aa2bd4c49df802e73c0e746eff53e2990bec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 11:08:53 +0000 Subject: [PATCH 6/6] fix: add validation for backup timestamp before arithmetic operations - Validate backup_timestamp is non-empty and numeric - Prevents arithmetic errors if stat command fails - Silently skips cleanup if timestamp cannot be determined Co-authored-by: prettyleaf <81024464+prettyleaf@users.noreply.github.com> --- ruleset-fetcher.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ruleset-fetcher.sh b/ruleset-fetcher.sh index e7f8446..dec21c4 100644 --- a/ruleset-fetcher.sh +++ b/ruleset-fetcher.sh @@ -762,12 +762,15 @@ self_update() { if [[ -f "${SCRIPT_PATH}.backup" ]]; then local backup_timestamp backup_timestamp=$(stat -c %Y "${SCRIPT_PATH}.backup" 2>/dev/null || stat -f %m "${SCRIPT_PATH}.backup" 2>/dev/null) - local backup_age_seconds=$(( $(date +%s) - backup_timestamp )) - local one_day_seconds=$((24 * 60 * 60)) # 86400 seconds - if [[ $backup_age_seconds -gt $one_day_seconds ]]; then - rm -f "${SCRIPT_PATH}.backup" - log_message "INFO" "Removed old backup file (${backup_age_seconds}s old)" + if [[ -n "$backup_timestamp" && "$backup_timestamp" =~ ^[0-9]+$ ]]; then + local backup_age_seconds=$(( $(date +%s) - backup_timestamp )) + local one_day_seconds=$((24 * 60 * 60)) # 86400 seconds + + if [[ $backup_age_seconds -gt $one_day_seconds ]]; then + rm -f "${SCRIPT_PATH}.backup" + log_message "INFO" "Removed old backup file (${backup_age_seconds}s old)" + fi fi fi