mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 11:31:46 +00:00
refactor: enhance uninstall safety and fix dock removal
- Add symlink/bundle_id/BOM validation to prevent injection attacks - Fix Dock removal for /Applications symlink (use pwd not pwd -P) - Fix brew uninstall test hanging (skip sudo in non-interactive mode) - Remove unused SENSITIVE_DATA_REGEX
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -48,6 +48,7 @@ tests/tmp-*
|
|||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
GEMINI.md
|
GEMINI.md
|
||||||
ANTIGRAVITY.md
|
ANTIGRAVITY.md
|
||||||
|
WARP.md
|
||||||
.cursorrules
|
.cursorrules
|
||||||
|
|
||||||
# Go build artifacts (development)
|
# Go build artifacts (development)
|
||||||
|
|||||||
@@ -749,6 +749,8 @@ find_app_files() {
|
|||||||
|
|
||||||
# Launch Agents by name (special handling)
|
# Launch Agents by name (special handling)
|
||||||
# Note: LaunchDaemons are system-level and handled in find_app_system_files()
|
# Note: LaunchDaemons are system-level and handled in find_app_system_files()
|
||||||
|
# Minimum 5-char threshold prevents false positives (e.g., "Time" matching system agents)
|
||||||
|
# Short-name apps (e.g., Zoom, Arc) are still cleaned via bundle_id matching above
|
||||||
if [[ ${#app_name} -ge 5 ]] && [[ -d ~/Library/LaunchAgents ]]; then
|
if [[ ${#app_name} -ge 5 ]] && [[ -d ~/Library/LaunchAgents ]]; then
|
||||||
while IFS= read -r -d '' plist; do
|
while IFS= read -r -d '' plist; do
|
||||||
local plist_name=$(basename "$plist")
|
local plist_name=$(basename "$plist")
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ remove_apps_from_dock() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -e "$app_path" ]]; then
|
if [[ -e "$app_path" ]]; then
|
||||||
if full_path=$(cd "$(dirname "$app_path")" 2>/dev/null && pwd -P); then
|
if full_path=$(cd "$(dirname "$app_path")" 2>/dev/null && pwd); then
|
||||||
full_path="$full_path/$(basename "$app_path")"
|
full_path="$full_path/$(basename "$app_path")"
|
||||||
else
|
else
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -47,11 +47,19 @@ validate_path_for_deletion() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# If symlink points to absolute path, validate target
|
# Resolve relative symlinks to absolute paths for validation
|
||||||
if [[ "$link_target" == /* ]]; then
|
local resolved_target="$link_target"
|
||||||
case "$link_target" in
|
if [[ "$link_target" != /* ]]; then
|
||||||
|
local link_dir
|
||||||
|
link_dir=$(dirname "$path")
|
||||||
|
resolved_target=$(cd "$link_dir" 2>/dev/null && cd "$(dirname "$link_target")" 2>/dev/null && pwd)/$(basename "$link_target") || resolved_target=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate resolved target against protected paths
|
||||||
|
if [[ -n "$resolved_target" ]]; then
|
||||||
|
case "$resolved_target" in
|
||||||
/System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/etc/*)
|
/System/* | /usr/bin/* | /usr/lib/* | /bin/* | /sbin/* | /private/etc/*)
|
||||||
log_error "Symlink points to protected system path: $path -> $link_target"
|
log_error "Symlink points to protected system path: $path -> $resolved_target"
|
||||||
return 1
|
return 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -11,25 +11,6 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|||||||
|
|
||||||
# Batch uninstall with a single confirmation.
|
# Batch uninstall with a single confirmation.
|
||||||
|
|
||||||
# User data detection patterns (prompt user to backup if found).
|
|
||||||
readonly SENSITIVE_DATA_PATTERNS=(
|
|
||||||
"\.warp" # Warp terminal configs/themes
|
|
||||||
"/\.config/" # Standard Unix config directory
|
|
||||||
"/themes/" # Theme customizations
|
|
||||||
"/settings/" # Settings directories
|
|
||||||
"/Application Support/[^/]+/User Data" # Chrome/Electron user data
|
|
||||||
"/Preferences/[^/]+\.plist" # User preference files
|
|
||||||
"/Documents/" # User documents
|
|
||||||
"/\.ssh/" # SSH keys and configs (critical)
|
|
||||||
"/\.gnupg/" # GPG keys (critical)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Join patterns into a single regex for grep.
|
|
||||||
SENSITIVE_DATA_REGEX=$(
|
|
||||||
IFS='|'
|
|
||||||
echo "${SENSITIVE_DATA_PATTERNS[*]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# High-performance sensitive data detection (pure Bash, no subprocess)
|
# High-performance sensitive data detection (pure Bash, no subprocess)
|
||||||
# Faster than grep for batch operations, especially when processing many apps
|
# Faster than grep for batch operations, especially when processing many apps
|
||||||
has_sensitive_data() {
|
has_sensitive_data() {
|
||||||
|
|||||||
@@ -174,17 +174,18 @@ brew_uninstall_cask() {
|
|||||||
debug_log "Attempting brew uninstall --cask $cask_name"
|
debug_log "Attempting brew uninstall --cask $cask_name"
|
||||||
|
|
||||||
# Ensure we have sudo access if needed, to prevent brew from hanging on password prompt
|
# Ensure we have sudo access if needed, to prevent brew from hanging on password prompt
|
||||||
if ! sudo -n true 2>/dev/null; then
|
if [[ "${NONINTERACTIVE:-}" != "1" && -t 0 && -t 1 ]]; then
|
||||||
sudo -v
|
if ! sudo -n true 2>/dev/null; then
|
||||||
|
sudo -v
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local uninstall_ok=false
|
local uninstall_ok=false
|
||||||
local brew_exit=0
|
local brew_exit=0
|
||||||
|
|
||||||
# Run with timeout to prevent hangs from problematic cask scripts
|
# Run with timeout to prevent hangs from problematic cask scripts
|
||||||
if run_with_timeout 300 \
|
if HOMEBREW_NO_ENV_HINTS=1 HOMEBREW_NO_AUTO_UPDATE=1 NONINTERACTIVE=1 \
|
||||||
env HOMEBREW_NO_ENV_HINTS=1 HOMEBREW_NO_AUTO_UPDATE=1 NONINTERACTIVE=1 \
|
run_with_timeout 300 brew uninstall --cask "$cask_name" 2>&1; then
|
||||||
brew uninstall --cask "$cask_name" 2>&1; then
|
|
||||||
uninstall_ok=true
|
uninstall_ok=true
|
||||||
else
|
else
|
||||||
brew_exit=$?
|
brew_exit=$?
|
||||||
|
|||||||
@@ -39,8 +39,6 @@ create_app_artifacts() {
|
|||||||
mkdir -p "$HOME/Library/Saved Application State/com.example.TestApp.savedState"
|
mkdir -p "$HOME/Library/Saved Application State/com.example.TestApp.savedState"
|
||||||
mkdir -p "$HOME/Library/LaunchAgents"
|
mkdir -p "$HOME/Library/LaunchAgents"
|
||||||
touch "$HOME/Library/LaunchAgents/com.example.TestApp.plist"
|
touch "$HOME/Library/LaunchAgents/com.example.TestApp.plist"
|
||||||
mkdir -p "$HOME/Library/LaunchDaemons"
|
|
||||||
touch "$HOME/Library/LaunchDaemons/com.example.TestApp.plist"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "find_app_files discovers user-level leftovers" {
|
@test "find_app_files discovers user-level leftovers" {
|
||||||
@@ -60,7 +58,6 @@ EOF
|
|||||||
[[ "$result" == *"Saved Application State/com.example.TestApp.savedState"* ]]
|
[[ "$result" == *"Saved Application State/com.example.TestApp.savedState"* ]]
|
||||||
[[ "$result" == *"Containers/com.example.TestApp"* ]]
|
[[ "$result" == *"Containers/com.example.TestApp"* ]]
|
||||||
[[ "$result" == *"LaunchAgents/com.example.TestApp.plist"* ]]
|
[[ "$result" == *"LaunchAgents/com.example.TestApp.plist"* ]]
|
||||||
[[ "$result" == *"LaunchDaemons/com.example.TestApp.plist"* ]]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@test "calculate_total_size returns aggregate kilobytes" {
|
@test "calculate_total_size returns aggregate kilobytes" {
|
||||||
@@ -121,7 +118,6 @@ batch_uninstall_applications
|
|||||||
[[ ! -d "$HOME/Library/Caches/TestApp" ]] || exit 1
|
[[ ! -d "$HOME/Library/Caches/TestApp" ]] || exit 1
|
||||||
[[ ! -f "$HOME/Library/Preferences/com.example.TestApp.plist" ]] || exit 1
|
[[ ! -f "$HOME/Library/Preferences/com.example.TestApp.plist" ]] || exit 1
|
||||||
[[ ! -f "$HOME/Library/LaunchAgents/com.example.TestApp.plist" ]] || exit 1
|
[[ ! -f "$HOME/Library/LaunchAgents/com.example.TestApp.plist" ]] || exit 1
|
||||||
[[ ! -f "$HOME/Library/LaunchDaemons/com.example.TestApp.plist" ]] || exit 1
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
|
|||||||
Reference in New Issue
Block a user