1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-07 12:13:31 +00:00

feat: harden user file handling and gate LaunchServices rebuild (#159)

- add ensure_user_dir/ensure_user_file helpers in lib/core/base.sh, including
  sudo-aware ownership correction under the invoking user’s home
- use the helpers across clean/optimize/purge/uninstall/whitelist to create
  cache and export files safely (no naked mkdir/touch), including log files and
  dry-run exports
- ensure purge stats/count files and update message caches are pre-created with
  safe permissions
- add Darwin version helpers and skip LaunchServices/dyld rebuild on macOS 15+,
  keeping the existing corruption protection for earlier versions
- guard brew cache timestamp writes and TCC permission flags with safe file
  creation to avoid root-owned artifacts
This commit is contained in:
Luke Bullimore
2025-12-26 02:54:56 +00:00
committed by GitHub
parent f838e9517d
commit 785032635a
13 changed files with 228 additions and 18 deletions

View File

@@ -174,7 +174,7 @@ CACHE_DIR="${HOME}/.cache/mole"
CACHE_TTL=600 # 10 minutes in seconds
# Ensure cache directory exists
mkdir -p "$CACHE_DIR" 2> /dev/null || true
ensure_user_dir "$CACHE_DIR"
clear_cache_file() {
local file="$1"
@@ -302,6 +302,7 @@ check_mole_update() {
latest_version=$(curl -fsSL https://api.github.com/repos/tw93/mole/releases/latest 2> /dev/null | grep '"tag_name"' | sed -E 's/.*"v?([^"]+)".*/\1/' || echo "")
# Save to cache
if [[ -n "$latest_version" ]]; then
ensure_user_file "$cache_file"
echo "$latest_version" > "$cache_file" 2> /dev/null || true
fi
fi

View File

@@ -116,7 +116,7 @@ clean_homebrew() {
# Update cache timestamp on successful completion
if [[ "$brew_success" == "true" || "$autoremove_success" == "true" ]]; then
mkdir -p "$(dirname "$brew_cache_file")"
ensure_user_file "$brew_cache_file"
date +%s > "$brew_cache_file"
fi
}

View File

@@ -52,8 +52,7 @@ check_tcc_permissions() {
fi
# Mark permissions as granted (won't prompt again)
mkdir -p "$(dirname "$permission_flag")" 2> /dev/null || true
touch "$permission_flag" 2> /dev/null || true
ensure_user_file "$permission_flag"
}
# Clean browser Service Worker cache, protecting web editing tools (capcut, photopea, pixlr)

View File

@@ -167,6 +167,25 @@ get_free_space() {
command df -h / | awk 'NR==2 {print $4}'
}
# Get Darwin kernel major version (e.g., 24 for 24.2.0)
get_darwin_major() {
local kernel
kernel=$(uname -r 2> /dev/null || true)
local major="${kernel%%.*}"
if [[ ! "$major" =~ ^[0-9]+$ ]]; then
major=0
fi
echo "$major"
}
# Check if Darwin kernel major version meets minimum
is_darwin_ge() {
local minimum="$1"
local major
major=$(get_darwin_major)
[[ "$major" -ge "$minimum" ]]
}
# Get optimal parallel jobs for operation type (scan|io|compute|default)
get_optimal_parallel_jobs() {
local operation_type="${1:-default}"
@@ -185,6 +204,180 @@ get_optimal_parallel_jobs() {
esac
}
# ============================================================================
# User Context Utilities
# ============================================================================
is_root_user() {
[[ "$(id -u)" == "0" ]]
}
get_user_home() {
local user="$1"
local home=""
if [[ -z "$user" ]]; then
echo ""
return 0
fi
if command -v dscl > /dev/null 2>&1; then
home=$(dscl . -read "/Users/$user" NFSHomeDirectory 2> /dev/null | awk '{print $2}' | head -1 || true)
fi
if [[ -z "$home" ]]; then
home=$(eval echo "~$user" 2> /dev/null || true)
fi
if [[ "$home" == "~"* ]]; then
home=""
fi
echo "$home"
}
get_invoking_user() {
if [[ -n "${SUDO_USER:-}" && "${SUDO_USER:-}" != "root" ]]; then
echo "$SUDO_USER"
return 0
fi
echo "${USER:-}"
}
get_invoking_uid() {
if [[ -n "${SUDO_UID:-}" ]]; then
echo "$SUDO_UID"
return 0
fi
local uid
uid=$(id -u 2> /dev/null || true)
echo "$uid"
}
get_invoking_gid() {
if [[ -n "${SUDO_GID:-}" ]]; then
echo "$SUDO_GID"
return 0
fi
local gid
gid=$(id -g 2> /dev/null || true)
echo "$gid"
}
get_invoking_home() {
if [[ -n "${SUDO_USER:-}" && "${SUDO_USER:-}" != "root" ]]; then
get_user_home "$SUDO_USER"
return 0
fi
echo "${HOME:-}"
}
ensure_user_dir() {
local raw_path="$1"
if [[ -z "$raw_path" ]]; then
return 0
fi
local target_path="$raw_path"
if [[ "$target_path" == "~"* ]]; then
target_path="${target_path/#\~/$HOME}"
fi
mkdir -p "$target_path" 2> /dev/null || true
if ! is_root_user; then
return 0
fi
local sudo_user="${SUDO_USER:-}"
if [[ -z "$sudo_user" || "$sudo_user" == "root" ]]; then
return 0
fi
local user_home
user_home=$(get_user_home "$sudo_user")
if [[ -z "$user_home" ]]; then
return 0
fi
user_home="${user_home%/}"
if [[ "$target_path" != "$user_home" && "$target_path" != "$user_home/"* ]]; then
return 0
fi
local owner_uid="${SUDO_UID:-}"
local owner_gid="${SUDO_GID:-}"
if [[ -z "$owner_uid" || -z "$owner_gid" ]]; then
owner_uid=$(id -u "$sudo_user" 2> /dev/null || true)
owner_gid=$(id -g "$sudo_user" 2> /dev/null || true)
fi
if [[ -z "$owner_uid" || -z "$owner_gid" ]]; then
return 0
fi
local dir="$target_path"
while [[ -n "$dir" && "$dir" != "/" ]]; do
chown "$owner_uid:$owner_gid" "$dir" 2> /dev/null || true
if [[ "$dir" == "$user_home" ]]; then
break
fi
dir=$(dirname "$dir")
if [[ "$dir" == "." ]]; then
break
fi
done
}
ensure_user_file() {
local raw_path="$1"
if [[ -z "$raw_path" ]]; then
return 0
fi
local target_path="$raw_path"
if [[ "$target_path" == "~"* ]]; then
target_path="${target_path/#\~/$HOME}"
fi
ensure_user_dir "$(dirname "$target_path")"
touch "$target_path" 2> /dev/null || true
if ! is_root_user; then
return 0
fi
local sudo_user="${SUDO_USER:-}"
if [[ -z "$sudo_user" || "$sudo_user" == "root" ]]; then
return 0
fi
local user_home
user_home=$(get_user_home "$sudo_user")
if [[ -z "$user_home" ]]; then
return 0
fi
user_home="${user_home%/}"
if [[ "$target_path" != "$user_home" && "$target_path" != "$user_home/"* ]]; then
return 0
fi
local owner_uid="${SUDO_UID:-}"
local owner_gid="${SUDO_GID:-}"
if [[ -z "$owner_uid" || -z "$owner_gid" ]]; then
owner_uid=$(id -u "$sudo_user" 2> /dev/null || true)
owner_gid=$(id -g "$sudo_user" 2> /dev/null || true)
fi
if [[ -n "$owner_uid" && -n "$owner_gid" ]]; then
chown "$owner_uid:$owner_gid" "$target_path" 2> /dev/null || true
fi
}
# ============================================================================
# Formatting Utilities
# ============================================================================

View File

@@ -26,7 +26,10 @@ readonly DEBUG_LOG_FILE="${HOME}/.config/mole/mole_debug_session.log"
readonly LOG_MAX_SIZE_DEFAULT=1048576 # 1MB
# Ensure log directory exists
mkdir -p "$(dirname "$LOG_FILE")" 2> /dev/null || true
ensure_user_dir "$(dirname "$LOG_FILE")"
if is_root_user && [[ -n "${SUDO_USER:-}" && "${SUDO_USER:-}" != "root" ]]; then
ensure_user_file "$LOG_FILE"
fi
# ============================================================================
# Log Rotation
@@ -41,7 +44,7 @@ rotate_log_once() {
local max_size="${MOLE_MAX_LOG_SIZE:-$LOG_MAX_SIZE_DEFAULT}"
if [[ -f "$LOG_FILE" ]] && [[ $(get_file_size "$LOG_FILE") -gt "$max_size" ]]; then
mv "$LOG_FILE" "${LOG_FILE}.old" 2> /dev/null || true
touch "$LOG_FILE" 2> /dev/null || true
ensure_user_file "$LOG_FILE"
fi
}
@@ -104,6 +107,7 @@ log_system_info() {
export MOLE_SYS_INFO_LOGGED=1
# Reset debug log file for this new session
ensure_user_file "$DEBUG_LOG_FILE"
: > "$DEBUG_LOG_FILE"
# Start block in debug log file

View File

@@ -44,7 +44,7 @@ save_whitelist_patterns() {
header_text="# Mole Whitelist - Protected paths won't be deleted\n# Default protections: Playwright browsers, HuggingFace models, Maven repo, Ollama models, Surge Mac, R renv, Finder metadata\n# Add one pattern per line to keep items safe."
fi
mkdir -p "$(dirname "$config_file")"
ensure_user_file "$config_file"
echo -e "$header_text" > "$config_file"

View File

@@ -17,9 +17,16 @@ flush_dns_cache() {
# Rebuild databases and flush caches
opt_system_maintenance() {
# DISABLED: Causes System Settings corruption - Issue #136
echo -e "${GRAY}${NC} LaunchServices rebuild disabled"
# run_with_timeout 10 /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user > /dev/null 2>&1 || true
local darwin_major
darwin_major=$(get_darwin_major)
if [[ "$darwin_major" -ge 24 ]]; then
echo -e "${GRAY}${NC} LaunchServices/dyld rebuild skipped on macOS 15+ (Darwin ${darwin_major})"
else
# DISABLED: Causes System Settings corruption - Issue #136
echo -e "${GRAY}${NC} LaunchServices rebuild disabled"
# run_with_timeout 10 /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -kill -r -domain local -domain system -domain user > /dev/null 2>&1 || true
fi
echo -e "${BLUE}${ICON_ARROW}${NC} Clearing DNS cache..."
if flush_dns_cache; then