H3AD-REF / GUIDES / SIGMA RULE WRITING

Sigma Rules.
One YAML, Any Backend.

A static reference for Sigma syntax and the judgment calls that go with it: what each field of a rule does, how detection blocks and conditions combine, and the habits that keep a rule portable across SIEMs instead of silently matching nothing. No conversion engine here, no live testing, just the reference. For the Windows Event IDs a Sigma rule's EventID field actually matches against, see the Windows Event ID Cheatsheet.

Rule Anatomy

Eight fields, most of them plain metadata. The two that actually decide a match are logsource and detection.

The Fields, In Order

title:
A short, specific sentence describing what fired
└── Shows up in SIEM alert lists, so it has to mean something at 2am without opening the rule
    // "Suspicious Activity" tells the analyst nothing, a good title tells them what to check first

id:
A UUID, unique to this rule, never reused
└── How dedup, rule updates, and cross-references to this exact rule work
    // changing the id turns an update into a brand new rule as far as tooling is concerned

status:
experimental | test | stable | deprecated | unsupported
└── Signals how much you trust the rule, not whether it's syntactically valid
    // most rules ship as experimental and get promoted after they survive real traffic

logsource:
category / product / service
├── Tells the backend which log source this rule targets, e.g. process_creation on windows
└── The wrong combination here means the rule compiles fine and matches nothing, ever
    // this is the field most pitfalls in this guide trace back to

detection: required
selection blocks + condition
├── One or more named blocks of field: value pairs, plus a condition that combines them
└── The only section that defines actual matching logic
    // everything above this line is context, everything here is the rule itself

level:
informational | low | medium | high | critical
└── Severity as the rule author sees it, feeds triage priority downstream
    // level is a judgment call, not a technical property of the query

tags:
attack.execution, attack.t1059.001, ...
└── Maps the rule to ATT&CK tactics and techniques for coverage tracking
    // a rule with no ATT&CK tag is invisible to any heat-map built from your ruleset

falsepositives:
A list of legitimate activity that can trigger this rule
└── Sets the analyst's expectation before the first alert ever fires
    // "Unknown" is a valid answer only after you've actually looked, not a default

Detection Block Syntax

Selection blocks are field: value maps. Modifiers change how a value matches, list values change what counts as a match.

DETECTION

Field Matching

The baseline behavior before any modifier is added
A bare field: value pair is an exact match against the field's normalized value. Every field inside a selection block is combined with AND by default, so Image: powershell.exe and CommandLine: -enc in the same block both have to hold for that block to match.
DETECTION

Value Modifiers

Appended to the field name with a pipe, change the comparison itself
  • contains — value appears anywhere in the field
  • startswith / endswith — value anchors to one end of the field
  • re — value is a regular expression, not a literal
  • cased — forces case-sensitive comparison, off by default
  • all — every value in a list must match, not just one
  • base64offset — matches the value against all three base64 byte-alignments
  • windash — matches command-line switches across -, /, and en-dash variants
DETECTION

List Values Are OR

The one place Sigma's default logic flips from AND to OR
A YAML list under a single field is an OR across its entries, not an AND. CommandLine|contains: ['-enc', '-EncodedCommand'] matches if either string appears. Add the all modifier if the intent is actually "every value in this list must be present."

Condition Logic

condition: wires the named selection blocks together. Correlation rules extend that logic across multiple base rules over time.

CONDITION

Selection And Not Filter

The standard shape for a rule with a known exclusion
Define the positive match as selection and the exclusion as a separate filter block, then write condition: selection and not filter. The two blocks stay independently readable instead of one tangled negated expression.
CONDITION

Wildcard Block References

Group related selection blocks without naming each one in the condition
  • 1 of selection* — at least one block whose name starts with selection matched
  • all of filter* — every block whose name starts with filter matched
  • all of them — every selection block defined in detection: matched
CONDITION

Correlation Rules

A newer Sigma rule type that reasons across other rules, not just events
A correlation rule references one or more base detection rules by id and adds logic on top: event_count for how many times a rule fired, value_count for how many distinct values of a field appeared, or temporal to require several rules to fire within a shared window. It turns "this fired once" into "this fired five times from the same host in ten minutes."

Best Practices

The difference between a rule that ports cleanly to any backend and one that only ever worked on the SIEM it was written against.

PRACTICE

Category And Product Together

Product alone leaves the backend guessing which log to query
Set both category and product under logsource, for example process_creation plus windows, instead of product alone. Most backends map category to a specific table or index; without it, the converted query either targets the wrong source or nothing at all.
PRACTICE

Filter Blocks Over Inline Negation

Exclusions belong in their own named block
Write exclusions as a separate filter selection and reference it as not filter in the condition, rather than piling negated field checks into the main selection block. It keeps the positive match and the exclusion list independently editable.
PRACTICE

Tag Every ATT&CK Technique

Coverage tracking only works if every rule reports in
Add the specific technique tag, like attack.t1059.001, not just the tactic. A rule tagged only attack.execution tells a coverage matrix almost nothing about which sub-techniques your detections actually reach.
PRACTICE

Falsepositives, Not A Formality

An honest list here saves the first analyst who triages a hit
List the specific legitimate activity that can trigger the rule, based on what you actually saw while testing it, not a placeholder line copied from another rule. A vague or empty falsepositives field just moves that discovery work onto whoever handles the first alert.

Common Pitfalls

Small mistakes that pass a syntax check and only show up once the rule is deployed against real traffic.

PITFALL

Assuming Case Sensitivity

Sigma matches case-insensitively unless told otherwise
Field values match case-insensitively by default. A value written as PowerShell.exe still matches powershell.exe in the log. Add |cased to the field if the rule genuinely needs an exact-case comparison, otherwise the default behavior is doing more matching than the YAML implies.
PITFALL

Trusting The YAML, Not The Conversion

The same rule can behave differently per backend
Wildcard and regex support varies across SIEM backends, and the converter has to translate Sigma's syntax into whatever that target actually supports. Always run the converted query against the destination SIEM before trusting a rule, the YAML passing validation says nothing about what it compiles into.
PITFALL

Silent Logsource Mismatch

The most common way a Sigma rule ships and never fires
A logsource.product or category that doesn't match what the backend expects fails with zero matches and no error. The rule parses, converts, and deploys cleanly, it just never sees the log source it was meant to. Verify the logsource mapping against the target backend's field mapping config before assuming a quiet rule is a clean rule.

Full Example

Every field from the sections above, in one rule, doing real work.

Suspicious PowerShell EncodedCommand Execution

title: Suspicious PowerShell EncodedCommand Execution
id: 5f2e7f3a-3c1e-4b8a-9f2d-1a2b3c4d5e6f
status: experimental
logsource:
  category: process_creation
  product: windows
detection:
  selection:
    Image|endswith: '\powershell.exe'
    CommandLine|contains:
      - '-enc'
      - '-EncodedCommand'
  condition: selection
level: medium
tags:
  - attack.execution
  - attack.t1059.001
falsepositives:
  - Legitimate admin scripts

// Image|endswith anchors on the binary regardless of install path, CommandLine|contains
// treats the two encoded-command flags as an OR since they're a list under one field.
// category + product together is what lets this convert cleanly to more than one backend.