Decoders
Decoders normalize raw events into structured documents that conform to the Wazuh Common Schema (WCS). When an event is accepted by a decoder, it applies a series of transformations to extract values, normalize fields, and map data to the appropriate schema fields.
All events enter the decoder tree through the root decoder and then move through a branch of child decoders. Each child decoder performs a more specialized layer of parsing, field extraction, and normalization, progressively refining the event until it reaches its final structured form.
The above image shows a logical decoder tree.
Custom decoders
Custom decoders are managed through the draft, test, and custom spaces on the Wazuh Security Analytics dashboard. They are organized within integrations, which serve as containers for grouping related decoders, rules, KVDBs, and supporting content.
A decoder is an asset that takes a raw event and normalizes it into the Wazuh Common Schema (WCS) using a small domain-specific language (DSL): check, parse, and normalize with map operations. Each operation in those stages is defined using literal values, field references, and helper functions. Decoders are arranged in a tree, starting from a root decoder, and each decoder can accept or reject an event based on its conditions before mapping fields.
Each decoder defines the:
checkstage: boolean conditions to decide if this decoder applies.parse|stage: parsing operations (e.g., parse JSON, key-value, CSV) on a field.normalizestage: one or more blocks that runcheck/parse|/mapin order to populate WCS fields. Themapblock is a transformation stage inside a decoder's normalize section that sets or modifies event fields (using literals, field references, or functions) without affecting whether the event is accepted.
Conceptually, a decoder asset looks like this:
name: decoder/custom-mylog/0
enabled: true
stages:
check:
- $event.original: contains("MyProduct")
parse|:
- $event.original: parse_json()
normalize:
- check:
- $some.field: exists()
map:
event.category: "network"
source.ip: $src_ip
# ...
Identifying the input field and format
Most decoders start from event.original for raw log text or JSON, or some field a previous decoder has already created (e.g., _message, _json). You decide which helper function to use based on the events format:
parse_json()for JSON logs.parse_key_value()fork=vstyle logs.parse_csv()orparse_dsv()for delimited logs.regex_extract()if you want custom regex capture into fields.
Example: Parse a JSON firewall log arriving as a string in event.original:
name: decoder/custom-firewall-json/0
enabled: true
stages:
parse|:
- _json: parse_json($event.original)
Here _json is a temporary variable used as scratch space during decoding and will later be cleaned in pre-enrichment. A scratch space refers to temporary fields (names starting with _) that decoders use to store intermediate values during event processing, which are later cleaned up before indexing. Temporary variables are any fields whose names start with _, like _raw_message or _parsed_ts. They are not part of the Wazuh Common Schema. Decoders can read and write these fields as an event moves through the decoder tree, so different decoders can share intermediate parsing results or flags.
At pre-enrichment, there is a mandatory cleanup step that removes all temporary fields, ensuring the final event contains only WCS-compliant fields for indexing.
Adding a check stage to target the right events
The check stage is used to target the right events. It uses filter helpers such as contains() and starts_with() to determine whether the decoder should process the event.
Example:
stages:
check:
- $event.original: contains("firewall")
- $event.original: starts_with("{")
parse|:
- _json: parse_json($event.original)
The above example shows that check lines use a filter helper (contains, starts_with). Both conditions must evaluate to true for the decoder to accept the event.
Normalizing parsed data into WCS fields
The normalize stage maps parsed fields to Wazuh Common Schema fields using mapping helpers, transforms, or direct references.
Example:
normalize:
- map:
event.kind: "event"
event.category: "network"
event.type: "connection"
network.transport: $_json.transport
source.ip: $_json.src_ip
source.port: to_int($_json.src_port)
destination.ip: $_json.dst_ip
destination.port: to_int($_json.dst_port)
event.outcome: $_json.action
In the above example, we use:
Direct references, such as
$_json.src_ip.Map helpers like
to_int()to change strings to integers.
Conditional normalize blocks
A normalize array can have multiple blocks, each with its own check and map, evaluated in order.
Example: treat action=ALLOW vs DENY differently:
normalize:
- check:
- $_json.action: string_equal("ALLOW")
map:
event.outcome: "success"
- check:
- $_json.action: string_equal("DENY")
map:
event.outcome: "failure"
Here, string_equal is a filter helper used as a condition inside normalize.
Practical example
The following log is a sample from a network device:
FW: conn src=10.0.0.5 src_port=34567 dst=192.168.1.10 dst_port=443 proto=tcp action=ALLOW
The following decoder YAML parses the key-value fields from the log and maps them to WCS fields:
name: decoder/custom-fw-kv/0
enabled: true
metadata:
description: "Custom firewall key=value logs"
version: "1.0.0"
stages:
check:
- $event.original: starts_with("FW:")
- $event.original: contains("conn ")
parse|:
- _kv: parse_key_value($event.original, " ", "=", true)
normalize:
- map:
event.kind: "event"
event.category: "network"
event.type: "connection"
source.ip: $_kv.src
source.port: to_int($_kv.src_port)
destination.ip: $_kv.dst
destination.port: to_int($_kv.dst_port)
network.transport: downcase($_kv.proto)
- check:
- $_kv.action: string_equal("ALLOW")
map:
event.outcome: "success"
- check:
- $_kv.action: string_equal("DENY")
map:
event.outcome: "failure"
In the above example:
parse_key_valuefrom the Transformation helpers to splitk=vpairs. In this context, "k=vpairs" just means the key=value fragments inside the log line, likesrc=10.0.0.5,src_port=34567,dst=192.168.1.10, etc.starts_with,contains, andstring_equalfrom Filter helpers.to_intanddowncasefrom Map helpers to convert types/normalize casing.Scratch field
_kvas temporary storage, later removed by the pre-enrichment cleanup.