# Custom Decoders Reference Detailed reference for every custom decoder in `local_decoder.xml`. Documents what each decoder matches, what fields it extracts, and why it exists. ## Table of contents - [pfsense-wrapped and children](#pfsense-wrapped-and-children) - [mailcow-journald-unwrap and child](#mailcow-journald-unwrap-and-child) - [gitea and gitea-auth-fail](#gitea-and-gitea-auth-fail) - [mailcow-dovecot-success](#mailcow-dovecot-success) - [mailcow-postfix-sasl-fail](#mailcow-postfix-sasl-fail) --- ## pfsense-wrapped and children **Purpose.** Parse pfSense filter log entries that arrive wrapped in additional syslog headers from the pfSense box. **Structure.** One parent decoder plus four children that extract different sections of the log line via chained `offset="after_regex"` extractions. ### pfsense-wrapped (parent) ```xml filterlog ``` Matches any log line containing `filterlog` (the pfSense filter daemon). ### pfsense-wrapped-fields (first child) ```xml pfsense-wrapped filterlog\S* \S*,\S*,\S*,(\S*),\S*,\S*,(\S*), id,action ``` Extracts rule ID and action (block/pass) from the CSV-like pfSense filter log. ### pfsense-wrapped-fields (second child) ```xml \S*,\S*,\S*,\S*,\S*,\S*,\S*,\S*,\S*,(\S*),\S*,(\S*),(\S*), protocol,srcip,dstip ``` Continues from where the first child left off. Extracts protocol, source IP, destination IP. ### pfsense-wrapped-fields (third child) ```xml (\d*),(\d*),\S* srcport,dstport ``` Extracts source and destination ports. ### pfsense-wrapped-fields (fourth child) ```xml datalength=(\S*)|(\d*) length ``` Extracts packet length. **Note.** All four children share the same name deliberately. Wazuh's chained extraction pattern supports multiple children with identical names when each uses `offset="after_regex"` to continue from the previous child's position. **Referenced by.** Rules 87699, 87761, 87762 (pfSense correlation chain). --- ## mailcow-journald-unwrap and child **Purpose.** Strip mailcow's outer journald syslog envelope to expose the inner Postfix log for further decoding. ```xml postfix\(\d+\): \w+ \d+ \d+:\d+:\d+ \w+ \.+\(\d+\): mailcow-journald-unwrap \.+ extra_data ``` **Status.** Present but not actively referenced by current rules. Reserved for future use if additional Postfix log types need parsing beyond what `mailcow-postfix-sasl-fail` handles. --- ## gitea and gitea-auth-fail **Purpose.** Parse Gitea log lines for authentication and account activity. ### gitea (parent) ```xml gitea ``` Matches on syslog program name `gitea`. Sets up the child decoder chain for Gitea-specific extraction. ### gitea-auth-fail (child) ```xml gitea Failed authentication attempt Failed authentication attempt for (\S+) from (\d+.\d+.\d+.\d+) user, srcip ``` Fires on Gitea failed login attempts. Extracts the attempted username and the source IP. Sample log this matches: ``` 2026/05/29 14:19:59 routers/web/auth/auth.go:309:SignInPost() [W] Failed authentication attempt for admin from 192.0.2.100 ``` **Referenced by.** Rules 100400 through 100470 (all Gitea rules). --- ## mailcow-dovecot-success **Purpose.** Extract source IP from any Dovecot event containing `rip=IP,`. Universally useful because it captures the source IP from successful logins, disconnects, port scan probes, and reconnaissance activity - all of which include the `rip=` field. ```xml dovecot rip=(\S+), srcip ``` **Design note.** Despite the name suggesting it only handles successful logins, this decoder actually populates srcip on ALL Dovecot events that include `rip=`. This was the result of iterative debugging - simpler patterns worked better than more specific ones. Consider renaming to `mailcow-dovecot-rip` in a future refactor. **Field extracted:** - `srcip` - source IP from the `rip=` field **Referenced by.** Rules 100200-100206 (Dovecot suppression and recon), 100311 (compromise correlation, indirectly via rule 100204). --- ## mailcow-postfix-sasl-fail **Purpose.** Extract source IP, subnet, and target username from mailcow-wrapped Postfix SASL authentication failure logs. Enables correlation on all three fields. ```xml postfix postfix/\S+smtpd\S*: warning: \S+: SASL unknown\[((\d+\.\d+\.\d+)\.\d+)\]: SASL \S+ authentication failed.*sasl_username=(\S+) srcip, src_subnet, dstuser ``` **Fields extracted:** | Field | Example | Notes | |-------|---------|-------| | `srcip` | `192.0.2.100` | Full IPv4 source address | | `src_subnet` | `192.0.2` | First three octets, for /24 correlation | | `dstuser` | `admin` | The sasl_username the attacker attempted | **Why PCRE2.** OSSEC regex lacks the `.*` quantifier needed to match the variable content between "authentication failed" and "sasl_username=" in Postfix logs. PCRE2 mode handles it cleanly. **Why not shipped decoder alone.** Wazuh's shipped `postfix-sasl` decoder assumes standard Postfix log format like `postfix/smtpd[PID]: ...`. Mailcow wraps every log in an outer journald envelope: `Jul 31 02:00:03 mailcow postfix[142]: Jul 30 21:00:03 e239f49ad459 postfix/submission/smtpd: ...`. The shipped decoder's anchored regex fails on the wrapped format, so srcip never gets extracted, and correlation on `same_srcip` never fires. **IPv4-only.** This decoder requires dotted-decimal IPv4 addresses. IPv6 attackers (rare against SMTP submission but possible) would fall through to the shipped `postfix` decoder alone with no field extraction. Add a separate IPv6 decoder if that becomes an issue. **Referenced by.** Rules 3332 (shipped Postfix SASL failure alert), 100310-100316 (all mail correlation rules). --- ## Testing decoders For any decoder change, verify with `wazuh-logtest`: ```bash sudo /var/ossec/bin/wazuh-logtest ``` Paste a real log line at the prompt. Look at Phase 2: - Verify the `name:` matches the decoder you expect - Verify each field in `` shows up as `field_name: 'value'` If Phase 2 doesn't show the expected fields, the regex isn't matching. Common causes: 1. Regex has `^` anchor but log doesn't start at that position 2. XML entities not escaped (`<` should be `<`, `>` should be `>`) 3. OSSEC regex used where PCRE2 syntax is required 4. Prematch too strict, not matching the log line 5. Parent decoder didn't match, so children never evaluated See `README.md` for full troubleshooting notes. Last updated: 2026-07-31