PRACTITIONER GUIDE | ENDPOINT SECURITY
Practitioner GuideUpdated 12 min read

Malicious LaunchAgents on macOS: Detection, Investigation, and Fleet Removal for Security Teams

com.user.gh-token-monitor
the LaunchAgent installed by the TanStack supply chain payload to maintain persistence and continue credential exfiltration after reboot
4
LaunchAgent directories on macOS that attackers use for persistence: user-level ~/Library/LaunchAgents/, system-level /Library/LaunchAgents/, /Library/LaunchDaemons/, and /System/Library/LaunchDaemons/
0
user interaction required for a LaunchAgent installed in ~/Library/LaunchAgents/ to execute, it runs at login automatically

SponsoredRetool

Retool's new app builder is where AI-generated code ships safely

Building apps with AI is easy. Getting them to production safely is another story.

Start building for free today

macOS LaunchAgents are property list (plist) files that tell macOS to run a program automatically at login or on a schedule. They are the most common persistence mechanism for macOS malware precisely because they are easy to install (no root required for user-level agents), survive reboots, and are rarely audited. The TanStack supply chain payload used a LaunchAgent named com.user.gh-token-monitor to continue exfiltrating credentials even after the malicious npm package was removed from the developer's machine.

LaunchAgent Basics for Security Teams

macOS has four relevant persistence directories:

  • ~/Library/LaunchAgents/: User-level agents. Run as the logged-in user at login. No administrator privileges required to install. This is where most malware persists.
  • /Library/LaunchAgents/: System-wide agents that run as the logged-in user. Requires admin/root to write.
  • /Library/LaunchDaemons/: System-wide daemons that run as root. Requires admin/root to write. Higher privilege but requires more access to install.
  • /System/Library/LaunchDaemons/: Apple system services. Should never contain third-party entries.

A LaunchAgent plist file has a minimum of two keys:

  • Label: A reverse-domain identifier (e.g., com.user.gh-token-monitor)
  • ProgramArguments: The command to run

Optional but common:

  • RunAtLoad: true, run immediately when loaded (at login)
  • StartInterval: N, run every N seconds
  • KeepAlive: true, restart if the process exits

Step 1: Audit LaunchAgents Across Your Fleet with osquery

osquery provides a standardized way to query system state across your macOS fleet. If you have osquery deployed (standalone or via tools like Fleet, Kolide, or Jamf Pro with osquery integration), run this query against all your macOS endpoints:

SELECT
  path,
  name,
  label,
  program,
  program_arguments,
  run_at_load,
  keep_alive,
  start_interval
FROM launchd
WHERE
  path LIKE '/Users/%/Library/LaunchAgents/%'
  OR path LIKE '/Library/LaunchAgents/%'
ORDER BY path;

This returns every LaunchAgent across all user profiles on the machine. For a fleet of 50+ machines, run this via osquery's distributed query feature and export the results to a spreadsheet.

For machines without osquery, use a shell script deployed via Jamf or Ansible:

find ~/Library/LaunchAgents /Library/LaunchAgents /Library/LaunchDaemons \
  -name '*.plist' 2>/dev/null | while read f; do
  echo "=== $f ==="
  /usr/libexec/PlistBuddy -c 'Print :Label' "$f" 2>/dev/null
  /usr/libexec/PlistBuddy -c 'Print :ProgramArguments' "$f" 2>/dev/null
done
Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

Step 2: Distinguish Malicious from Legitimate LaunchAgents

A typical developer machine has between 20 and 60 LaunchAgents. Most are legitimate. Distinguish them by checking these attributes:

Publisher verification: Legitimate LaunchAgents from major software vendors (Adobe, Dropbox, Zoom, Microsoft) have consistent naming patterns (e.g., com.adobe., com.microsoft.). An entry with a generic label like com.user.* or com.apple.* in the user LaunchAgents directory (~/Library/LaunchAgents/) is suspicious, Apple's own services are in /System/Library/, not the user directory.

Executable path: Check the ProgramArguments key. Legitimate agents point to executables in /Applications/, /usr/local/bin/, or other standard locations. An agent pointing to a binary in ~/Library/Application Support/ with a non-descriptive name, or to a shell script in a temp directory, is a red flag.

Code signing: Check whether the executable is signed by Apple or a verified developer:

codesign -dv /path/to/executable 2>&1

Look for Authority= lines. An unsigned executable or one signed by a developer certificate with an unfamiliar name warrants investigation.

Installation time: The plist file's modification time indicates when it was installed. An entry that appeared recently (within the last 30 days) and was not associated with a software installation you remember is suspicious.

Step 3: Investigate a Suspicious LaunchAgent

When you find a suspicious entry, do not remove it yet, investigate first to understand the scope.

Read the full plist:

cat ~/Library/LaunchAgents/com.user.gh-token-monitor.plist

Check when it was installed and by what process:

mdls ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
# Look for kMDItemDateAdded

Correlate the installation time with your shell history, what did you run around that time?

grep -h '' ~/.zsh_history | grep -B5 -A5 'npm install'

Examine the executable it runs:

# Get the executable path from the plist
EXEC=$(/usr/libexec/PlistBuddy -c 'Print :ProgramArguments:0' \
  ~/Library/LaunchAgents/com.user.gh-token-monitor.plist)

# Check file type and hash
file "$EXEC"
sha256sum "$EXEC"

Submit the hash to VirusTotal or compare against known IOCs from the relevant security advisory.

Check network activity of the running process: If the LaunchAgent is currently running:

lsof -i -n -P | grep $(pgrep -f gh-token-monitor)

This shows current network connections, revealing the C2 server the malware is communicating with.

Step 4: Remove at Scale via MDM

Once you have confirmed a malicious LaunchAgent label, remove it across your fleet before the attackers know you are aware.

For immediate removal via Jamf: Create a Jamf policy with a shell script payload:

#!/bin/bash
AGENT="$HOME/Library/LaunchAgents/com.user.gh-token-monitor.plist"
if [ -f "$AGENT" ]; then
  launchctl unload "$AGENT" 2>/dev/null
  rm -f "$AGENT"
  echo "Removed malicious LaunchAgent"
  logger -t security "Malicious LaunchAgent removed: $AGENT"
else
  echo "LaunchAgent not found"
fi

Set the policy scope to All Computers and trigger it as a self-service policy with immediate execution, or trigger via API for instant fleet-wide deployment.

For manual removal on a single machine:

launchctl unload ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
rm ~/Library/LaunchAgents/com.user.gh-token-monitor.plist

Also check for the associated executable and remove it:

rm ~/Library/Application\ Support/com.user.gh-token-monitor  # or whatever path the plist referenced

Ongoing Detection: osquery Scheduled Query

Add a scheduled osquery query to your fleet that runs every 6 hours and alerts on any new user LaunchAgent not in your approved baseline:

{
  "schedule": {
    "new_launch_agents": {
      "query": "SELECT path, label, program_arguments FROM launchd WHERE path LIKE '/Users/%/Library/LaunchAgents/%';",
      "interval": 21600,
      "description": "Detect new user LaunchAgents for persistence monitoring"
    }
  }
}

Pair this with a differential check, alert only on entries that did not exist in the previous run. Integrate the output with your SIEM or send to Slack/Teams via osquery's logger plugin.

For a faster check without osquery, deploy a Launch Constraint configuration via MDM that restricts which executables can be loaded as LaunchAgents, this is a macOS 13+ feature that blocks unsigned or unknown binaries from persisting via the LaunchAgent mechanism.

The bottom line

A malicious LaunchAgent installed in ~/Library/LaunchAgents/ requires zero administrator privileges, survives reboots, and continues operating after the initial malware payload is removed. The two controls that stop this class of persistence are: osquery scheduled queries that detect new LaunchAgents within hours of installation, and MDM-enforced removal scripts that can reach every machine in your fleet within minutes of confirmation.

Frequently asked questions

What is the difference between a LaunchAgent and a LaunchDaemon?

LaunchAgents run as the logged-in user and are loaded at login. LaunchDaemons run as root (or another system user) and are loaded at boot, before any user logs in. Malware more commonly uses LaunchAgents for user-level persistence because they do not require administrator access to install.

How do I list all currently loaded LaunchAgents on my machine?

Run: launchctl list | grep -v '^-' to see all loaded services. The output shows the PID (or - if not running), exit status, and label. To filter to only user-level agents you should recognize: launchctl list | grep 'com.user'

Can a LaunchAgent survive a macOS reinstall?

A standard reinstall that preserves user data (migration from Time Machine or from an old machine) will carry over ~/Library/LaunchAgents/ and the persistence will survive. A clean install with a new user account will not. If you are cleaning an infected machine, either perform a clean install or manually remove all LaunchAgent plist files and their associated executables before migrating user data.

What should I do if I find a LaunchAgent I do not recognize but it is not flagged by VirusTotal?

Zero detections on VirusTotal does not mean clean. Read the plist to find the executable it runs, then examine what that executable does: run strings on it to identify embedded URLs or filenames, check its code signing, review its network connections if it is running, and compare the installation time against your recent activity. When in doubt, remove it, legitimate software reinstalls cleanly.

Does Apple Silicon (M1/M2/M3) change how LaunchAgent persistence works?

The LaunchAgent mechanism is the same on Apple Silicon. The relevant difference is that Apple Silicon enforces stricter code signing requirements for executables, binaries must be signed or they are blocked by Gatekeeper. However, attackers can sign with an ad-hoc signature, a free Apple Developer certificate, or steal a valid certificate, so code signing alone is not a reliable indicator of legitimacy.

What is the fastest way to check whether a specific LaunchAgent label is running on a remote Mac without physical access?

Use Jamf Remote or your MDM's remote script execution to run: launchctl list | grep 'LABEL-YOU-ARE-LOOKING-FOR' on the target machine. If you have osquery deployed, the query SELECT label, pid, program_arguments FROM launchd WHERE label LIKE '%gh-token-monitor%' returns the result across the entire fleet in a single distributed query. For machines where neither is available, deploy a lightweight shell script via Jamf policy that writes launchctl list output to a file in a shared network location, then reads and filters that file centrally. The osquery route is the most scalable because it queries all endpoints simultaneously and returns structured results rather than raw text.

Sources & references

  1. Apple LaunchAgent/LaunchDaemon documentation
  2. osquery documentation
  3. MITRE ATT&CK T1543.001, Launch Agent

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.

Black Hat Giveaway

Win a $2,495 Black Hat pass.

Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.

Joins Decryption Digest daily briefing. Unsubscribe anytime.

Giveaway: Black Hat USA 2026 Full-Access Pass ($2,495 value)

Details →
Daily Briefing

Subscribe to enter the giveaway

Every subscriber is automatically entered. You also get daily threat intel every morning: zero-days, ransomware, and nation-state campaigns. Free. No spam.

Already subscribed? You're already entered.

Giveaway

Win a $2,495 Black Hat USA 2026 pass.