Skip to content

Evaluation

Methodology

  • Data. Everything is pinned and checksum-verified: SigmaHQ 07ec293 (rules, regression_data, known-FPs.csv), evtx-baseline v0.8.5 (benign), OTRF d9d40ef (Windows, Linux, AWS emulations), Splunk attack_data b4573ed (Linux, CloudTrail), ATT&CK 19.2. See Datasets.
  • Run. One bench workflow run, 37093721154 at commit 9906b42 with all three jobs green, on clean ubuntu-latest runners (4 vCPU, 16 GB): the bench job (all stages), the backend job (OpenSearch) and the report job, which joins both and checks that every results file carries that run's id and commit. The run id and commit are stated at the top of the results below and in every file's provenance (with package versions and dataset pins). The run asserts that every OTRF archive and regression capture was readable, so the files that endpoint AV quarantines on the development laptop are included.
  • Technique detected. A rule tagged with the capture's technique or its parent fired on the capture. Sibling sub-techniques are reported separately as "lenient".
  • Alerts per host-day. Hits divided by the sum of per-host recording spans (2.55 host-days). The budget is 20 alerts per host-day per rule (10% of a 200/day SOC); the fleet table projects it to N hosts. The recordings are install-day telemetry, so install-related rules are likely noisier here than in steady state.
  • Decay ground truth, simulated. A TP-validated rule is broken by a change when its regression captures, replayed through the simulated change, no longer fire it. The static check only sees the benign field inventory, transformed by the same change, so its precision there is partly a soundness property of the transform.
  • Decay ground truth, real. OTRF captures that log both Sysmon EID 1 and native Security 4688 are replayed with and without their Sysmon channel; a rule is broken when it fires on them before and on none of them after. The prediction uses the benign inventory with its real Sysmon events dropped. No simulator is involved.
  • Intervals and tests. Wilson 95% intervals for proportions, printed as k/n, p% [lo, hi]; exact McNemar tests for paired comparisons on the same rules or datasets; a bootstrap over rules for the FP-model AUCs and for Cohen's kappa; the "interval over CV repetitions" in the FP model only measures fold-assignment noise.
  • Exclusions. The 23-rule rules-placeholder folder; deprecated rules; 2 rules with unsupported features.

Results

Provenance. Every number below comes from one bench workflow run, 37093721154 at commit 9906b42, with all three jobs green (bench, OpenSearch backend, report) on clean ubuntu runners and fresh, checksum-verified downloads. The results artefact of that run is committed unmodified; each results file records the run id, git SHA, package versions and dataset pins, and python benchmarks/bench.py verify diffs a reproduction against it. Proportions are k/n, p% [Wilson 95% CI]; the full tables are in results/SUMMARY.md and the method is on the Evaluation page.

1. Decay monitor (the novel part)

anvil decay checks every rule's condition for satisfiability against the field inventory observed per log source. It uses benign telemetry only, with no attack data. It is compared with two field-presence checks (the per-field logic of schema validation): per log source, and global (ANVIL 0.1).

decay ablation

Real change, no simulator. 88 OTRF captures log both Sysmon EID 1 and native Security 4688. 286 Windows rules fire on them; 122 fire on none once the Sysmon channel is dropped from the real captures. The prediction comes from the benign inventory with its real Sysmon events dropped:

method flags on the 122 broken rules (recall) precision false alarms on the 164 rules that keep firing
symbolic (ANVIL) 114/122, 93.4% [87.6, 96.6] 114/114, 100% [96.7, 100] 0
field presence, per source 119/122, 97.5% [93.0, 99.2] 119/182, 65.4% [58.2, 71.9] 63
field presence, global (0.1) 115/122, 94.3% [88.6, 97.2] 115/177, 65.0% [57.7, 71.6] 62

Exact McNemar tests on the same rules: false alarms 0 vs 63 (p = 2.2e-19) and 0 vs 62 (p = 4.3e-19); flags on broken rules 114 vs 119 (5 discordant, p = 0.062) and 114 vs 115 (5 / 6 discordant, p = 1.0). Of the 8 misses, 3 (two WMI-event rules and one file-deletion rule) have no log source in the benign inventory even before the change, so they sit on the floor below and are not counted as worsened; 5 are process-creation rules with a selection whose fields 4688 also carries, which stop firing because the captures' 4688 events do not match them (different values, or a field: null filter that now excludes every event), an effect a field-level check cannot see. Labels here are "fires at all", not "detects its technique".

Simulated changes. Each change is applied to the TP-validated rules' SigmaHQ regression captures (ground truth) and to the benign inventory (prediction). Because the same transform builds both sides, the symbolic check's 100% precision in these rows is partly a soundness property of the transform; the real row above does not have that property. Recall / precision:

change (broken TP rules) symbolic (ANVIL) field presence per source field presence global (0.1)
Sysmon removed, 4688 only (115) 109/115, 94.8% [89.1, 97.6] / 109/109, 100% [96.6, 100] 114/115, 99.1% [95.2, 99.8] / 114/272, 41.9% [36.2, 47.8] 109/115, 94.8% [89.1, 97.6] / 109/266, 41.0% [35.2, 47.0]
... and 4688 command-line auditing off, fleet-wide (349) 333/349, 95.4% [92.7, 97.2] / 333/333, 100% [98.9, 100] 347/349, 99.4% [97.9, 99.8] / 347/369, 94.0% [91.1, 96.0] 342/349, 98.0% [95.9, 99.0] / 342/363, 94.2% [91.3, 96.2]
same, off only on converted hosts (mixed inventory, 349) 109/349, 31.2% [26.6, 36.3] / 109/109, 100% [96.6, 100] 252/349, 72.2% [67.3, 76.6] / 252/272, 92.6% [88.9, 95.2] 247/349, 70.8% [65.8, 75.3] / 247/266, 92.9% [89.1, 95.4]
ECS field rename (430) 426/430, 99.1% [97.6, 99.6] / 426/426, 100% [99.1, 100] 430/430, 100% [99.1, 100] / 430/435, 98.9% [97.3, 99.5] 430/430, 100% [99.1, 100] / 430/435, 98.9% [97.3, 99.5]
CommandLine dropped (234) 220/234, 94.0% [90.2, 96.4] / 220/220, 100% [98.3, 100] 233/234, 99.6% [97.6, 99.9] / 233/246, 94.7% [91.2, 96.9] 233/234, 99.6% [97.6, 99.9] / 233/246, 94.7% [91.2, 96.9]
Hashes dropped (0) 6 rules flagged, none TP-validated 47 flagged; 7 have TP evidence and all 7 are false alarms 54 flagged; same 7

No change-induced false alarm on a TP-validated rule. Against per-source presence, the false-alarm difference is significant in every row (exact McNemar, e.g. 0 vs 158 of 345 unbroken rules, p = 5.5e-48, for the Sysmon removal), and presence catches more broken rules in the command-line rows (0 vs 14 discordant, p = 1.2e-4). The symbolic check's weakness is the fleet-union inventory: when only some hosts change (the mixed row), the union still contains the field. The 6 rules it flags for the Hashes change need Hashes on every path, so presence flags them too; they have no regression capture, so they are not scored. On unchanged telemetry, 183 rules (6.4%) are already broken or have no source and are reported separately as the floor; 3 of them are TP-validated (WMI Event Subscription, Important Scheduled Task Deleted/Disabled, and a RedSun EICAR rule), i.e. false alarms before any change. Per-rule flag sets and every test are in results/decay.json.

2. TP replay (recall) on SigmaHQ regression captures

engine detected missed could not evaluate detection rate
ANVIL engine (1.x) 463 0 0 463/463, 100% [99.2, 100]
ANVIL 0.1 (frozen baseline) 420 14 29 420/463, 90.7% [87.7, 93.0]
pySigma -> SQLite (independent condition/value semantics on ANVIL-routed events) 461 1 1 461/463, 99.6% [98.4, 99.9]

This only measures recall, and it was the development acceptance set: most captures hold a single event, so over-matching can barely be detected here. The benign replay and the OpenSearch cross-check test the other direction. On an 1,800-event benign sample, the 1.x engine with log-source routing switched off raises 163 alerts from 16 rules over 5.12M rule evaluations, exactly what the 0.1 engine raises; with routing on it raises 15 alerts from 2 rules over 44k evaluations. So on this sample the whole reduction comes from routing, not from the 1.x value-semantics changes.

3. False positives on clean Windows hosts (evtx-baseline)

The corpus has 2,994,137 events from three hosts (Win10, Win11, Server 2022 AD), covering 2.55 host-days of install-day recordings. 75/2,789 observable rules fire, 2.7% [2.2, 3.4]. The budget lets a rule use 10% of a 200 alerts/day SOC, i.e. 20 alerts per host-day. When the per-host rate is projected to a fleet, the number of rules over budget grows:

hosts 1 3 10 100
rules over budget (of 75 firing) 12 26 41 75

The noisiest rules are install-driven (scheduled-task registry writes, AppX installs, AppCompat), so these per-day rates are likely higher than a steady-state host's (see Limitations). By level: 0/128 critical, 5/1,353 high (0.4% [0.2, 0.9]), 24/1,096 medium, 40/199 low and 6/13 informational rules fire. Against SigmaHQ's known-FPs.csv, 27 of the 29 medium+ rules that fire are listed (matched per rule id; the list was also used during development). Eight non-low rules fire in ANVIL without an entry: 2 medium (PowerShell-classic HostApplication parsing) and 6 informational. SigmaHQ's goodlog CI is green on the same images, and excusing per rule id is more lenient than its MatchString filters, so these 8 are a lower bound on rules where ANVIL fires and SigmaHQ's checker does not. They are listed in SUMMARY.

fp by group

4. Emulated attacks and ATT&CK coverage

corpus captures technique detected (exact or parent) lenient (sibling sub-techniques credited) any alert
OTRF Windows atomic 98 62/98, 63.3% [53.4, 72.1] 69/98, 70.4% [60.7, 78.5] 95/98, 96.9% [91.4, 99.0]
Linux auditd (Splunk attack_data, OTRF) 65 8/65, 12% [6, 22] - 19/65, 29% [20, 41]
Sysmon for Linux 63 13/63, 21% [12, 32] - 38/63, 60% [48, 71]
AWS CloudTrail 40 6/40, 15% [7, 29] - 32/40, 80% [65, 90]

The CI runner also records its own telemetry (auditd and Sysmon for Linux; scripts/record_linux_live.sh). In a 3-second window of benign discovery-style commands, each labelled with the technique it resembles, 7/8 techniques are detected, 88% [53, 98] (T1033, T1082, T1007, T1087.001, T1057, T1016, T1083; T1027 missed: the SigmaHQ base64 rules look for decoding, piping into a shell or encoded shebangs, not for encoding a file), from 755 events and 17 alerts by 10 Linux rules. An 11-second benign developer workload on the same runner (venv, pip install, the unit tests, git; 15,480 events) fires 42 alerts from 6 Linux rules; it is far too short for a per-day rate and serves as a parser smoke test.

On ATT&CK Enterprise 19.2 for Windows, rule tags claim 299/474 techniques, 63.1% [58.6, 67.3]. Only 164/474, 34.6% [30.5, 39.0], are validated by a rule that fires on a real capture. On the APT29 evaluation captures, 111 and 117 rules fire, and they carry 69-70 distinct technique tags. That counts tags on fired rules, including rules triggered by background noise, not techniques covered.

SigmaHQ ships no correlation rules at the pinned commit 07ec293 (0 correlation: documents in any folder; also 0 on master 330d1cf, 2026-10-02). Its legacy aggregations live outside the rule folders: unsupported/ holds 87 rules, 45 with count() and 54 with any aggregation in the condition, plus one count() rule in deprecated/ (results/lint.json, aggregation_census). Correlation is therefore out of scope.

coverage

5. Real backend: OpenSearch vs ANVIL

In the CI backend job, pySigma's OpenSearch Lucene backend converts each Windows rule, using pySigma's own Sysmon and Windows log-source pipelines rather than ANVIL's router. The job then runs the queries with _search against an OpenSearch 2.19.1 container that holds the regression captures and an 1,800-event benign sample (results/backend_opensearch.json). The container runs without a restart policy, and the job fails on a restart or any java.lang.OutOfMemoryError in its log.

comparison compared agree
regression captures: verdict 458 450/458, 98.3% [96.6, 99.1]
benign sample: identical matching event set per rule 2,836 2,835/2,836, 99.96% [99.80, 99.99]
benign sample, only rules that fire in either engine 3 2/3

All 8 regression disagreements are captures that ANVIL and SigmaHQ's expected counts say should match, but OpenSearch returns nothing: 7 use |re (Lucene regexes must match the whole term, whereas Sigma regexes are unanchored searches, so the converted query is stricter than the rule) and 1 uses |cidr on a field indexed as keyword. In 1.1.0 there were 26: the 17 PowerShell script-block captures then counted as undiagnosed held ScriptBlockText values of 11,890-19,086 characters, which the old 10,922-character ignore_above mapping never indexed; values are now indexed up to Lucene's 32,766-byte term limit.

On benign data the two engines differ on one rule (OpenSearch 4 hits, ANVIL 0). The 99.96% is dominated by rules that fire in neither engine: only 3 rules fire at all, and 2 of them agree exactly (Cohen's kappa 0.80, bootstrap 95% CI [0.00, 1.00], i.e. undetermined with 3 firing rules). 19 rules raise HTTP 400 query errors, all recorded with their cause in results/backend_opensearch.json: 14 use regex syntax Lucene rejects (\b, \n, named groups, quote characters), 3 exceed Lucene's 10,000-state regex determinization limit, and 2 are very long OR lists (npm package names, driver hashes) that fail to parse. 4 rules cannot be converted. In 1.1.0 the job's first run died with java.lang.OutOfMemoryError at a 2 GB heap: Sigma keyword searches have no field and were expanded over every mapped field. They now query one catch-all field, and the run above has no restart and no heap error.

6. Drafter, SIEM conversion, FP prediction

drafter on OTRF descriptions + transcripts fires on its own emulation fires and within budget datasets with benign FPs benign alerts
heuristic (68 datasets with a draft) 32/68, 47% [36, 59] 32 6 57
naive keywords (baseline, 75 datasets with a draft) 56/75, 75% [64, 83] 35 33 126,768
hand-written SigmaHQ, same 68 (any alert / technique) 66 / 46 - - -

Paired over all 98 datasets (a dataset without a draft counts as a miss), the keyword baseline fires more often, 56 vs 32 (discordant 34 vs 10, exact McNemar p = 0.0004). After the budget gate the two cannot be told apart, 35 vs 32 of 98 (discordant 22 vs 19, p = 0.76), while the keyword drafts cost 126,768 benign alerts. On the 58 datasets where both drafted: 39 vs 22 (p = 1.5e-5) and 26 vs 22 within budget (p = 0.52). That trade-off is why the human review gate exists. The LLM backend is implemented but not benchmarked (see Limitations).

pySigma conversion of the 2,861 Windows rules succeeds for 2,857 (99.9% [99.6, 99.9]) on Splunk, 2,855 (99.8% [99.5, 99.9]) on Elastic, 2,842 (99.3% [99.0, 99.6]) on SQLite and 2,074 (72.5% [70.8, 74.1]) on Microsoft XDR KQL. For KQL, 632 rules have a log source with no XDR table and 119 use fields the pipeline cannot map.

FP prediction from static rule features (75 noisy rules of 2,789), seed-7 cross-validation with bootstrap 95% CIs over rules: logistic regression ROC-AUC 0.831 [0.776, 0.881], PR-AUC 0.178 [0.122, 0.265]; GBDT ROC-AUC 0.824 [0.768, 0.871], PR-AUC 0.209 [0.135, 0.304]. A one-line level heuristic scores ROC-AUC 0.853 [0.808, 0.894] and PR-AUC 0.172 [0.118, 0.243]. (Means over 10 CV seeds: logreg 0.829 / 0.183, GBDT 0.830 / 0.231.) The paired bootstrap puts the PR-AUC difference against the heuristic at [-0.051, 0.083] for logreg and [-0.037, 0.120] for GBDT. With 75 positives, no scorer can be told apart from the heuristic. Treat the models as a review-order aid only.

fp model

Comparison with reference and published numbers

Each row states whether the comparison is like-for-like.

reference their result ANVIL on the same input like-for-like?
SigmaHQ regression CI at 07ec293 (evtx-sigma-checker + SigmaHQ's json_matcher v0.0.2, run as json_checker), run 36187990502 all cases pass (green) 463/463 yes: same captures and expected counts
SigmaHQ goodlog CI on evtx-baseline win10, win11 and 2022 DC, non-low rules, run 36187990423 0 unexcused rules (green) 8 non-low rules fire without a known-FPs.csv entry (2 medium, 6 informational) partly: same images and rules, but ANVIL excuses per rule id, which is more lenient than SigmaHQ's MatchString filters, so the 8 are a lower bound on rules where ANVIL fires and evtx-sigma-checker does not
GAUNTLET v1.1.0 (sister project, tag cd41fa8), OTRF recordings detected at technique-family level sigma-all (SigmaHQ release package r2026-07-01, 2,519 rules): 67/98, 68.4% [58.6, 76.7]; sigma-full (adds low-level and threat-hunting rules, 2,805): 73/98, 74.5% [65.0, 82.1] lenient column: 69/98, 70.4% [60.7, 78.5] (strict 62/98) partly: the same 98 recordings and the same family-level match (ANVIL's lenient column), but different rule sets (release package r2026-07-01 vs repo commit 07ec293, 2,844 routable Windows rules, all levels). GAUNTLET v1.0.0 reported 67/96 (69.8%) before two AV-blocked recordings were added
Decay / breakage prediction for detection rules no published benchmark found Section 1 n/a
LLM Sigma generation from CTI: AutoSigma (arXiv 2608.19011), CTI-REALM (arXiv 2603.13517) rule validity and coverage on cloud blogs / agent tasks drafts scored by execution on their own emulation no: different corpora and metrics
AMIDES (Uetz et al., USENIX Security 2024, arXiv 2311.10197) evasion of process-creation rules - no: it measures adversarial evasion, not benign volume or recall

Full summary

Every table generated from results/*.json (results/SUMMARY.md)

Generated by python benchmarks/bench.py report from results/*.json. All result files come from bench run 37093721154 at commit 9906b42. Proportions are k/n, p% [95% Wilson CI].

SigmaHQ corpus lint

rules windows compiled unsupported with regression test test/stable w/o regression test rules with lint errors
3757 2861 3755 2 460 3029 0
finding count
W209 3029
W205 2826
W203 468
W206 46
W204 27
W213 3
W210 2

Aggregations and correlations at the pinned commit: unsupported/ holds 87 rules, 45 with count() and 54 with any aggregation in the condition; deprecated/ has 1 count() rule; the rule folders have none; 0 correlation: documents in total.

TP replay (recall) on SigmaHQ regression captures

TP-only check, and the development acceptance set: over-matching is detectable in only a few captures.

463 regression cases (499 real events); 0 samples unreadable.

engine detected missed could not evaluate detection rate
ANVIL engine (1.x) 463 0 0 463/463, 100.0% [99.2, 100.0]
ANVIL 0.1 (baseline) 420 14 29 420/463, 90.7% [87.7, 93.0]
pySigma -> SQLite 461 1 1 461/463, 99.6% [98.4, 99.9]

ANVIL vs pySigma->SQLite: agree on 461 cases, disagree on 1.

  • Local Network Connection Initiated By Script Interpreter: ANVIL 1, SQLite 0, expected 1

Benign sample (1800 events): ANVIL 1.x raised 15 alerts from 2 rules with 44,084 rule evaluations; the same 1.x engine with routing disabled raised 163 alerts from 16 rules with 5,119,200 evaluations; the 0.1 engine (no routing, older value semantics) raised 163 alerts from 16 rules with 5,149,800 evaluations.

Benign replay (false positives)

2,994,137 events, 2.55 host-days of clean Windows 10/11/Server 2022 AD telemetry (install-day recordings); 2789 of 2844 Windows rules have their log source present. Budget: 20 alerts per host-day per rule (10% of a 200/day SOC).

observable rules firing alerts rules over budget (1 host) events / CPU-second
75/2789, 2.7% [2.2, 3.4] 13249 12 3720

Rates are alerts per host-day. Rules over the 20/day budget when the per-host rate is projected to a fleet:

hosts rules over budget
1 12
3 26
10 41
100 75
1000 75
status observable fired over budget
experimental 217 5/217, 2.3% [1.0, 5.3] 1
stable 73 4/73, 5.5% [2.2, 13.3] 0
test 2499 66/2499, 2.6% [2.1, 3.3] 11
folder observable fired over budget
core 2353 53/2353, 2.3% [1.7, 2.9] 5
emerging-threats 316 2/316, 0.6% [0.2, 2.3] 1
threat-hunting 120 20/120, 16.7% [11.1, 24.3] 6
level observable fired over budget
critical 128 0/128, 0.0% [0.0, 2.9] 0
high 1353 5/1353, 0.4% [0.2, 0.9] 0
informational 13 6/13, 46.2% [23.2, 70.9] 2
low 199 40/199, 20.1% [15.1, 26.2] 9
medium 1096 24/1096, 2.2% [1.5, 3.2] 1

Cross-check with SigmaHQ's known-FPs.csv (per rule id, MatchString filters not applied; the list was also used during development): 27/29, 93.1% [78.0, 98.1] of the medium+ rules ANVIL saw firing are on the list.

SigmaHQ's goodlog CI is green on the same images, so these 8 non-low rules that fire in ANVIL but are not excused by the list are rules where ANVIL fires and the reference checker does not (a lower bound: per-id excusing is more lenient than MatchString filtering):

  • User Logoff Event (informational, 35 hits)
  • Windows Update Error (informational, 3 hits)
  • New PowerShell Instance Created (informational, 19 hits)
  • Suspicious Non PowerShell WSMAN COM Provider (medium, 11 hits)
  • Suspicious High IntegrityLevel Conhost Legacy Option (informational, 128 hits)
  • New Application in AppCompat (informational, 141 hits)
  • Windows Spooler Service Suspicious Binary Load (informational, 19 hits)
  • Uncommon PowerShell Hosts (medium, 11 hits)

Reverse direction: 32 of 68 listed rules fire here (the list covers 7 images, ANVIL replays 3).

Top noisy rules:

rule level status folder alerts per day gate
Scheduled Task Created - Registry low test threat-hunting 5513 2161.11 fail
Shell Context Menu Command Tampering low test threat-hunting 4282 1678.56 fail
EVTX Created In Uncommon Location medium test core 876 343.39 fail
System Drawing DLL Load low test threat-hunting 608 238.34 fail
Successful MSIX/AppX Package Installation low experimental threat-hunting 511 200.31 fail
Potential Raspberry Robin Registry Set Internet Settings ZoneMap low test emerging-threats 200 78.4 fail
New Application in AppCompat informational test core 141 55.27 fail
Suspicious High IntegrityLevel Conhost Legacy Option informational test core 128 50.18 fail
Modification of IE Registry Settings low test core 119 46.65 fail
Scheduled Task Created - FileCreation low test threat-hunting 82 32.14 fail
Potential Defense Evasion Via Raw Disk Access By Uncommon Tools low test core 72 28.22 fail
Firewall Rule Modified In The Windows Firewall Exception List low test threat-hunting 62 24.3 fail
BITS Client BitsProxy DLL Loaded By Uncommon Process low experimental threat-hunting 49 19.21 pass
Potential Executable Run Itself As Sacrificial Process low experimental threat-hunting 43 16.86 pass
Signed DLL Loaded With Missing PE Version Metadata low experimental threat-hunting 41 16.07 pass

Emulated attacks (OTRF Security-Datasets)

Technique detected = a rule tagged with the dataset technique or its parent fired (sibling sub-techniques do not count; they are credited only in the lenient column).

datasets events any alert technique claimed by a rule technique detected lenient (siblings credited)
98 754,764 95/98, 97% [91, 99] 90 62/98, 63% [53, 72] 69/98, 70% [61, 79]
APT29 evaluation capture events rules fired alerts distinct technique tags on fired rules
syslog_auoms_auditd_log4shell_cve2021_44228_jndi_reference.zip 50 0 0 0
syslog_sysmon_log4shell_cve2021_44228_jndi_reference.zip 93 4 5 4
apt29_evals_day1_manual.zip 196,081 111 3765 69
apt29_evals_day2_manual.zip 587,286 117 4723 70

ATT&CK coverage (Enterprise 19.2, Windows)

view techniques tagged validated parent techniques validated
claimed 474 299/474, 63.1% [58.6, 67.3] 299/474, 63.1% [58.6, 67.3] 137/176
tp_validated 474 299/474, 63.1% [58.6, 67.3] 164/474, 34.6% [30.5, 39.0] 89/176
tp_validated_and_gated 474 299/474, 63.1% [58.6, 67.3] 164/474, 34.6% [30.5, 39.0] 89/176

Decay monitor under telemetry changes

Field inventory: benign evtx-baseline only (no TP captures). False-alarm floor on unchanged telemetry: 183 rules (6.4%) are already broken/source-missing and are excluded; 3 of them are TP-validated.

  • WMI Event Subscription (source-missing; missing -)
  • Important Scheduled Task Deleted/Disabled (broken; missing TaskName)
  • RedSun - TieringEngineService.exe Detected as EICAR Test File (broken; missing Path, ProcessName, SourceName, ThreatName)

Simulated changes (ground truth: the change applied to each TP-validated rule's regression captures; the same transform builds the post-change benign inventory):

change TP rules that stop firing method recall precision library flagged
ecs_rename 430 symbolic (ANVIL) 426/430, 99.1% [97.6, 99.6] 426/426, 100.0% [99.1, 100.0] 2285
ecs_rename 430 field presence, per source 430/430, 100.0% [99.1, 100.0] 430/435, 98.9% [97.3, 99.5] 2365
ecs_rename 430 field presence, global 430/430, 100.0% [99.1, 100.0] 430/435, 98.9% [97.3, 99.5] 2396
sysmon_to_4688 115 symbolic (ANVIL) 109/115, 94.8% [89.1, 97.6] 109/109, 100.0% [96.6, 100.0] 925
sysmon_to_4688 115 field presence, per source 114/115, 99.1% [95.2, 99.8] 114/272, 41.9% [36.2, 47.8] 1593
sysmon_to_4688 115 field presence, global 109/115, 94.8% [89.1, 97.6] 109/266, 41.0% [35.2, 47.0] 1547
4688_no_cmdline 349 symbolic (ANVIL) 333/349, 95.4% [92.7, 97.2] 333/333, 100.0% [98.9, 100.0] 1865
4688_no_cmdline 349 field presence, per source 347/349, 99.4% [97.9, 99.8] 347/369, 94.0% [91.1, 96.0] 2090
4688_no_cmdline 349 field presence, global 342/349, 98.0% [95.9, 99.0] 342/363, 94.2% [91.3, 96.2] 2061
4688_no_cmdline_mixed 349 symbolic (ANVIL) 109/349, 31.2% [26.6, 36.3] 109/109, 100.0% [96.6, 100.0] 925
4688_no_cmdline_mixed 349 field presence, per source 252/349, 72.2% [67.3, 76.6] 252/272, 92.6% [88.9, 95.2] 1593
4688_no_cmdline_mixed 349 field presence, global 247/349, 70.8% [65.8, 75.3] 247/266, 92.9% [89.1, 95.4] 1547
no_commandline 234 symbolic (ANVIL) 220/234, 94.0% [90.2, 96.4] 220/220, 100.0% [98.3, 100.0] 949
no_commandline 234 field presence, per source 233/234, 99.6% [97.6, 99.9] 233/246, 94.7% [91.2, 96.9] 1107
no_commandline 234 field presence, global 233/234, 99.6% [97.6, 99.9] 233/246, 94.7% [91.2, 96.9] 1118
no_hashes 0 symbolic (ANVIL) - - 6
no_hashes 0 field presence, per source - 0/7, 0.0% [0.0, 35.4] 47
no_hashes 0 field presence, global - 0/7, 0.0% [0.0, 35.4] 54

Exact McNemar tests, symbolic vs each presence check on the same rules (discordant counts symbolic-only / baseline-only):

change vs false alarms on unbroken TP rules flags on broken TP rules
ecs_rename field presence, per source 0 / 5 of 30, p = 0.062 0 / 4 of 430, p = 0.12
ecs_rename field presence, global 0 / 5 of 30, p = 0.062 0 / 4 of 430, p = 0.12
sysmon_to_4688 field presence, per source 0 / 158 of 345, p = 5.5e-48 0 / 5 of 115, p = 0.062
sysmon_to_4688 field presence, global 0 / 157 of 345, p = 1.1e-47 5 / 5 of 115, p = 1.00
4688_no_cmdline field presence, per source 0 / 22 of 111, p = 4.8e-07 0 / 14 of 349, p = 1.2e-04
4688_no_cmdline field presence, global 0 / 21 of 111, p = 9.5e-07 5 / 14 of 349, p = 0.064
4688_no_cmdline_mixed field presence, per source 0 / 20 of 111, p = 1.9e-06 0 / 143 of 349, p = 1.8e-43
4688_no_cmdline_mixed field presence, global 0 / 19 of 111, p = 3.8e-06 5 / 143 of 349, p = 3.2e-36
no_commandline field presence, per source 0 / 13 of 226, p = 2.4e-04 0 / 13 of 234, p = 2.4e-04
no_commandline field presence, global 0 / 13 of 226, p = 2.4e-04 0 / 13 of 234, p = 2.4e-04
no_hashes field presence, per source 0 / 7 of 460, p = 0.016 0 / 0 of 0, p = 1.00
no_hashes field presence, global 0 / 7 of 460, p = 0.016 0 / 0 of 0, p = 1.00

Real Sysmon removal (no simulator): 88 OTRF captures that log both Sysmon EID 1 (1,434 events) and native Security 4688 (1,869). 286 Windows rules fire on them; 122 fire on none once the Sysmon channel is dropped. Predictions come from the benign inventory:

inventory method recall precision false alarms
native 4688 symbolic (ANVIL) 114/122, 93.4% [87.6, 96.6] 114/114, 100.0% [96.7, 100.0] 0
native 4688 field presence, per source 119/122, 97.5% [93.0, 99.2] 119/182, 65.4% [58.2, 71.9] 63
native 4688 field presence, global 115/122, 94.3% [88.6, 97.2] 115/177, 65.0% [57.7, 71.6] 62
simulated symbolic (ANVIL) 114/122, 93.4% [87.6, 96.6] 114/114, 100.0% [96.7, 100.0] 0
simulated field presence, per source 119/122, 97.5% [93.0, 99.2] 119/182, 65.4% [58.2, 71.9] 63
simulated field presence, global 115/122, 94.3% [88.6, 97.2] 115/177, 65.0% [57.7, 71.6] 62

Both inventories flag exactly the same rules: the benign hosts' native 4688 events carry the same per-source field set as the simulated conversion, so here the simulator and the real inventory agree; only the ground truth differs from the simulated rows above.

inventory symbolic vs false alarms (symbolic-only / baseline-only) flags on broken rules (symbolic-only / baseline-only)
native 4688 field presence, per source 0 / 63, p = 2.2e-19 0 / 5, p = 0.062
native 4688 field presence, global 0 / 62, p = 4.3e-19 5 / 6, p = 1.00
simulated field presence, per source 0 / 63, p = 2.2e-19 0 / 5, p = 0.062
simulated field presence, global 0 / 62, p = 4.3e-19 5 / 6, p = 1.00

native 4688: missed e.g. WMI Event Subscription; Security Event Logging Disabled via MiniNt Registry Key - Process; ADS Zone.Identifier Deleted; UNC2452 Process Creation Patterns; false alarms e.g. -.

simulated: missed e.g. WMI Event Subscription; Security Event Logging Disabled via MiniNt Registry Key - Process; ADS Zone.Identifier Deleted; UNC2452 Process Creation Patterns; false alarms e.g. -.

SIEM conversion (pySigma)

target converted (of Windows rules) seconds errors
splunk 2857/2861, 99.9% [99.6, 99.9] 5.4 SigmaFeatureNotSupportedByBackendError: 2; SigmaPlaceholderError: 2
elastic 2855/2861, 99.8% [99.5, 99.9] 6.0 SigmaFeatureNotSupportedByBackendError: 4; SigmaPlaceholderError: 2
kusto 2074/2861, 72.5% [70.8, 74.1] 6.3 SigmaTransformationError (log source has no table): 632; SigmaTransformationError (field cannot be mapped): 119; InvalidHashAlgorithmError: 28; : 5; NotImplementedError: 2; SigmaValueError: 1
sqlite 2842/2861, 99.3% [99.0, 99.6] 6.4 SigmaFeatureNotSupportedByBackendError: 17; SigmaPlaceholderError: 2

FP-prediction model

2789 observable rules, 75 noisy (rule fires at least once on the benign evtx-baseline corpus (observable rules only)). Point estimates from one CV run (seed 7); precision@k and recall use expected values under random tie-breaking.

scorer ROC-AUC PR-AUC precision at 75 recall @ top 10%
logreg 0.831 0.178 0.253 0.573
gbdt 0.824 0.209 0.28 0.533
heuristic_level 0.853 0.172 0.246 0.633
heuristic_hunting 0.852 0.173 0.242 0.641
heuristic_no_filter 0.524 0.107 0.202 0.394
random 0.5 0.03 0.053 0.12

Repeated 5-fold CV over 10 seeds (mean [interval over CV repetitions only, not over rules]):

scorer ROC-AUC PR-AUC precision at 75
logreg 0.829 [0.819, 0.838] 0.183 [0.172, 0.194] 0.259 [0.237, 0.280]
gbdt 0.830 [0.820, 0.840] 0.231 [0.218, 0.244] 0.299 [0.279, 0.318]
heuristic_level 0.853 [0.853, 0.853] 0.172 [0.172, 0.172] 0.246 [0.246, 0.246]
heuristic_hunting 0.852 [0.852, 0.852] 0.173 [0.173, 0.173] 0.242 [0.242, 0.242]
heuristic_no_filter 0.524 [0.524, 0.524] 0.107 [0.107, 0.107] 0.202 [0.202, 0.202]
random 0.493 [0.469, 0.516] 0.029 [0.027, 0.032] 0.035 [0.022, 0.047]

Bootstrap over rules (1000 resamples, seed-7 out-of-fold scores), paired against heuristic_level. The last column counts resamples with a PR-AUC difference <= 0 (a one-sided bootstrap tail; 1000/1000 means p > 0.999 for 'better than the reference').

scorer ROC-AUC 95% CI PR-AUC 95% CI dPR-AUC vs ref 95% CI resamples with d <= 0
logreg [0.776, 0.881] [0.122, 0.265] [-0.051, 0.083] 377/1000
gbdt [0.768, 0.871] [0.135, 0.304] [-0.037, 0.120] 146/1000
heuristic_level [0.808, 0.894] [0.118, 0.243] - -
heuristic_hunting [0.807, 0.894] [0.116, 0.253] [-0.056, 0.059] 501/1000
heuristic_no_filter [0.426, 0.625] [0.061, 0.180] [-0.102, -0.030] 1000/1000
random [0.427, 0.565] [0.022, 0.049] [-0.210, -0.085] 1000/1000

Drafter: CTI text -> rule -> tested

backend drafts datasets with a draft (readable) fires on its emulation fires and within budget datasets with benign FPs benign alerts
heuristic 90 68 32/68, 47.1% [35.7, 58.8] 32 6 57
keywords 75 75 56/75, 74.7% [63.8, 83.1] 35 33 126768

SigmaHQ on the same 68 datasets: any alert in 66, technique detected in 46.

Paired comparison on the same datasets (a dataset without a draft counts as a miss), exact McNemar:

datasets outcome heuristic keywords discordant (heuristic-only / keywords-only) p
all datasets fires 32/98, 33% [24, 42] 56/98, 57% [47, 66] 10 / 34 3.9e-04
all datasets fires within budget 32/98, 33% [24, 42] 35/98, 36% [27, 46] 19 / 22 0.76
datasets where both backends drafted fires 22/58, 38% [27, 51] 39/58, 67% [54, 78] 0 / 17 1.5e-05
datasets where both backends drafted fires within budget 22/58, 38% [27, 51] 26/58, 45% [33, 58] 9 / 13 0.52

Linux and AWS CloudTrail (SigmaHQ linux + cloud/aws rules)

278 of 305 Linux/AWS rules routed; 168 labelled captures.

source captures events technique detected any alert
auditd 65 7,158 8/65, 12% [6, 22] 19/65, 29% [20, 41]
cloudtrail 40 27,793 6/40, 15% [7, 29] 32/40, 80% [65, 90]
sysmon-linux 63 551,655 13/63, 21% [12, 32] 38/63, 60% [48, 71]
all 168 27/168, 16% [11, 22] 89/168, 53% [45, 60]

Captures that parsed to 0 events: splunk:T1068/linux_dirtyfrag

Benign command emulation recorded on the CI runner (755 events: linux-auditd 697, linux-sysmon/operational 58): technique detected for 7/8, 88% [53, 98] emulated techniques (detected T1033, T1082, T1007, T1087.001, T1057, T1016, T1083; missed T1027); 10 Linux rules fired 17 alerts.

Benign Linux telemetry recorded on the CI runner (15,480 events: linux-auditd 12,662, linux-sysmon/operational 2,818; 0.003 h; venv + pip install (exit 0) + unit tests (exit 0) + git on the runner): 6 Linux rules fired 42 alerts. The window is too short to project a daily rate, so this is a smoke test of the Linux parsers and rules on benign activity, not an FP-rate estimate.

Real backend: OpenSearch 2.19.1 vs ANVIL

Rules converted by pySigma's OpenSearch Lucene backend with pySigma's own Sysmon + Windows log-source pipelines (independent of ANVIL's router), executed with _search in an OpenSearch container in CI.

comparison agree
regression captures: verdict 450/458, 98.3% [96.6, 99.1]
regression captures: exact count 450/458, 98.3% [96.6, 99.1]
benign sample: same matching events per rule 2835/2836, 99.96% [99.80, 99.99]
benign sample: same events, rules that fire in either engine 2/3, 67% [21, 94]

Benign fire/no-fire table: {'both': 2, 'anvil_only': 0, 'opensearch_only': 1, 'neither': 2833}; Cohen's kappa 0.80 (bootstrap 95% CI [0.00, 1.00]). Disagreements by modifier: {'(plain)': 1}.

Query errors: regression 3, benign 19 ({'400 query_shard_exception': 21, '400 None': 1}); conversion errors {'SigmaFeatureNotSupportedByBackendError': 4}; values above Lucene's term limit (not indexed): {}.

rule set HTTP error type reason
63bf8794-9917-45bc-88dd-e1b5abc0ecfd regression 400 query_shard_exception failed to create query: Determinizing (copy-item|cpi) .{2,128} -destination .{1,32}\windows\(system32|syswow64) would require more than 10000 effort.
589ac73f-8e12-409c-964e-31a2f5775ae2 regression 400 query_shard_exception failed to create query: Determinizing (?i).exe[\"\']?\s+[^\"]{0,64}werfaultsecure.exe[\"\']?\s+\d{2,10} would require more than 10000 effort.
bae7c70b-8569-44e9-accf-b30073da8a5d regression 400 query_shard_exception Failed to parse query [Channel:Microsoft-Windows-Sysmon\/Operational AND (EventID:1 AND ((Image:\node.exe AND (CommandLine:(install OR \ i\ *))) AND (Com
7aaaf4b8-e47c-4295-92ee-6ed40a6f60c8 benign 400 None {"error":{"root_cause":[{"type":"query_shard_exception","reason":"Failed to parse query [Channel:Microsoft\-Windows\-Sysmon\/Operational AND (EventID:6 AND (
9ac8b09b-45de-4a07-9da1-0de8c09304a3 benign 400 query_shard_exception failed to create query: expected '"' at position 61
6bfb8fa7-b2e7-4f6c-8d9d-824e5d06ea9e benign 400 query_shard_exception failed to create query: expected '"' at position 86
c72aca44-8d52-45ad-8f81-f96c4d3c755e benign 400 query_shard_exception failed to create query: expected '"' at position 60
f3c89218-8c3d-4ba9-9974-f1d8e6a1b4a6 benign 400 query_shard_exception failed to create query: expected '"' at position 46
63bf8794-9917-45bc-88dd-e1b5abc0ecfd benign 400 query_shard_exception failed to create query: Determinizing (copy-item|cpi) .{2,128} -destination .{1,32}\windows\(system32|syswow64) would require more than 10000 effort.
779c8c12-0eb1-11eb-adc1-0242ac120002 benign 400 query_shard_exception failed to create query: expected '"' at position 62
0adfbc14-0ed1-11eb-adc1-0242ac120002 benign 400 query_shard_exception failed to create query: expected '"' at position 86
86b896ba-ffa1-4fea-83e3-ee28a4c915c7 benign 400 query_shard_exception failed to create query: expected '"' at position 59
e54f5149-6ba3-49cf-b153-070d24679126 benign 400 query_shard_exception failed to create query: expected '"' at position 46
27aec9c9-dbb0-4939-8422-1742242471d0 benign 400 query_shard_exception failed to create query: expected ')' at position 90
9c14c9fa-1a63-4a64-8e57-d19280559490 benign 400 query_shard_exception failed to create query: expected '"' at position 63
589ac73f-8e12-409c-964e-31a2f5775ae2 benign 400 query_shard_exception failed to create query: Determinizing (?i).exe[\"\']?\s+[^\"]{0,64}werfaultsecure.exe[\"\']?\s+\d{2,10} would require more than 10000 effort.
c83bf4b5-cdf0-437c-90fa-43d734f7c476 benign 400 query_shard_exception failed to create query: expected '>' at position 7
dd8756e7-a3a0-4768-b47e-8f545d1a751c benign 400 query_shard_exception failed to create query: invalid character class \110
cb5a2333-56cf-4562-8fcb-22ba1bca728d benign 400 query_shard_exception failed to create query: invalid character class \98
56d19cb4-6414-4769-9644-1ed35ffbb148 benign 400 query_shard_exception failed to create query: invalid character class \98
bae7c70b-8569-44e9-accf-b30073da8a5d benign 400 query_shard_exception Failed to parse query [Channel:Microsoft-Windows-Sysmon\/Operational AND (EventID:1 AND ((Image:\node.exe AND (CommandLine:(install OR \ i\ *))) AND (Com
064060aa-09fb-4636-817f-020a32aa7e9e benign 400 query_shard_exception failed to create query: Determinizing (get-item|gci|get-childitem).{1,64}-path.{1,64}\(currentcontrolset\services|currentversion\policies\explorer\run|curr