A year ago we introduced coreSigma, a set of Sigma rules for the macOS Endpoint Security Framework (ESF) and Unified Log telemetry. Ten months ago (in Part II), we said what would come next: rule tuning guides, correlation rules across ESF and Unified Log events, and support for more SIEM platforms. coreSigma 2.0 delivers all three.
2.0 also updates how the shipped rules perform. Every rule now has a tested status on each target as seen in our simulations: it fires, it's noisy, it's silent, the field it needs isn't in our data, it converts (but there's nothing to run it on), or it doesn't convert or run. The support matrix in the repository records each one, with a reason for every rule we couldn’t verify.
The blog title is the main lesson of this release: a Sigma rule that converts cleanly can still never match anything. Converting isn’t detecting, and the only way to know the difference is to run the rule against real (or real, but simulated) telemetry and look.
What's improved
Part III also hardens what shipped in the first release:
- Collectors. Both were reworked to run cleanly as shipped and to read the fields
eslogger actually emits. - Event mapping. Rules now key on
eslogger event names instead of numbers, checked against Apple's ESTypes.h, so each rule points at the event it means. - A tested status for every rule. Each rule was run against real telemetry, then kept, fixed or retired based on what it actually matched.
- Pinned backends, so a backend update can't change field names under you.
A two-layer design
coreSigma 2.0 separates what a macOS event means from how a given SIEM stores it.
The first layer is pySigma-pipeline-macos, a pySigma plugin built from one macOS taxonomy table. The table says which eslogger events make up each Sigma category (process creation, file events, background tasks, TCC changes and so on) and which raw fields each Sigma field comes from. Rules carry no event numbers. The plugin scopes each rule from its logsource. A rule that says product: macos and category: process_creation becomes a query on exec events in Elastic, Splunk, OpenSearch and ClickHouse, and a process-launch query limited to Macs in Falcon. The rule file is the same in every case.
The second layer is a thin map per target. Elastic, OpenSearch, Splunk and ClickHouse share an ECS map. Unified Log rules get their own maps, one for ECS documents and one for raw log output. Falcon gets a map onto its process telemetry.
The collectors are built from the same table. They turn eslogger and log stream output into flat ECS documents shaped like Elastic Defend’s, so one Elastic pipeline serves both collector data and the Defend-style layout.
Part II listed custom backends, pipelines for more SIEM platforms, as something to explore. The two-layer design is how we did it. coreSigma doesn't write its own backends. Instead, it uses the existing pySigma backends and adds a field map for each data shape. Splunk, OpenSearch and ClickHouse reuse Elastic's ECS map, so they needed no new map at all (and Falcon needed one map onto its process telemetry). The taxonomy and the rules didn't change for any of them. The only backend code we wrote lets Kibana run ordered correlations as EQL sequences.
The collectors are still a research bench. They let you see macOS telemetry and test rules end to end. In production, the rules are meant to run on the data your EDR already collects.
Six targets
The support matrix covers six query targets, plus two that only convert:
- Elastic, with both EQL and ES|QL. All 69 rules deploy as Kibana detection rules on Elasticsearch 8.19: single-event rules and the two ordered correlations as EQL, the other five correlations as ES|QL.
- Splunk, as a generated app with one saved search for each of the 69 rules.
- OpenSearch.
- ClickHouse, through the community pySigma backend.
- CrowdStrike Falcon, through LogScale queries for process-creation rules.
- Microsoft Defender and Sentinel, through KQL, and Elastic Defend’s own data. The matrix lists these as convert-only. We then spot-checked Defender for Endpoint and Elastic Defend 9.5.6 on the test Mac. On both, the process-creation rules fire, including HTTP tools, SSH and suspicious execution, and on Defend the paste chain’s
osascript, dscl and ditto steps. File-creation rules stay silent: on macOS, Defender recorded only script and binary creates, and Defend reports a new file only as a modification. The Elasticsearch backend change we’re preparing matches those modifications, and with it the LaunchAgent, Application Support and agent-settings file rules fire on Defend too. Unified Log rules and rules on ESF-only fields don’t apply to Defend data. Neither EDR showed noise over a short idle window. Sentinel wasn’t tested.
On Falcon, fourteen process-creation rules convert to LogScale. We ran the converted queries read-only in Falcon, scoped to our test Mac, in the same simulation windows as everything else. Twelve matched: HTTP tools with protocol indicators, suspicious process execution, SSH connections, Script Editor running a shell, and the tool rules the single-step simulations exercise (cloud storage, compression, DNS lookups, encryption, file transfer, network shares, network utilities and remote execution). Data destruction also matched, but only on the simulations’ own rm -rf of their staging directories (so we’re not exactly counting it). These are query matches, not Falcon alerts.
Falcon exposes only a parent process's base name, so rules that test a parent path with "contains", or a signing ID, don't convert. The LogScale backend has no correlation support, so the correlations don't convert either.
We've submitted the upstream pieces to SigmaHQ for review:
Fields for seven more ESF categories (authentication, sudo, screen sharing, configuration profiles, XProtect, Gatekeeper overrides and Open Directory user changes) are part of the spec update submitted to SigmaHQ, and our plugin maps them now. XProtect fields come from Apple’s headers, and we haven’t seen an XProtect event on our test host yet. A Gatekeeper override event appeared when we clicked Open Anyway by hand, and the override rule fires on it. The Elasticsearch backend gets these fields in a later PR.
A tuning method, delivered as Sigma filters
The tuning guide is built on Sigma filters. A filter lives next to the rule instead of inside it, so you can take upstream rule updates without losing your local exclusions.
The guide covers nine patterns:
- Measure before cutting. Group a noisy rule's alerts by host, process path, and command line, and cut only the pair that drives the volume.
- Pair a process name with its directory. Exclude
/usr/bin/curl, not every process named curl, which would also hide a curl dropped in /tmp. - Match the forms attackers use, not generic strings. A curl rule keyed on
https:// fires on every download. Requiring an output file in /tmp keeps that attack form and drops the noise. Check that the form appears in the process you filter on: a pipe to a shell never shows up in curl's own command line. - Catch dead filters, the ones on fields your data doesn't collect or populate as written. A filter on
/etc/hosts never matches ESF data, which reports the resolved path, /private/etc/hosts. - Filter on the process that runs, not on names in its command line. Excluding command lines that contain
softwareupdate also excludes /tmp/evil.sh softwareupdate. Exclude /usr/sbin/softwareupdate as the process instead. - Keep universal exclusions apart from environment-specific ones. Apple's own daemons go in one filter file. Your MDM agent running
openssl enc goes in another, which you re-check when the agent changes. - Scope AI coding agent exclusions to one agent and one rule. An npm-installed agent runs its shell tool as
/bin/zsh under node, which trips the npm persistence correlation. Exclude only /bin/zsh from that one base rule; node starting /bin/sh, curl or osascript still alerts. - Threshold noisy events with a correlation instead of excluding them. Deleting one file in
/Library/LaunchDaemons can be maintenance; deleting ten in a minute is worth an alert. - Write down what you still catch. Next to an exclusion for Xcode's compression tools, note that compression from
/tmp still alerts, and that a binary planted inside Xcode.app is the blind spot.
Pattern 7 is new for this release. AI coding agents are an attack surface as much as a noise source: a poisoned package, a malicious extension or a prompt injection runs with the user's privileges through the agent's own tools. So the guide never excludes an agent from everything. Each exclusion covers one agent and one rule, and says what still alerts and what it misses.
Six example filters ship in filters/examples/. The guide is about method. It has no numbers from any environment.
Author Note: To keep a record of those decisions, pair the patterns with our Agentic Detection Engineering Framework (ADEF) (repo). ADEF gives each detection a journal and walks it through the FORGE lifecycle (Find, Observe, Refine, Govern, Evolve), so every exclusion, threshold and keep-or-retire call, along with what it still catches, gets a dated, written reason.
Correlation rules for 2026 macOS attack chains
We picked three chains from public 2026 reporting:
- ClickFix-delivered stealers in the AMOS lineage. The victim pastes a command, a script fetches the stealer, a fake password prompt collects credentials, and the loot is staged for exfiltration. Microsoft (and again in August), Unit 42 and Jamf Threat Labs all wrote these campaigns up this year.
- Signed-dropper Gatekeeper evasion, as in the 2026 MacSync variants Kaspersky described: a quarantine attribute stripped, a binary re-signed ad hoc, then persistence.
- npm install-time droppers that persist through AI-agent and IDE configuration, as in the Axios package compromise (StepSecurity, Huntress) and the Mini Shai-Hulud worm.
From those come seven correlation rules:
- three chain correlations: Paste-to-Stealer, Gatekeeper Evasion Staging, and Package Manager Payload to Persistence
- a credential-file fan-out by Node or Bun (
value_count) - two thresholds: mass process termination and a DNS failure spike
- one rule across ESF and Unified Log
The cross-source rule joins the paste chain's hidden-answer osascript prompt, seen in ESF, with a keychain-subsystem log line from the same process, seen in the Unified Log, on host, process ID and executable within one minute. It matched in the paste simulation and stayed silent over 13 idle hours.
We also checked what the Unified Log half adds. In a probe, the log line appeared in 12 of 12 ordinary osascript runs, so all of the rule's selectivity comes from the ESF side. It's a working ESF and Unified Log correlation pattern, and an example of how to join the two sources. It isn't a stronger detection than the ESF rule on its own.
Six correlations are verified: Paste-to-Stealer, Gatekeeper Evasion Staging (through its keychain partition-list step), Package Manager Payload to Persistence, the cross-source rule, the credential-file fan-out and mass process termination. The DNS failure-spike rule isn’t: the collector doesn’t capture the DNS lines it counts.
Paste-to-Stealer is an ordered rule, and Kibana runs it as an EQL sequence, which enforces the order. It alerts on our simulation run in the real ClickFix order (download, prompt and credential check, then staging) and not on an earlier run with the download step last. The other backends don't check order:
- Splunk runs the rule's declared unordered fallback.
- ES|QL can't convert ordered correlations.
- OpenSearch has no correlations.
- The ClickHouse backend doesn't check order either; our matrix tool rewrites its query with
windowFunnel to do so.
Correlations run on Elastic (the two ordered rules as EQL sequences, the rest as ES|QL), Splunk, and ClickHouse. Each backend counts differently. Splunk counts in fixed time bins, so a chain that crosses a bin boundary is missed. ClickHouse groups over the whole search range rather than per time span. Both are backend behaviors and the matrix notes them on every affected cell.
How we tested
We built a repeatable testing methodology and invite you to test these rules in your own environment. The simulations, the matrix tool, and the full results are all in the repo.
The test machine
- A short-lived EC2 Mac in an isolated sandbox account.
- No inbound access, managed only over SSM, and DNS through an allow-list.
- ESF and Unified Log captures were shipped out and replayed into local stores.
Simulations
- Benign simulations of the three chains, plus two single techniques: an SSH key login to localhost and a request for writable and executable memory.
- The full Gatekeeper evasion sequence in a row: quarantine strip, ad hoc signing, then a keychain partition-list change.
- 27 single benign steps, one per rule, such as a
zip archive, a dig lookup, a throwaway local account created and deleted, the firewall toggled and restored, and a refused csrutil disable. - Three hands-on windows, done by a person over Screen Sharing: a Screen Sharing connection, Gatekeeper’s Open Anyway, and Script Editor running a one-line shell command. Each window counts only for its own rule, so nothing else the person did verifies anything.
- Each has its fidelity limits written down:
- ClickFix can’t click through Script Editor; its download goes to a sink that returns nothing and its persistence is inert.
- npm uses a compiled stand-in for Node.js.
- Gatekeeper signs an inert script and never runs it.
- The single steps act on dummy files, throwaway accounts and keychains, and stand-in processes named like the real tools.
- 40 clean runs went into the matrix. The npm and paste chains ran three times each. The second npm run used the exact simulation we ship, and the third runs used revised atomics: launch items and a stealer stub for paste, and node writing its own configuration file for npm.
- All 36 atomics are in the repository for you to reuse.
Rounds
- Replay. Each rule was converted with the pinned backends and our pipelines, then run against Elastic (EQL and ES|QL), OpenSearch, Splunk, and ClickHouse holding the replayed captures. Falcon was queried separately, read-only.
- README walkthrough. We followed the README word for word on a clean checkout. The first pass found four documentation and script bugs. After the fixes, the second pass deployed all 62 single-event Kibana rules with no failures.
- End to end, live. On the test Mac, we followed the QUICKSTART word for word with the collectors running live. One Paste-to-Stealer run raised 60 Kibana alerts from 13 rules, including the chain's own.
- Through the three attack simulations, live collector output gave the same hit counts as the replay for the 12 rules verified at that point.
- On our local test bench, all 69 rules deployed to Kibana, and on the replayed windows the verified rules we checked alerted with the matrix's counts, Paste-to-Stealer on the in-order run only.
- Install and operations. This testing also caught install problems, now fixed. If you're upgrading: the tooling needs Python 3.10 to 3.14, and redeploying over 1.x Kibana rules now replaces them cleanly. After an 80-second Elasticsearch outage, the collector spooled every document, and a new script re-sent all of them.
Noise
- A rule that fires on everything isn't verified.
- The baselines: 13 idle hours on the test Mac, plus a private sample from a daily-use laptop (2.0 hours of an ESF capture, 0.3 hours of slices of a longer ESF log, and 2.6 hours of Unified Log).
- Only each rule's quiet or noisy outcome is published, never the laptop data. Eleven rules came out noisy.
Reading a "fires" cell
A hit inside a clean simulation window isn't attribution to the simulated chain: it can come from anything the host did in those seconds. We read the hits behind each verdict before counting it.
Results
Our simulations exercised 56 of the 69 rules, and 40 of those are verified: each fired on at least one target in a simulation window without exceeding its noise threshold. The other 13 cover techniques we haven’t simulated yet. They convert and run on every store and the matrix records each one with its reason.
|
Rules |
| Verified live |
40 |
| Exercised, but noisy on the idle host or the laptop |
7 |
| Exercised, but the evidence wasn't in our captures |
4 |
| Exercised, but the simulation left nothing the rule matches |
3 |
| Exercised, but the hits weren't the behavior |
2 |
| Not exercised by a simulation yet |
13 |
- By target: 41 rules fired on Elastic EQL, 42 on ES|QL, 39 on OpenSearch, 45 on Splunk, 43 on ClickHouse, and 13 on Falcon.
- Reproducible: the npm simulation’s verdicts matched across its first two runs, on every store, and on Falcon.
- Added by the later runs: 24 rules. The single steps added the tool-use rules (archiving, encryption, DNS lookups, file transfer, remote execution, network shares), account changes, kills of security tools, XPC from unusual locations, keychain partition-list changes, failed
sudo attempts, and the credential fan-out and mass-termination correlations. The full Gatekeeper sequence verified its correlation, and the hands-on windows verified Gatekeeper user overrides and Script Editor running a shell. Two Unified Log rules, Firewall Disabled and securityd code-signature failures, fired once they matched the lines macOS 26.7 actually logs. - “Not verified” doesn’t mean broken. Sensitive File Access, for example, matched the stealer stub’s read of a TCC database copy but is noisy on the laptop, and Data Destruction Tools matched only our simulations’ own
rm -rf. The spctl Gatekeeper rule ran against a real assessment of an unsigned binary, but macOS 26.7 logged no line that marks a refusal.
Testing doesn't stop at this release. We'll keep simulating the rules we haven't exercised yet, tuning the noisy ones, and update the support matrix in the repository as each rule is verified or tuned.
Lessons worth passing on:
- TCC consent prompts hang headless runs. Write simulations that never touch protected folders.
- Launch constraints kill renamed Apple binaries. Compile a stand-in instead of copying
/bin/sh. - Your own capture job shows up in your noise baseline. Exclude the harness.
- A capture job must use exactly the collector’s flags. Ours missed
—level info for a while, and Info-level rules read as silent on those captures. - Before claiming the second half of a correlation adds signal, check how often it appears on its own.
- Run the converted query on the real platform. A field name the platform doesn't have can match nothing without an error.
The full matrix, with every cell and footnote, is in docs/SUPPORT_MATRIX.md
If you run the atomics or the rules on your own Macs or EDR data, we'd like to hear what fires and what's noisy. We encourage you to open an issue with what you find.
What not verified…yet
- Jamf Protect is deferred to the next release.
- Case-sensitive matching. ES|QL matches plain strings case-sensitively, and OpenSearch does too on plain keyword fields such as the Unified Log subsystem. XProtect logs under two casings, so some of its lines are missed on those targets.
- Unified Log collection.
log stream never delivers launchd's own lines, and the collector has no category for XPC or DNS subsystems, so rules on those see only lines that other categories happen to select. Our test captures were also Default-level until late in testing, so rules whose evidence is an Info-level line read as silent on the earlier windows. And during the simulation windows, the unfiltered log stream behind our test captures was missing about half of the in-scope lines that the live collector received, so Unified Log hit and noise counts from replay are lower bounds. Rerunning the rules on the live documents changed no verdict. - Renames. File-event rules also take the destination of a rename, but the collector keeps only some renames, so rename coverage is partial.
- Defender for Endpoint and Elastic Defend were spot-checked, not measured rule by rule, and Sentinel is convert-only. On both EDRs, file-creation rules stay silent on macOS.
- Falcon covers process creation only, with no correlations.
- One correlation per run. With the pinned sigma-cli, convert one correlation and its base rules per
sigma convert run.
Try it. Test it.
To run the local test bench (Elasticsearch and Kibana in Docker, plus Splunk, OpenSearch or ClickHouse if you want them), with Python 3.10 to 3.14:
git clone https://github.com/Nebulock-Inc/macos-coresigma.git
cd macos-coresigma
./setup.sh && source venv/bin/activate
./scripts/bench_up.sh elastic
python3 scripts/deploy_sigma_rules.py
Then run the collectors on a test Mac, or replay a capture, and import the dashboards. Splunk Free works too: deploy_sigma_rules.py --target splunk writes the app. The README has the details, including how to pass credentials to the ESF collector under sudo.
On your own EDR data, install the plugin and convert with sigma-cli for your backend:
pip install pySigma-pipeline-macos
sigma convert -t eql -p macos_esf_ecs rules/esf/
Thanks, and what’s next
Thanks to Doug Metz (#2, #3, #4) and Sydney Marrone (Nebulock's own, and THOR Collective) (#5) for their contributions, and to Thomas Patzke & the SigmaHQ maintainers.
Next on our list:
The project is open source. Issues, rules, and pull requests are welcome.