Skip to content

This guide shows you how to run Sigma rules on parsed security telemetry with the sigma operator. Use this pattern to turn Windows Event Logs and other normalized records into Sigma sightings without leaving the Tenzir pipeline.

The sigma operator transpiles rule YAML into a TQL expression. Semantically, you can think of it as applying where to the input: non-matching events are discarded, and matching events become tenzir.sigma records that include the original event and the matched rule.

At a high level, the translation process looks as follows:

Sigma Ruletranspileto TQLexecutein pipeline...wheredetection: foo: a: 42 b: "evil" bar: c: 1.2.3.4 condition: foo or not barYAMLwhere (a == 42 and b == /evil/i) or not (c == 1.2.3.4)TQLTenzir ProgramTenzir Pipeline

Detect encoded PowerShell in Windows Event Logs

Section titled “Detect encoded PowerShell in Windows Event Logs”

Windows process creation events are a good fit for Sigma because many detections are field-level predicates over Event ID 4688. Start with a rule that matches PowerShell launched with an encoded command:

rules/windows/encoded-powershell.yml
title: Encoded PowerShell Command
id: 7f01f6b8-9f1e-48f5-bab9-2d1f7040c6a1
status: experimental
description: Detects Windows process creation events where PowerShell runs an encoded command.
logsource:
product: windows
service: security
detection:
selection:
System.EventID: 4688
EventData.NewProcessName|endswith:
- '\powershell.exe'
- '\pwsh.exe'
EventData.CommandLine|contains:
- ' -enc '
- ' -EncodedCommand '
- ' -encodedcommand '
condition: selection
fields:
- System.Computer
- EventData.SubjectUserName
- EventData.NewProcessName
- EventData.CommandLine
level: high

If your collector sends native Windows Event Log XML, parse each event with fnparse_winlog and then run the rule:

from_file "windows-security.xml" {
read_delimited "</Event>\n", include_separator=true
}
this = data.parse_winlog()
sigma "rules/windows/encoded-powershell.yml"
{
event: {
System: {
Provider: {
Name: "Microsoft-Windows-Security-Auditing",
},
EventID: 4688,
TimeCreated: {
SystemTime: 2026-06-07T10:00:00Z,
},
Channel: "Security",
Computer: "WORKSTATION-17",
},
EventData: {
NewProcessName: "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
CommandLine: "powershell.exe -NoP -EncodedCommand SQBFAFgA",
ParentProcessName: "C:\\Windows\\explorer.exe",
SubjectUserName: "alice",
},
},
rule: {
title: "Encoded PowerShell Command",
id: "7f01f6b8-9f1e-48f5-bab9-2d1f7040c6a1",
status: "experimental",
description: "Detects Windows process creation events where PowerShell runs an encoded command.",
logsource: {
product: "windows",
service: "security",
},
detection: {
selection: {
"System.EventID": 4688,
"EventData.NewProcessName|endswith": [
"\\powershell.exe",
"\\pwsh.exe",
],
"EventData.CommandLine|contains": [
" -enc ",
" -EncodedCommand ",
" -encodedcommand ",
],
},
condition: "selection",
},
fields: [
"System.Computer",
"EventData.SubjectUserName",
"EventData.NewProcessName",
"EventData.CommandLine",
],
level: "high",
},
}

For a live Windows Event Collector stream, keep the same parse-and-detect shape and change only the source:

accept_tcp "0.0.0.0:1514" {
read_delimited "</Event>\n", include_separator=true
}
this = data.parse_winlog()
sigma "/etc/tenzir/sigma/windows/", refresh_interval=30s
publish "detections.sigma"

The directory form lets you add or update rules without restarting the pipeline. Every 30 seconds, Tenzir reloads the files in /etc/tenzir/sigma/windows/ and uses the refreshed rule set for subsequent events.

See Windows Event Logs for collection patterns that deliver Windows Event Log XML to Tenzir.

Many public Sigma rules use generic Windows field names such as EventID, Image, CommandLine, ParentImage, and User. You can either edit the rule to use System.* and EventData.* paths, or map the parsed Windows event to those field names before sigma:

from_file "windows-security.xml" {
read_delimited "</Event>\n", include_separator=true
}
this = data.parse_winlog()
EventID = System.EventID
Image = EventData.NewProcessName
CommandLine = EventData.CommandLine
ParentImage = EventData.ParentProcessName
User = EventData.SubjectUserName
sigma "rules/windows/encoded-powershell-standard-fields.yml"
{
event: {
System: {
Provider: {
Name: "Microsoft-Windows-Security-Auditing",
},
EventID: 4688,
TimeCreated: {
SystemTime: 2026-06-07T10:00:00Z,
},
Channel: "Security",
Computer: "WORKSTATION-17",
},
EventData: {
NewProcessName: "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
CommandLine: "powershell.exe -NoP -EncodedCommand SQBFAFgA",
ParentProcessName: "C:\\Windows\\explorer.exe",
SubjectUserName: "alice",
},
EventID: 4688,
Image: "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
CommandLine: "powershell.exe -NoP -EncodedCommand SQBFAFgA",
ParentImage: "C:\\Windows\\explorer.exe",
User: "alice",
},
rule: {
title: "Encoded PowerShell Command",
logsource: {
product: "windows",
category: "process_creation",
},
detection: {
selection: {
EventID: 4688,
"Image|endswith": [
"\\powershell.exe",
"\\pwsh.exe",
],
"CommandLine|contains": [
" -enc ",
" -EncodedCommand ",
" -encodedcommand ",
],
},
condition: "selection",
},
level: "high",
},
}

With that mapping, the corresponding rule can use the common field names:

rules/windows/encoded-powershell-standard-fields.yml
title: Encoded PowerShell Command
logsource:
product: windows
category: process_creation
detection:
selection:
EventID: 4688
Image|endswith:
- '\powershell.exe'
- '\pwsh.exe'
CommandLine|contains:
- ' -enc '
- ' -EncodedCommand '
- ' -encodedcommand '
condition: selection
level: high

This approach is useful when you import a rule set that already follows a field taxonomy. The original Windows structure remains on the event, so analysts can still inspect System and EventData after a match.

You can also implement a Sigma detection in TQL directly, without going through the sigma operator. This keeps your events in their canonical shape — useful when you’ve normalized to a schema such as OCSF and don’t want to rename fields back to Sigma’s Windows-style selectors only to satisfy a rule.

A recent upstream Sigma process-creation rule, proc_creation_win_print_dump_sensitive_files.yml (Sensitive File Dump Via Print.EXE, April 28, 2026), detects print.exe abuse for copying sensitive Windows credential stores such as SAM, SECURITY, SYSTEM, and ntds.dit.

The relevant source YAML is:

/tmp/sigma/rules/windows/process_creation/proc_creation_win_print_dump_sensitive_files.yml
title: Sensitive File Dump Via Print.EXE
id: 2fcda7e2-8c57-4904-86ac-37fc3157e09d
status: test
date: 2026-04-28
logsource:
category: process_creation
product: windows
detection:
selection_img:
- Image|endswith: '\print.exe'
- OriginalFileName: 'Print.EXE'
selection_cli:
CommandLine|contains|windash: '/D'
CommandLine|contains:
- '\config\SAM'
- '\config\SECURITY'
- '\config\SYSTEM'
- '\windows\ntds\ntds.dit'
condition: all of selection_*
falsepositives:
- Unlikely
level: high

When your source already emits OCSF Process Activity events, translate the selectors to OCSF fields:

Sigma selectorOCSF Process Activity field
Imageprocess.path or process.name
OriginalFileNameprocess.file.internal_name
CommandLineprocess.cmd_line
Host and user groupingdevice.hostname and actor.user.name

The following fixture contains two matching process launches and one benign print.exe launch. The pipeline keeps the Sigma rule’s single-event semantics and emits one detection per matching process event:

from {
time: 2026-04-28T10:01:00Z,
class_uid: 1007,
activity_id: 1,
device: {hostname: "WORKSTATION-17"},
actor: {user: {name: "alice"}},
process: {
name: "print.exe",
path: "C:\\Windows\\System32\\print.exe",
file: {internal_name: "Print.EXE"},
cmd_line: "print.exe /D:C:\\Windows\\System32\\config\\SAM C:\\Temp\\sam.bak",
},
}, {
time: 2026-04-28T10:02:00Z,
class_uid: 1007,
activity_id: 1,
device: {hostname: "WORKSTATION-17"},
actor: {user: {name: "alice"}},
process: {
name: "print.exe",
path: "C:\\Windows\\System32\\print.exe",
file: {internal_name: "Print.EXE"},
cmd_line: "print.exe /D:C:\\Windows\\System32\\config\\SYSTEM C:\\Temp\\system.bak",
},
}, {
time: 2026-04-28T10:02:30Z,
class_uid: 1007,
activity_id: 1,
device: {hostname: "WORKSTATION-17"},
actor: {user: {name: "alice"}},
process: {
name: "print.exe",
path: "C:\\Windows\\System32\\print.exe",
file: {internal_name: "Print.EXE"},
cmd_line: "print.exe C:\\Users\\alice\\report.txt",
},
}
where class_uid == 1007 and activity_id == 1
// TQL does not support case-insensitive string matching functions yet. To avoid
// the verbosity and overhead of calling `to_lower()` repeatedly (and to avoid
// mutating the event by storing lowercase fields), we can use `match_regex`
// with raw strings and the case-insensitive `(?i)` flag.
where process.path.match_regex(r"(?i)\\print\.exe$") \
or process.name.match_regex(r"(?i)^print\.exe$") \
or process.file.internal_name.match_regex(r"(?i)^print\.exe$")
where process.cmd_line.match_regex(r"(?i)[/-]d") and process.cmd_line.match_regex(
r"(?i)\\config\\(sam|security|system)|\\windows\\ntds\\ntds\.dit"
)
select time,
host=device.hostname,
user=actor.user.name,
command=process.cmd_line,
rule="Sensitive File Dump Via Print.EXE",
level="high"
{
time: 2026-04-28T10:01:00Z,
host: "WORKSTATION-17",
user: "alice",
command: "print.exe /D:C:\\Windows\\System32\\config\\SAM C:\\Temp\\sam.bak",
rule: "Sensitive File Dump Via Print.EXE",
level: "high",
}
{
time: 2026-04-28T10:02:00Z,
host: "WORKSTATION-17",
user: "alice",
command: "print.exe /D:C:\\Windows\\System32\\config\\SYSTEM C:\\Temp\\system.bak",
rule: "Sensitive File Dump Via Print.EXE",
level: "high",
}

If you package this predicate as operators/detections/print_sensitive_dump.tql in a package named windows_threats, callers can use the operator windows_threats::detections::print_sensitive_dump and add correlation separately. For example, use window after the UDO when you want one alert for repeated matches on the same host and user:

from_file "ocsf-process-events.json" {
read_ndjson
}
windows_threats::detections::print_sensitive_dump
window size=5min, on=time {
summarize host,
user,
rule,
level,
match_count=count(),
commands=distinct(command)
where match_count > 1
window_start = $window.start
window_end = $window.end
}

For the two detection records from the earlier fixture, the windowed pipeline emits one correlated result:

{
host: "WORKSTATION-17",
user: "alice",
rule: "Sensitive File Dump Via Print.EXE",
level: "high",
match_count: 2,
commands: [
"print.exe /D:C:\\Windows\\System32\\config\\SAM C:\\Temp\\sam.bak",
"print.exe /D:C:\\Windows\\System32\\config\\SYSTEM C:\\Temp\\system.bak",
],
window_start: 2026-04-28T10:00:00Z,
window_end: 2026-04-28T10:05:00Z,
}

Last updated: