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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
193
lib/core/base.sh
193
lib/core/base.sh
@@ -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
|
||||
# ============================================================================
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user