What Is a Sigma Rule Catalog and Why Your SOC Needs One
HuntRule Team · · 9 min read

On this page
On 2023-12-15 SigmaHQ changed the rule carrying id 5cc90652-4cbd-4241-aa3b-4b462fa5a248. The change added dnsgetdc: to the flag list and stripped the leading slash from most of the other strings, so /domain_trusts became domain_trusts and started matching nltest.exe -domain_trusts too. If you exported that rule into Splunk in 2022 and never went back, your saved search still carries the 2022 logic.
That is the problem a catalog solves. It is not "where do I get rules". SigmaHQ publishes more than 3000 of them and the download costs nothing. The problem starts the moment a rule leaves the repository.
The import that never gets a second version
Most SIEM onboarding of Sigma looks like this. Clone the repo, run a conversion, paste the queries into the console.
sigma plugin install splunk
sigma convert -t splunk -p sysmon sigma/rules/windows/process_creationThat command is correct and it works. It is also a one-way door. The output is a pile of SPL with no id, no author, no related block and no record of which commit produced it. Six months later upstream has changed 200 of those rules and you have no mechanism that can tell you which 200, because the artifact in your SIEM shares no identifier with the artifact in the repo.
SigmaHQ ships rule packages precisely to make ingestion repeatable. The Core package is stable and test status rules at high and critical level. Core+ adds medium, which is where most environment-specific tuning lands. Core++ adds experimental. Picking a package fixes your noise appetite. It does not fix the upgrade path, because the package is still just a zip of YAML you have to reconcile against whatever you did to the last one.
A rule that moved after you copied it
Here is the rule from the opening, verbatim from rules/windows/process_creation/proc_creation_win_nltest_recon.yml.
title: Potential Recon Activity Via Nltest.EXE
id: 5cc90652-4cbd-4241-aa3b-4b462fa5a248
related:
- id: 410ad193-a728-4107-bc79-4419789fcbf8
type: similar
- id: 903076ff-f442-475a-b667-4f246bcc203b
type: similar
- id: 77815820-246c-47b8-9741-e0def3f57308
type: obsolete
status: test
description: Detects nltest commands that can be used for information discovery
author: Craig Young, oscd.community, Georg Lauenstein
date: 2021-07-24
modified: 2023-12-15
tags:
- attack.discovery
- attack.t1016
- attack.t1482
logsource:
category: process_creation
product: windows
detection:
selection_nltest:
- Image|endswith: '\nltest.exe'
- OriginalFileName: 'nltestrk.exe'
selection_recon:
- CommandLine|contains|all:
- 'server'
- 'query'
- CommandLine|contains:
- '/user'
- 'all_trusts'
- 'dclist:'
- 'dnsgetdc:'
- 'domain_trusts'
- 'dsgetdc:'
- 'parentdomain'
- 'trusted_domains'
condition: all of selection_*
falsepositives:
- Legitimate administration use but user and host must be investigated
level: mediumNote modified: 2023-12-15. The file has been touched since. A 2024-07-02 commit rewrote the Microsoft reference URL from the docs subdomain to learn, and a 2024-08-12 commit changed the related type from obsoletes to obsolete for Sigma spec v2. Neither bumped modified, because the spec only asks for a bump on title, detection, level, logsource or a status change to deprecated. So modified is a signal about detection semantics, not a change detector. If your sync logic keys on modified, it will miss real file changes. Hash the file or track the commit.

What a local tune actually looks like
Assume a lab environment where an inventory agent runs nltest /dclist: on a schedule and buries the analyst. The obvious move is to edit the YAML in place. Below is a reconstruction of that edit, shown as the changed hunks of local/rules/windows/process_creation/proc_creation_win_nltest_recon.yml against the upstream copy of the same path.
@@
id: 5cc90652-4cbd-4241-aa3b-4b462fa5a248
-status: test
+status: stable
-modified: 2023-12-15
+modified: 2026-05-04
@@
- 'parentdomain'
- 'trusted_domains'
- condition: all of selection_*
+ filter_main_inventory:
+ ParentImage: 'C:\Program Files\Contoso\inventory-agent.exe'
+ CommandLine|contains: '/dclist:'
+ condition: all of selection_* and not 1 of filter_*
falsepositives:
- Legitimate administration use but user and host must be investigated
-level: medium
+level: lowFour lines of YAML and the rule is now a fork wearing upstream's id. Nothing in the file records that it was forked, when, by whom, or on what evidence the level was dropped. Pull the next release and one of three things happens. Your sync overwrites the tune. Your sync skips the file and you stop getting upstream improvements forever. Or your sync raises a conflict that a human has to resolve with no context about why the local side looks like that.
The spec is explicit about the id question. A change to detection logic warrants a new id, with the origin recorded in related as type: derived. Almost nobody does this, because there is no place to put the rest of the story.
The metadata a catalog has to carry
A catalog is the place to put the rest of the story. Per rule, at minimum:
Field | Example value | What it answers |
|---|---|---|
upstream_id | 5cc90652-4cbd-4241-aa3b-4b462fa5a248 | Which SigmaHQ rule is this descended from |
upstream_ref | commit sha, path, package | Exactly which version was copied, and from where |
local_id | fresh UUIDv4 | This is our rule now, not theirs |
local_diff | filter_main_inventory added, level medium to low | What we changed, and nothing else |
tuning_reason | inventory agent runs | Why the diff exists, in one sentence |
validated_on | 2026-05-04, Atomic Red Team T1482 test 2 | Who proved it fires, against what, when |
backends | splunk (sysmon), elasticsearch (ecs_windows) | The conversion target is part of the rule |
data_source | Sysmon EID 1, or Security 4688 with command line auditing | Which log actually feeds it |
owner | detection engineering | Who answers the page |
review_due | 2026-11-04 | The drift check has a date on it |
data_source is the field teams skip and regret. This rule needs OriginalFileName, which Sysmon Event ID 1 populates and Security Event ID 4688 does not. Deploy it against 4688 only and the renamed-binary branch of selection_nltest is dead weight, and you will not know unless the mapping is written down somewhere a person reads.
validated_on is the review problem in a single row. Ask a SOC who validated a given rule, against what test, and on what date. If the answer lives in a closed ticket or a Slack thread, it does not exist. Sigma covers the basics of what a rule is, and our primer on Sigma rules walks the format. Provenance is the part the format leaves to you.
Tuning without forking
Sigma has a mechanism most teams have not adopted. A global filter is a separate YAML document that attaches an exclusion to named rule ids without touching the rule files.
title: Filter inventory agent domain enumeration
id: 0e95725d-7320-415d-80f7-004da920fc11
description: Contoso inventory agent runs nltest /dclist: hourly on all DCs
logsource:
category: process_creation
product: windows
filter:
rules:
- 5cc90652-4cbd-4241-aa3b-4b462fa5a248
selection:
ParentImage: 'C:\Program Files\Contoso\inventory-agent.exe'
condition: not selectionUpstream rule files stay byte-identical to the release, so git diff against a new package is clean and the whole tune lives in one reviewable file. The tradeoff is real. Filter support depends on your tooling version, it arrived in pySigma 0.11.7 and sigma-cli 1.0.3 in June 2024, and a filter cannot express a level change or a detection widening. Forks are still necessary sometimes. The catalog's job is to make the fork visible, not to pretend it never happens.
What the rule catches and what it misses
The rule fires when a process ends in nltest.exe, or reports nltestrk.exe as its OriginalFileName, and the command line contains both server and query, or any one of the discovery flags. That maps to T1482 and T1016.
It misses domain trust enumeration that never calls nltest. Get-ADTrust, raw LDAP from System.DirectoryServices, and any custom binary calling DsEnumerateDomainTrusts all produce the same intelligence and none of the command lines. It also misses a copy of nltest.exe with the PE version resource stripped, executed under an unrelated name, on a sensor that only gives you 4688.
Known false positives are ordinary. Domain join troubleshooting, replication health scripts, asset inventory, and helpdesk runbooks all call these flags. That is exactly why the rule ships at medium and lands in the Core+ package rather than Core.
Related detections live under Windows rules in the catalog, and discovery-stage coverage is searchable at /rules?q=discovery.
Git alone is often enough
Do not build a catalog because a blog post said so. A two-person team with one SIEM and 40 active rules gets most of the value from a repo laid out as upstream/ (a pinned SigmaHQ release, never edited), local/ (your rules and filters), and a CI job.
sigma check local/
sigma convert -t splunk -p sysmon local/ -o build/splunk.conf
git diff upstream/That gives you version history, a diff against upstream, syntax validation and reproducible builds. It does not give you review dates, per-rule ownership, validation evidence, or a query that answers "which rules cover Windows credential access and have not been reviewed this year". Those are what the extra metadata buys, and the price is that somebody has to fill the fields in and keep them honest.
The break-even point is roughly: more than one conversion backend, more than one team writing rules, or an auditor who asks questions. Below that, a git repo and a discipline of writing the tuning reason in the commit message is the correct answer, and adding a catalog first is overhead with no payer.
Summary
A Sigma rule catalog exists to answer questions the YAML cannot: which upstream rule this came from, what we changed and why, who proved it works and when, which log source feeds it, and when it gets looked at again. The raw import into a SIEM answers none of these, and the modified field is not a reliable change detector because SigmaHQ does not bump it for reference or schema edits. Global filters let you tune common noise without forking, which keeps the upstream diff clean. Small teams can get most of the benefit from a pinned upstream directory plus sigma check in CI, and should start there.
Fields to record per rule before you call it managed:
upstream_id Sigma UUID the rule descends from
upstream_ref commit sha + path + release package
local_id new UUIDv4 if detection logic changed
local_diff summary of every deviation from upstream
tuning_reason one sentence, why the diff exists
validated_on date + test used (e.g. Atomic Red Team T1482)
backends conversion targets and pipelines
data_source Sysmon EID / Windows Security EID / EDR table
owner team that answers the alert
review_due next drift and validation checkRelated articles

What Is a Detection Rule? From Alerts to Analytics
ParentImage ends with \WINWORD.EXE and Image ends with \cmd.exe. That pair, plus a statement about which log it reads and what happens when it fires, is a complete detection rule. Everything else is…
2026-07-31 · 9 min read

What Is Detection Engineering?
vssadmin.exe delete shadows /all /quiet maps to T1490, Inhibit System Recovery. Writing the Sigma rule for it takes about ten minutes. Everything after those ten minutes is detection engineering.…
2026-07-31 · 8 min read

What Are Sigma Rules? The Open Detection Standard Explained
A Sigma rule is a YAML file with three required blocks: title, logsource and detection. Everything else is metadata. That is the whole format. The point of it is that the same file compiles to a…
2026-07-31 · 7 min read