macos-plugin/skills/endpoint-security-cpu/SKILL.md
macOS EndpointSecurity/EDR high CPU & battery drain. Use when Kandji ESF / XProtect pegs a core; trace the exec storm via powermetrics + eslogger.
npx skillsauth add laurigates/claude-plugins endpoint-security-cpuInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Use this skill when... | Use something else when... |
|------------------------|----------------------------|
| A security extension (Kandji ESF, XProtect, an EDR) sits at sustained high CPU | launchservicesd is the hot process — use launchservices-health |
| Battery drains fast and powermetrics blames the security stack | The machine actually hung or panicked — use macos-incident-postmortem |
| syspolicyd/trustd/tccd/auditd are all elevated together | One ordinary app is hot — use ps/top directly |
| You suspect a process-spawn (exec) storm is taxing the system | Auditing kernel-extension inventory — systemextensionsctl list alone |
macOS-only. EndpointSecurity, eslogger (macOS 13+), powermetrics, and systemextensionsctl are Darwin-specific.
test "$(uname -s)" = "Darwin" || { echo "macos-plugin: not Darwin, refusing"; exit 1; }
An EndpointSecurity (ES) client — Kandji's ESF extension, XProtect's behavioral service, a corporate EDR — subscribes to the kernel's exec/open/rename event stream. Its CPU is proportional to the system-wide rate of those events. When something spawns hundreds of short-lived processes per second, every behavioral/security monitor must inspect each one, so they light up together:
XprotectService behavioral malware scan, per exec
syspolicyd Gatekeeper code-signing assessment, per binary
io.kandji…ESF corporate EDR, per exec/open
trustd / tccd cert eval / privacy, per access
auditd audit record, per event
kernel_task servicing the syscall + interrupt load
That column lighting up at once is the signature: one exec storm, taxing the whole stack. Restarting the EDR clears its queue for a few seconds, then the storm refills it. The fix is upstream — find and stop whatever is spawning the processes.
A hot ES extension with near-zero wakeups confirms this: it is draining a real inbound event queue in user space, not timer-spinning. High CPU with a high wakeup count and a quiet eslogger points instead at an internally wedged extension — the one case where restarting it (below) actually helps.
Both probes need root. If sudo isn't cached, hand them to the user with the ! prefix.
1. Per-process energy + wakeups — who is taxed, and is it spawn churn?
sudo powermetrics --samplers tasks --show-process-energy -n 1 | head -45
Read three things:
DEAD_TASKS high (CPU + deadlines + wakeups) = a storm of processes that spawned and exited within the sample — the exec churn made visible.2. The EndpointSecurity firehose — name the generator.
eslogger subscribes to the same stream the EDR consumes, and reports the initiating process. Rank the offenders over a short sample:
sudo eslogger exec 2>/dev/null | head -400 | jq -r '.process.executable.path' | sort | uniq -c | sort -rn | head -15
Whatever path dominates is the storm's source. If the stream scrolls instantly to 400 lines, the event rate itself is the problem. If it only trickles while the ESF stays hot, the extension is wedged internally — restart it.
Recurring sources, with the upstream fix that removes the load (not the symptom):
| Generator (fingerprint in eslogger) | Fix |
|---|---|
| uvx --from git+https://… MCP / tools re-resolving the git source on every launch | Install once (uv tool install git+…) and invoke the bare command |
| brew invoked in a loop (portable-ruby + Homebrew bash + git) — often a menubar "outdated" poller or per-invocation auto-update | export HOMEBREW_NO_AUTO_UPDATE=1; throttle/remove the caller |
| Many concurrent dev/AI sessions spawning short-lived git/node/bun/gh | Collapse duplicate sessions; dedupe per-session helper servers |
| A status tool shelling out on a tight timer | Lengthen its interval or cache its result |
macOS rewards not rebooting — both the EDR and the inventory are inspectable and (mostly) restartable live.
eslogger trickled while it stayed hot): sudo killall -9 <ESF-process-name>. sysextd relaunches the [activated enabled] extension within seconds. No reboot, no logout.systemextensionsctl list. Over long uptime, each EDR update leaves the prior version as [terminated waiting to uninstall on reboot]. Those are terminated — zero CPU — pure disk/registry cruft that only a reboot clears, and never the performance problem. Don't reboot for them.| Context | Command |
|---------|---------|
| ESF extension CPU now | ps -Ao pcpu,comm \| grep -i ESF-Extension |
| Storm source (top initiators) | sudo eslogger exec 2>/dev/null \| head -400 \| jq -r '.process.executable.path' \| sort \| uniq -c \| sort -rn \| head |
| Security-stack energy snapshot | sudo powermetrics --samplers tasks --show-process-energy -n 1 \| head -45 |
| Stale sysext versions (reboot-gated cruft) | systemextensionsctl list \| grep -c 'waiting to uninstall' |
| Restart wedged ESF (sysextd relaunches) | sudo killall -9 <esf-process-name> |
| Tool | Role | Root? |
|------|------|-------|
| powermetrics --samplers tasks | Per-process energy, wakeups, DEAD_TASKS churn | yes |
| eslogger exec open rename | The ES event stream the EDR consumes; names the initiator | yes (macOS 13+) |
| systemextensionsctl list | Extension inventory + reboot-gated stale versions | no |
| ps -Ao pcpu,comm | Instantaneous CPU per process | no |
launchservices-health — when launchservicesd (not an ES extension) is the hot processmacos-incident-postmortem — when the load caused an actual hang/panic; reconstruct from DiagnosticReportstesting
Verify accumulated bug claims at upstream HEAD and dedup against trackers before filing issues. Use when filing upstream reports from backlogs, audit docs, or git-history findings.
documentation
Gate outward-bound text (upstream issues, docs, PR bodies) through isolated haiku fresh-reader critique before publishing. Use when an artifact must survive a reader with zero project context.
tools
Suggest improvements to SKILL.md content, descriptions, or tool config from eval results. Use when raising pass rates, fixing triggering, or iterating on a skill after evaluation.
tools
deadbranch CLI for stale-branch cleanup — dry-run preview, TUI or non-interactive delete, protects main/develop/WIP. Use when asked to clean up branches, prune branches, or remove stale branches.