1
0
mirror of https://github.com/tw93/Mole.git synced 2026-02-16 04:51:11 +00:00

security: harden BOM processing and LaunchAgents detection

- Add path traversal protection in BOM receipt parsing
- Remove invalid ~/Library/LaunchDaemons path references
- Strengthen LaunchAgents matching (min 5 chars, exclude com.apple.*)
- Add 300s timeout to brew cask uninstall to prevent hangs
Addresses security review findings from V1.21.0 audit.
This commit is contained in:
Tw93
2026-01-17 09:08:41 +08:00
parent 4fb754e927
commit 7a46aa04db
2 changed files with 146 additions and 128 deletions

View File

@@ -643,6 +643,14 @@ is_path_whitelisted() {
find_app_files() { find_app_files() {
local bundle_id="$1" local bundle_id="$1"
local app_name="$2" local app_name="$2"
# Early validation: require at least one valid identifier
# Skip scanning if both bundle_id and app_name are invalid
if [[ -z "$bundle_id" || "$bundle_id" == "unknown" ]] &&
[[ -z "$app_name" || ${#app_name} -lt 2 ]]; then
return 0 # Silent return to avoid invalid scanning
fi
local -a files_to_clean=() local -a files_to_clean=()
# Normalize app name for matching # Normalize app name for matching
@@ -665,7 +673,6 @@ find_app_files() {
"$HOME/Library/HTTPStorages/$bundle_id" "$HOME/Library/HTTPStorages/$bundle_id"
"$HOME/Library/Cookies/$bundle_id.binarycookies" "$HOME/Library/Cookies/$bundle_id.binarycookies"
"$HOME/Library/LaunchAgents/$bundle_id.plist" "$HOME/Library/LaunchAgents/$bundle_id.plist"
"$HOME/Library/LaunchDaemons/$bundle_id.plist"
"$HOME/Library/Application Scripts/$bundle_id" "$HOME/Library/Application Scripts/$bundle_id"
"$HOME/Library/Services/$app_name.workflow" "$HOME/Library/Services/$app_name.workflow"
"$HOME/Library/QuickLook/$app_name.qlgenerator" "$HOME/Library/QuickLook/$app_name.qlgenerator"
@@ -740,18 +747,16 @@ find_app_files() {
fi fi
fi fi
# Launch Agents and Daemons by name (special handling) # Launch Agents by name (special handling)
if [[ ${#app_name} -gt 3 ]]; then # Note: LaunchDaemons are system-level and handled in find_app_system_files()
if [[ -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
files_to_clean+=("$plist") local plist_name=$(basename "$plist")
done < <(command find ~/Library/LaunchAgents -maxdepth 1 \( -name "*$app_name*.plist" \) -print0 2> /dev/null) if [[ "$plist_name" =~ ^com\.apple\. ]]; then
continue
fi fi
if [[ -d ~/Library/LaunchDaemons ]]; then
while IFS= read -r -d '' plist; do
files_to_clean+=("$plist") files_to_clean+=("$plist")
done < <(command find ~/Library/LaunchDaemons -maxdepth 1 \( -name "*$app_name*.plist" \) -print0 2> /dev/null) done < <(command find ~/Library/LaunchAgents -maxdepth 1 -name "*$app_name*.plist" -print0 2>/dev/null)
fi
fi fi
# Handle specialized toolchains and development environments # Handle specialized toolchains and development environments
@@ -764,7 +769,7 @@ find_app_files() {
# 2. Android Studio (Google) # 2. Android Studio (Google)
if [[ "$app_name" =~ Android.*Studio|android.*studio ]] || [[ "$bundle_id" =~ google.*android.*studio|jetbrains.*android ]]; then if [[ "$app_name" =~ Android.*Studio|android.*studio ]] || [[ "$bundle_id" =~ google.*android.*studio|jetbrains.*android ]]; then
for d in ~/AndroidStudioProjects ~/Library/Android ~/.android ~/.gradle; do for d in ~/AndroidStudioProjects ~/Library/Android ~/.android; do
[[ -d "$d" ]] && files_to_clean+=("$d") [[ -d "$d" ]] && files_to_clean+=("$d")
done done
[[ -d ~/Library/Application\ Support/Google ]] && while IFS= read -r -d '' d; do files_to_clean+=("$d"); done < <(command find ~/Library/Application\ Support/Google -maxdepth 1 -name "AndroidStudio*" -print0 2>/dev/null) [[ -d ~/Library/Application\ Support/Google ]] && while IFS= read -r -d '' d; do files_to_clean+=("$d"); done < <(command find ~/Library/Application\ Support/Google -maxdepth 1 -name "AndroidStudio*" -print0 2>/dev/null)
@@ -904,6 +909,13 @@ find_app_receipt_files() {
# Skip if no bundle ID # Skip if no bundle ID
[[ -z "$bundle_id" || "$bundle_id" == "unknown" ]] && return 0 [[ -z "$bundle_id" || "$bundle_id" == "unknown" ]] && return 0
# Validate bundle_id format to prevent wildcard injection
# Only allow alphanumeric characters, dots, hyphens, and underscores
if [[ ! "$bundle_id" =~ ^[a-zA-Z0-9._-]+$ ]]; then
debug_log "Invalid bundle_id format: $bundle_id"
return 0
fi
local -a receipt_files=() local -a receipt_files=()
local -a bom_files=() local -a bom_files=()
@@ -935,6 +947,15 @@ find_app_receipt_files() {
clean_path="/$clean_path" clean_path="/$clean_path"
fi fi
# Path traversal protection: reject paths containing ..
if [[ "$clean_path" =~ \.\. ]]; then
debug_log "Rejected path traversal in BOM: $clean_path"
continue
fi
# Normalize path (remove duplicate slashes)
clean_path=$(echo "$clean_path" | sed 's#//*#/#g')
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
# Safety check: restrict removal to trusted paths # Safety check: restrict removal to trusted paths
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------

View File

@@ -173,25 +173,22 @@ brew_uninstall_cask() {
debug_log "Attempting brew uninstall --cask $cask_name" debug_log "Attempting brew uninstall --cask $cask_name"
# Run uninstall with timeout (suppress hints/auto-update)
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
# Many brew casks need sudo to uninstall
if ! sudo -n true 2>/dev/null; then if ! sudo -n true 2>/dev/null; then
# If we don't have sudo, try to get it (visibly)
sudo -v sudo -v
fi fi
local uninstall_ok=false local uninstall_ok=false
local brew_exit=0
# Run directly without output capture to allow user interaction/visibility # Run with timeout to prevent hangs from problematic cask scripts
# This avoids silence/hangs when brew asks for passwords or confirmation 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 \
brew uninstall --cask "$cask_name"; then brew uninstall --cask "$cask_name" 2>&1; then
uninstall_ok=true uninstall_ok=true
else else
debug_log "brew uninstall failed with exit code $?" brew_exit=$?
debug_log "brew uninstall timeout or failed with exit code: $brew_exit"
fi fi
# Verify removal # Verify removal