Skip to content

fix(macos): stop XProtect's own rule loading from raising alerts - #2788

Merged
kryonsx merged 1 commit into
utmstack:v11from
kryonsx:codex/macos-alert-noise-20260929
Oct 1, 2026
Merged

kryonsx merged 1 commit into
utmstack:v11from
kryonsx:codex/macos-alert-noise-20260929

Conversation

@kryonsx

@kryonsx kryonsx commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Why

"XProtect Evasion or Tampering Detected" alerted every time XProtect loaded its own rules. It is meant to ignore XProtect's own service (!equals("origin.process", "XProtectService")), but macOS logs the service as XprotectService (lower-case p) and the comparison is case-sensitive, so the service's routine "Using XProtect rules location: /var/protected/xprotect/XProtect.bundle/Contents/Resources/XProtect.yara" and "Using meta-plist from: …/XProtect.meta.plist" messages matched. These come in bursts when the service starts: between 2026-09-28 00:00 UTC and the afternoon of 2026-09-29 one v11 deployment stored 29 alerts, 23 of them in one hour, and another stored 35, 11 in one hour. groupBy on process and host then stored a child alert for every record.

Noise is reduced here by de-duplication and by making the rule's own XProtect service exclusion work; nothing else is excluded.

What changes

Before After
XProtect service exclusion !equals("origin.process", "XProtectService") !equalsIgnoreCase("origin.process", "XProtectService")
Alerts groupBy adversary.process, adversary.host deduplicateBy dataSource, adversary.process: one alert per Mac and process, repeats dropped for seven days

The other five branches (framework bypass messages, writes under XProtect.bundle, MRT stopped, gk.db changes, MRT subsystem failures) are unchanged, and a process other than XProtect's service that names XProtect's rule files still matches.

Expected volume

Replaying 30 days of real records from the five v11 deployments that send macOS logs (96 records matched the shipped rule, all on three deployments):

Alerts in 30 days Worst hour Worst day
Shipped condition, de-duplicated 9 2 3
This change 3 1 2

The remaining matches are osascript and sandboxd naming XProtect's script rules and app protection rules while loading them.

Tests

  • plugins/alerts/macos_alert_volume_test.go (new) runs fabricated unified-log records through the macOS parser model and the pinned go-sdk v1.1.36 CEL. It pins the de-duplication keys and checks that XProtect's service loading its rules does not match in either spelling, while another process copying over the rules, a delete of the rules and MRT being terminated do. On the shipped rule the XprotectService record matches.
  • macCheckGrouping in macos_contract_test.go now also reads deduplicateBy and accepts dataSource as the source identity.
  • go test ./... in plugins/alerts passes on this branch, which is based on current v11 (89cd26c4).
  • Local EventProcessor lab (engine 8a3ade7, go-sdk v1.1.36, the production events and alerts plugins, OpenSearch 2.19.1, this branch's macOS filter from current v11) with fabricated unified-log records in two runs: XProtect's service loading its rules and meta-plist, another process copying over the rules, and MRT being terminated on one Mac; then the copy and MRT again on that Mac and the copy on a second Mac. Both runs processed all of their events (one hung after writing its output, the engine's plugin restart under memory pressure on the test machine).
    • With this branch: 5 alerts produced and 3 stored (the copy and MRT on the first Mac, the copy on the second); both repeats on the first Mac were dropped as duplicates, and XProtect's service loading its rules produced none.
    • With the rule as shipped: 7 alerts produced and 5 top-level alerts plus 2 children stored, including one for each of the service's two routine messages.

🤖 Generated with Claude Code

"XProtect Evasion or Tampering Detected" excludes XProtect's own service
by name, but macOS logs it as XprotectService and the check was
case-sensitive, so every time the service loaded its rules ("Using
XProtect rules location: ... XProtect.yara") the rule alerted, and
groupBy stored a child alert for each record.

The service is now recognised whatever the letter case of its name, and
the rule raises one alert per Mac and process, dropping repeats for
seven days (deduplicateBy dataSource, adversary.process).

macos_alert_volume_test.go pins both; macCheckGrouping now also reads
deduplicateBy and accepts dataSource as the source identity.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@kryonsx
kryonsx marked this pull request as ready for review October 1, 2026 02:06
@kryonsx
kryonsx requested a review from a team October 1, 2026 02:06
@kryonsx
kryonsx merged commit 2cc95db into utmstack:v11 Oct 1, 2026
3 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant