Lab
Wazuh Rule Builder
Write and test Wazuh custom rules and decoders against sample log lines in the browser.
Wazuh Rule Builder Playground
Write Wazuh custom decoders and rules, then test them against log lines — entirely in the browser. No server needed. A simplified decoder/rule matching engine runs in TypeScript.
How it works
- 1. Paste or load sample log lines
-
2. Write a
<decoder>to extract fields -
3. Write a
<rule>to match patterns - 4. Click Test Rules to see results
What you'll learn
- Wazuh decoder XML syntax
- Rule matching, chaining, and frequency
- Field extraction with regex groups
- MITRE ATT&CK tagging
Keyboard shortcuts
Quick-start: Decoder template (copy & paste)
A decoder extracts structured fields from raw log lines. Wazuh processes decoders in order — the first match wins. Copy this template into the Decoder XML editor and modify it for your log format.
<decoder name="my-sshd-decoder">
<!-- Match only logs from this program (syslog program field) -->
<program_name>sshd</program_name>
<!-- Pre-filter: line must contain this pattern to continue -->
<prematch>Failed password</prematch>
<!-- Capture groups extract fields. Each (\S+) becomes a field. -->
<regex>Failed password for (\S+) from (\S+) port (\S+)</regex>
<!-- Field names map 1:1 to capture groups above -->
<order>dstuser,srcip,srcport</order>
</decoder> How decoding works
- 1. The engine auto-extracts the
program_namefrom syslog headers (hostname program[pid]:). - 2. If the decoder has
<program_name>, it must match the extracted program. - 3. If
<prematch>is set, the full log line must match that regex. - 4.
<regex>runs against the full log line. Each(\S+)capture group maps to the corresponding field name in<order>. - 5. First matching decoder wins — order matters.
Quick-start: Rule template (copy & paste)
A rule evaluates decoded log lines and triggers alerts at a severity level. Copy this template into the Rule XML editor.
<!-- Basic rule: fires on any line decoded by "my-sshd-decoder" -->
<rule id="100001" level="5">
<decoded_as>my-sshd-decoder</decoded_as>
<match>Failed password</match>
<description>SSH failed password attempt</description>
</rule>
<!-- Frequency rule: fires when the parent rule fires 4+ times -->
<rule id="100002" level="10">
<if_sid>100001</if_sid>
<frequency>4</frequency>
<timeframe>120</timeframe>
<same_source_ip />
<description>SSH brute force detected</description>
<mitre>
<id>T1110</id>
<tactic>Credential Access</tactic>
</mitre>
</rule> How rules work
- 1.
id(required) — unique rule identifier. Use 100000+ for custom rules. - 2.
level(required) — alert severity: 0 = no alert, 1-4 = low, 5-7 = medium, 8-11 = high, 12+ = critical. - 3.
decoded_as— only fire if this decoder matched the log line. - 4.
match— substring match against the raw log line. - 5.
regex— regex match against the raw log line. - 6.
if_sid— chaining: this rule only fires if the parent rule fired on the same log line. - 7.
frequency+timeframe— fire when parent+child conditions occur N times inside the timeframe. Use<same_source_ip />to count per source IP.
Chained & frequency rule template (copy & paste)
Rule chaining is Wazuh's most powerful feature — it lets you build multi-stage
detections. A child rule with <if_sid> only fires
if its parent also fired on the same event. Add <frequency> to detect repeated patterns like brute force.
<!-- STEP 1: Base decoder for sudo logs -->
<decoder name="sudo-cmd">
<program_name>sudo</program_name>
<regex>(\S+) : TTY=\S+ ; PWD=\S+ ; USER=(\S+) ; COMMAND=(.*)</regex>
<order>srcuser,dstuser,command</order>
</decoder>
<!-- STEP 2: Base rule — detects any sudo command -->
<rule id="100010" level="3">
<decoded_as>sudo-cmd</decoded_as>
<description>Sudo command executed</description>
</rule>
<!-- STEP 3: Chained rule — only fires if 100010 fired AND -->
<!-- the log matches a dangerous command pattern -->
<rule id="100011" level="12">
<if_sid>100010</if_sid>
<regex>COMMAND=(/bin/bash|/bin/sh|/usr/bin/passwd)</regex>
<description>Suspicious sudo command — possible privilege escalation</description>
<mitre>
<id>T1548.003</id>
<tactic>Privilege Escalation</tactic>
</mitre>
</rule> Supported XML elements reference
Decoder elements
program_name Regex match on the syslog program name field. prematch Regex pre-filter. Line must match before extraction runs. regex Capture-group regex. Each () group maps to an order field. order Comma-separated field names: srcip,dstuser,srcport Rule elements
decoded_as Only fire if this decoder matched. match Substring match against raw log line. regex Regex match against raw log line. srcip Match on extracted srcip field. program_name Regex match on program name (in rule context). if_sid Parent rule ID — this rule only fires if the parent also fired on the
same log line. frequency Parent must fire this many times for this rule to trigger. timeframe Seconds window for frequency counting. same_source_ip Self-closing tag. Group frequency count by source IP. description Human-readable alert description. mitre Block with <id> and <tactic> for ATT&CK
mapping. Common field names for <order>
srcip dstip srcport dstport srcuser dstuser protocol action status url data command Regex cheat sheet for Wazuh
Common patterns
\S+ One or more non-whitespace chars \d+ One or more digits .* Any characters (greedy) (\S+) Capture group — extracts a field \. Literal dot (escape special chars) (a|b|c) Alternation — match a, b, or c Wazuh-specific tips
- Wazuh uses standard regex — the same syntax works here.
-
Each
()capture group in<regex>maps to the corresponding field name in<order>, left to right. -
Use
\S+instead of.*when you want to stop at whitespace — it's more precise. -
Use
(.*)at the end of a pattern to capture everything remaining. -
IP addresses:
\d+\.\d+\.\d+\.\d+or just(\S+)if the field is space-delimited. -
Escape special regex chars:
. * + ? [ ] ( ) | \ ^
Worked example: SSH failed login detection
Walk through a complete example from raw log to firing rule. Use the "SSH Auth Logs" preset in sandbox mode to follow along.
Step 1 — Examine the log
Jun 14 15:16:01 web01 sshd[2001]: Failed password for invalid user admin from 10.0.0.50 port 22 ssh2
The syslog header gives us: timestamp = Jun 14 15:16:01, hostname = web01, program = sshd, PID = 2001. The engine auto-extracts program_name = "sshd".
Step 2 — Write the decoder
<decoder name="sshd-failed">
<program_name>sshd</program_name>
<prematch>Failed password</prematch>
<regex>Failed password for \S+ user (\S+) from (\S+) port</regex>
<order>dstuser,srcip</order>
</decoder> program_name filters to sshd logs only.
prematch pre-filters to "Failed password" lines. The
regex captures dstuser = "admin" and srcip = "10.0.0.50".
Step 3 — Write the rules
<rule id="100001" level="5">
<decoded_as>sshd-failed</decoded_as>
<description>SSH failed login attempt</description>
</rule>
<rule id="100002" level="10">
<if_sid>100001</if_sid>
<frequency>4</frequency>
<timeframe>120</timeframe>
<same_source_ip />
<description>SSH brute force — 4+ failures from same IP</description>
</rule>
Rule 100001 fires on every failed login (level 5 = medium). Rule 100002 chains off
100001 with if_sid — it fires when 100001 has triggered
4+ times from the same source IP within 120 seconds (level 10 = high).
Step 4 — Expected results
- Lines 1-2 (failed logins): Rule 100001 fires on each (level 5).
- Lines 3-4 (accepted logins): No decoder match — no rules fire.
- The brute force rule (100002) would fire if you had 4+ failed lines from the same IP.
Results
Alert level reference
Common MITRE ATT&CK IDs for rules
T1110 Brute Force T1078 Valid Accounts T1505.003 Web Shell T1548.003 Sudo / Sudo Caching T1021 Remote Services (Lateral Movement) T1059 Command and Scripting Interpreter T1190 Exploit Public-Facing Application T1562 Impair Defenses T1070 Indicator Removal (Log Tampering)
Add MITRE tags to your rules with <mitre><id>T1110</id><tactic>Credential
Access</tactic></mitre>. These show up as purple badges in the results.
Differences from production Wazuh
- Simplified engine. This playground implements a
subset of the Wazuh decoder/rule syntax — enough for learning and prototyping, but
not a full replacement for
wazuh-analysisd. - No parent decoders. In production Wazuh, decoders
can chain via
<parent>. This playground uses flat decoders only. - No CDB lists. Production Wazuh supports
<list>lookups against CDB files. Not implemented here. - No active response. This playground only evaluates detection rules — no blocking or response actions.
- Time parsing is best-effort.
<timeframe>uses parsed syslog timestamps when available. If timestamps are missing, line order is used as a fallback. - Standard regex. Wazuh uses PCRE2. This playground uses JavaScript's built-in regex engine, which covers most patterns identically.
For production rule development, test against wazuh-logtest on a running
Wazuh manager or use the Wazuh API's /logtest endpoint.
Security model
Everything runs in your browser. No log lines, decoders, or rules are sent to any server. All parsing, decoding, and rule evaluation happens in TypeScript on the client side. Nothing is persisted between page reloads.