← Field Notes
Endpoint security · Field note

Build the uninstaller first

R Rob Flanagan · · 3 min read

A security agent that will not uninstall cleanly can turn one swap into a fleet-wide cleanup problem.

Replacing a macOS security agent means removing more than its app bundle: system extensions, PPPC profiles, and launch daemons can remain after the app is gone.

Eventually, something forces a swap. A contract ends. The product stops fitting the environment. Or a new agent conflicts with the one already installed. On macOS, taking the old one off is the hard half of the job.

I've seen teams get this backwards. They validate the install, watch the agent check in green across the fleet, and call it done. Removal is a one-line script for later.

Then later shows up mid-incident. The agent I deployed turned out to be four moving parts: a system extension, a PPPC profile for TCC grants, launch daemons, and an app bundle. Those pieces had to come apart in the right order.

The teardown has four parts

Delete the app from a script and the extension can remain active. Remove an allow profile and Apple's payload schema says an active extension deactivates; that still is not a complete uninstall. A leftover extension can block its own reinstall.

Apple's RemovableSystemExtensions key lets an app request deactivation of its own extension without local administrator authorization. Apple's documentation covers that deactivation step. It does not cover cleaning up the rest of the agent.

The PPPC payload is its own removal concern. The macOS 27 PPPC guide explains why a legacy profile still carries Full Disk Access and Screen Recording even as some privacy defaults move elsewhere. A system-extension agent needs its teardown tested against the payloads it actually received, not just its app bundle.

Before the first production Mac got the new agent, I built and proved the teardown.

  • Validated a clean uninstall on a real device before any production machine was touched: a system-extension deactivation request, a restart-state check, dependency-ordered component removal, and a check that no extension stayed registered.
  • Made both install and removal run without user interaction, so rollout day did not turn into a fleet's worth of approval prompts and help-desk tickets.
  • Staged it through Jamf Pro as test scope, then soak, then broad, with the tested rollback behind every ring.

Prove the rollback before rollout

Proving removal before trusting the deploy is the belt-and-suspenders habit applied to a rollout. Confirm the retreat path works before you need it, not while hundreds of Macs are waiting on you.

The honest cost: building the uninstaller first is slower up front. You spend real hours on a path you hope to never run. That time is insurance. You pay it once now, calmly, instead of during an incident, with an agent you cannot remove and a replacement you cannot cleanly put in its place.

The rollout itself was boring. Green check-ins, no ticket flood, and a rollback I had already run for real. Boring was the goal. Boring means I handled the failure modes before they went live.

The same discipline matters when an endpoint tool becomes the incident itself. CrashStealer's user-session chain is a different problem, but the response still depends on knowing which agent components and access grants are present before a cleanup starts.

Anyone can push software to a large fleet. Taking it cleanly back off is what separates a deployment from a gamble.

So when you roll out an endpoint agent: do you prove the uninstall first, or find out you needed it the hard way, one stuck system extension at a time?

Sources

Sources preserved from the verified publication package. Recheck vendor and community guidance before relying on it in a new rollout:

← Back to Field Notes