Skip to content

Instantly share code, notes, and snippets.

@mevanlc
Created September 11, 2025 13:06
Show Gist options
  • Select an option

  • Save mevanlc/6ba1f76c69e9e73d80343ba426c67680 to your computer and use it in GitHub Desktop.

Select an option

Save mevanlc/6ba1f76c69e9e73d80343ba426c67680 to your computer and use it in GitHub Desktop.
uninstall bitdefender
#!/bin/bash
# uninstall_bitdefender.sh
# Purpose: Remove Bitdefender Antivirus for Mac files as outlined in the vendor PDF.
# Works in Recovery mode (preferred when Finder removal fails) and can also run in normal macOS.
# Usage:
# 1) Recovery mode (recommended if Finder method failed):
# chmod +x uninstall_bitdefender.sh
# ./uninstall_bitdefender.sh --volume "Macintosh HD"
# 2) Normal mode (may fail due to protections, but safe to try):
# sudo ./uninstall_bitdefender.sh
# Flags:
# --volume NAME Specify the startup disk under /Volumes (ex: "Macintosh HD").
# --dry-run Show what would be removed without deleting anything.
# --force Do not prompt for confirmation.
#
# Notes from the PDF:
# - Recovery Terminal method removes SelfProtect.kext and the /Library/Bitdefender folder.
# - Finder method also removes Application Support items and additional kexts when present.
#
set -o errexit
set -o nounset
set -o pipefail
IFS=$'\n\t'
info() { printf "[INFO] %s\n" "$*"; }
warn() { printf "[WARN] %s\n" "$*" >&2; }
error() { printf "[ERROR] %s\n" "$*" >&2; exit 1; }
# Default options
DRY_RUN=0
FORCE=0
VOLUME_NAME=""
# Parse args
while (( "$#" )); do
case "$1" in
--dry-run) DRY_RUN=1; shift ;;
--force) FORCE=1; shift ;;
--volume) shift; VOLUME_NAME="${1:-}"; [[ -z "$VOLUME_NAME" ]] && error "--volume requires a name"; shift ;;
*) error "Unknown argument: $1" ;;
esac
done
# Root check (not strictly required in Recovery, but good practice)
if [[ "$(id -u)" -ne 0 ]]; then
warn "Not running as root. Re-running with sudo..."
exec sudo -- "$(command -v bash)" "$0" ${VOLUME_NAME:+--volume "$VOLUME_NAME"} $([[ $DRY_RUN -eq 1 ]] && printf -- "--dry-run ") $([[ $FORCE -eq 1 ]] && printf -- "--force ")
fi
# Determine base path of the target system volume.
BASE="/"
detect_base() {
# If we can see BD artifacts on /, use root.
if [[ -e "/Library/Extensions/SelfProtect.kext" || -e "/Library/Bitdefender" || -e "/Library/Application Support/Bitdefender" ]]; then
BASE="/"
return
fi
# If a specific volume was provided, validate it exists.
if [[ -n "$VOLUME_NAME" ]]; then
local vol_path="/Volumes/$VOLUME_NAME"
[[ -d "$vol_path" ]] || error "Volume not found: $vol_path"
BASE="$vol_path"
return
fi
# Try common default.
if [[ -d "/Volumes/Macintosh HD" ]]; then
BASE="/Volumes/Macintosh HD"
return
fi
# Otherwise, try to auto-pick a plausible system volume.
local candidates=()
for d in /Volumes/*; do
[[ -d "$d" ]] || continue
if [[ -e "$d/System/Library/CoreServices/SystemVersion.plist" && -d "$d/Library" ]]; then
candidates+=("$d")
fi
done
if [[ "${#candidates[@]}" -eq 1 ]]; then
BASE="${candidates[0]}"
else
warn "Could not unambiguously detect the system volume."
warn "Found candidates:"
for c in "${candidates[@]}"; do printf " - %s\n" "$c"; done
error "Re-run with: --volume \"Your Startup Disk Name\""
fi
}
detect_base
info "Using system volume base: $BASE"
# Safety: refuse to operate if BASE looks wrong.
[[ "$BASE" == "/" || "$BASE" == /Volumes/* ]] || error "Invalid BASE path: $BASE"
# Construct list of targets, from most critical (per PDF method 2) to optional (per method 1).
declare -a TARGETS=(
# Method 2 core targets
"$BASE/Library/Extensions/SelfProtect.kext"
"$BASE/Library/Bitdefender"
# Method 1 additional targets (best effort)
"$BASE/Library/Extensions/FileProtect.kext"
"$BASE/Library/Extensions/TMProtection.kext"
"$BASE/Library/Application Support/Antivirus for Mac"
"$BASE/Library/Application Support/Bitdefender"
"$BASE/Applications/Antivirus for Mac.app"
"$BASE/Applications/Bitdefender.app"
"$BASE/Applications/Bitdefender"
)
# Safety checks for rm -rf
safe_rm() {
local path="$1"
# Must not be empty
[[ -n "$path" ]] || error "Refusing to delete an empty path"
# Only allow under BASE/Library or BASE/Applications
case "$path" in
"$BASE/Library/"|"$BASE/Applications/") error "Refusing to delete top-level Library/Applications";;
"$BASE/Library/"*|"$BASE/Applications/"*) ;;
*) error "Refusing to delete path outside $BASE/Library or $BASE/Applications: $path" ;;
esac
if [[ $DRY_RUN -eq 1 ]]; then
printf "[DRY-RUN] rm -rf -- %q\n" "$path"
return 0
fi
if [[ -e "$path" ]]; then
printf "[REMOVE] %s\n" "$path"
rm -rf -- "$path"
else
printf "[SKIP] Not found: %s\n" "$path"
fi
}
# Show what we plan to do
info "Planned removals:"
for t in "${TARGETS[@]}"; do
if [[ -e "$t" ]]; then
printf " - %s (exists)\n" "$t"
else
printf " - %s (not found)\n" "$t"
fi
done
# Confirmation prompt unless --force or --dry-run
if [[ $FORCE -ne 1 && $DRY_RUN -ne 1 ]]; then
printf "\nAbout to remove the items above.\n"
printf "Type YES to proceed: "
read -r REPLY
if [[ "$REPLY" != "YES" ]]; then
error "Aborted by user."
fi
fi
# Perform removals
for t in "${TARGETS[@]}"; do
safe_rm "$t"
done
# Verification listings similar to the PDF steps
printf "\n[VERIFY] Listing %s\n" "$BASE/Library/Extensions"
ls -la "$BASE/Library/Extensions" 2>/dev/null || true
printf "\n[VERIFY] Listing %s\n" "$BASE/Library"
ls -la "$BASE/Library" 2>/dev/null || true
info "Completed. If you ran this from Recovery, restart into normal macOS."
info "If some items persisted, try running again from Recovery mode with: --volume \"YourDiskName\""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment