AI agents: see /llms.txt for a full index of this site, or /llms-full.txt for concatenated documentation.

Back
Defense EvasionExecutionUpdated Jan 26, 2026

Prevent Unloading of Security Services

Block launchctl from unloading EDR, MDM, and observability LaunchDaemons, preventing attackers from disabling your security stack after compromise.

Idea

Once malware gains a foothold on a system, a common next step is disabling the security tools that would detect or contain it. On macOS, those tools — EDR sensors, MDM agents, DLP clients, observability daemons — typically run as LaunchDaemons managed by launchd, and any process running as root can stop them with a single launchctl unload (or bootout) command.

Santa is a natural place to enforce a policy against this. Santa ships with active anti-tampering mechanisms that defend its own security-critical processes and resources from being disabled, removed, or modified; this rule extends that same hardening to the rest of the security stack you deploy alongside it.

Common protection targets:

  • EDR sensors (CrowdStrike Falcon, SentinelOne, Microsoft Defender for Endpoint, Sophos, Carbon Black)
  • Mac management agents (Jamf, Mosyle, Kandji)
  • Observability / audit daemons (osquery, Elastic Agent, Splunk forwarders)
  • DLP / CASB clients (Zscaler, Netskope)

The rule below blocks the launchctl subcommands that can stop or remove a service (unload, bootout, remove, disable) when invoked against a representative starter list of security tools. Adapt the bundle-ID list to whatever your fleet runs.

Solutions

ExecutionBlock Security Service Unloading
Prevent launchctl from unloading critical security services
Signing ID
CEL Expression
Custom Message

Mitre Attack

Tags

launchctllaunchdaemondefense-evasionedr

Deployment Notes

This rule blocks launchctl invocations that would stop a protected security service. Customize the bundle-ID list in the CEL expression to match the tools your fleet actually runs.

Subcommands blocked: unload, bootout, remove, disable. Read-only and lifecycle operations like list, print, start, kickstart, and load remain unaffected. (kill — which signals a running service without unloading it — is intentionally not in the list; consider a separate rule if that's a concern for your environment.)

Bundle-ID prefixes to consider, depending on what you deploy:

  • EDR: com.crowdstrike.falcon, com.sentinelone, com.microsoft.fresno, com.sophos.endpoint, com.vmware.carbonblack
  • MDM agents: com.jamf, com.mosyle, com.kandji
  • Observability / audit: io.osquery.agent, co.elastic.endpoint
  • DLP / CASB: vendor-specific (Zscaler, Netskope, etc.)

Note: This rule does not prevent direct plist removal from /Library/LaunchDaemons/. For complete protection, pair it with the launch-items monitoring rule to catch tampering with the on-disk service definitions themselves.

False Positive Guidance

Legitimate reasons to unload security services:

  • System administrators troubleshooting issues
  • Upgrading security software (may require unload/reload cycle)
  • Decommissioning hosts before reimaging

Consider:

  • Using Workshop tags to exempt IT admin accounts
  • Creating approval workflows for unload operations
  • Temporarily disabling the rule during planned maintenance windows

For software updates, vendors should use proper upgrade mechanisms that don't require manual service unloading.

Testing Instructions

Substitute one of the bundle IDs your fleet actually protects. The examples below use io.osquery.agent (osquery) as a stand-in.

  1. Try to unload a protected service: sudo launchctl unload /Library/LaunchDaemons/io.osquery.agent.plist (should be blocked)
  2. Try the modern subcommand form: sudo launchctl bootout system/io.osquery.agent (should also be blocked)
  3. Try to load a protected service: sudo launchctl load /Library/LaunchDaemons/io.osquery.agent.plist (should work — load is not in the blocked subcommand list)
  4. Try to unload an unprotected service: sudo launchctl unload /Library/LaunchDaemons/com.example.test.plist (should work)
  5. Verify the protected service is still running: sudo launchctl list | grep osquery (should show the service active)

Detection Methods

Monitor for CEL execution events showing blocked launchctl commands. Any attempt to unload security services should generate an alert.

Investigate:

  • Who attempted the unload (user account)
  • What binary called launchctl (direct terminal or script?)
  • Timing (during business hours or off-hours?)
  • Parent process (was it an automated script or manual command?)

False positives are rare - most unload attempts are either malicious or unauthorized troubleshooting.

Resources

Related Rules