Skip to content

Repository files navigation

husk

Finds and removes what a macOS app leaves behind.

UninstallKit is the engine. husk is the CLI. The SwiftUI app links the same library, so the GUI is a shell over this code rather than a second implementation.

Build

swift build -c release
.build/release/husk list
.build/release/husk scan "Some App"
.build/release/husk remove "Some App" --dry-run
.build/release/husk orphans --min-size-mb 50

How matching works

Confidence is the core concept. Nothing is deleted on a guess.

Level Signal Example
exact Bundle identifier match, executable inside the bundle, or a file listed in an installer receipt ~/Library/Preferences/com.acme.app.plist
likely Team identifier, vendor prefix, or the folder name equals the app name exactly ~/Library/Group Containers/AB12CD34.com.acme
possible Fuzzy name resemblance ~/Library/Caches/Acme Helper

likely is the default floor for removal. possible requires --include-possible and should never be pre-selected in the GUI.

Names shorter than five characters and a denylist of generic words (mail, notes, code, sync) are excluded from fuzzy matching entirely, which is where most uninstallers cause damage.

What it checks

Per-app

  • The bundle itself
  • Application Support, Caches, Preferences (including ByHost), Containers, Group Containers, Application Scripts, Saved Application State, Logs, Cookies, HTTPStorages, WebKit, Autosave Information
  • Plug-in locations in both domains: Internet Plug-Ins, PreferencePanes, QuickLook, Services, Spotlight, Screen Savers, Audio units, Mail bundles
  • launchd agents and daemons, matched by label and by program path, plus /Library/PrivilegedHelperTools
  • Installer receipts via pkgutil --pkg-info and pkgutil --files, which give an authoritative file manifest for anything installed from a .pkg

System-wide

  • Orphan scan: bundle-identifier-shaped Library entries with no installed app claiming them

Safety design

  • Nothing is deleted. User-owned items go to the Trash via FileManager.trashItem, so Finder's Put Back still works. Root-owned items are moved to ~/.husk-quarantine/<timestamp>/, since trashItem cannot move files the user does not own.
  • Safety.isProtected is checked immediately before every removal, not just at scan time. It rejects Library roots, anything under /System, /usr, /bin, com.apple.* names, Keychains, CloudDocs, TCC, and any path fewer than four components deep.
  • launchd jobs are booted out before their plists are deleted, otherwise the job stays loaded until reboot and can respawn its helper.
  • The app is asked to quit, then force-terminated, before anything is touched. A running app rewrites its preferences on exit.
  • Admin operations are batched into a single authorization prompt rather than one per file.
  • pkgutil --forget is printed as a command rather than executed, because it is not reversible.

Permissions and distribution

This cannot be sandboxed. Reading other apps' containers is precisely what the sandbox exists to prevent, so the App Store is not an option. Ship it Developer ID signed and notarized, distributed yourself.

The GUI will need to walk the user through granting Full Disk Access in System Settings on first launch. Without it, several Library subpaths silently return empty directory listings rather than an error, which looks like a clean system rather than a permissions failure. Detect this explicitly and say so.

Known gaps

  • Privileged removal currently shells out to osascript ... with administrator privileges. It works and prompts once, but the right answer is an SMAppService daemon with an XPC interface. Replace before shipping.
  • System extensions and network extensions are not handled yet. systemextensionsctl list is the starting point.
  • Login items registered through SMAppService are not enumerated.
  • Not compiled or run against a real system yet. Expect a first-pass compile round.

About

A macOS app uninstaller that finds leftover files with confidence-scored matching (exact/likely/possible) and removes them safely to Trash — CLI and SwiftUI GUI, same engine.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages