<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nguyen Dong</title>
    <description>The latest articles on DEV Community by Nguyen Dong (@xuxu298).</description>
    <link>https://dev.to/xuxu298</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3810025%2F6d6873c9-b089-4f5b-8872-076d73c641d7.png</url>
      <title>DEV Community: Nguyen Dong</title>
      <link>https://dev.to/xuxu298</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xuxu298"/>
    <language>en</language>
    <item>
      <title>What tells you a Wazuh rule has gone quiet?</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Fri, 06 Nov 2026 03:30:01 +0000</pubDate>
      <link>https://dev.to/xuxu298/what-tells-you-a-wazuh-rule-has-gone-quiet-5530</link>
      <guid>https://dev.to/xuxu298/what-tells-you-a-wazuh-rule-has-gone-quiet-5530</guid>
      <description>&lt;h1&gt;
  
  
  What tells you a Wazuh rule has gone quiet?
&lt;/h1&gt;

&lt;p&gt;An MSSP detection lead described a gap to us this week. Use cases are validated before go-live and re-tested by a BAS tool every six months. In between, a log source changes. A parser update renames a field, or someone switches off command-line auditing on a group of Windows hosts, and a production rule stops firing. Nothing reports an error.&lt;/p&gt;

&lt;p&gt;We measured what that looks like on a Wazuh 4.14.7 manager, and what can tell you it happened. Everything ran in disposable Docker containers with no network. Each claim is tagged &lt;strong&gt;[MEASURED]&lt;/strong&gt; (we ran it, raw output kept) or &lt;strong&gt;[NOT MEASURED]&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup, and a first surprise
&lt;/h2&gt;

&lt;p&gt;We pushed Windows events into &lt;code&gt;wazuh-analysisd&lt;/code&gt;'s input queue the way the agent forwards them: JSON whose &lt;code&gt;Event&lt;/code&gt; key holds the EventChannel XML, which the &lt;code&gt;windows_eventchannel&lt;/code&gt; decoder consumes.&lt;/p&gt;

&lt;p&gt;Our first try was &lt;code&gt;wazuh-logtest -l EventChannel&lt;/code&gt;. &lt;strong&gt;[MEASURED]&lt;/strong&gt; For the same 4688 event, logtest reported decoder &lt;code&gt;json&lt;/code&gt;, and no rule matched in Phase 3. The Windows chain starts at rule 60000, which requires &lt;code&gt;decoded_as windows_eventchannel&lt;/code&gt;. If your pre-go-live check for Windows rules is "paste it into logtest", it may test nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 1: the command line disappears
&lt;/h2&gt;

&lt;p&gt;This is a lab use case. It flags PowerShell started with &lt;code&gt;-EncodedCommand&lt;/code&gt;, keyed on the 4688 command line. The payload in our events is the harmless placeholder &lt;code&gt;ATK_LAB_HARMLESS_PLACEHOLDER_0000&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;rule&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"100200"&lt;/span&gt; &lt;span class="na"&gt;level=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;if_sid&amp;gt;&lt;/span&gt;67027&lt;span class="nt"&gt;&amp;lt;/if_sid&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;field&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"win.eventdata.newProcessName"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"pcre2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;(?i)\\powershell\.exe$&lt;span class="nt"&gt;&amp;lt;/field&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;field&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"win.eventdata.commandLine"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"pcre2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;(?i)\s-EncodedCommand\s&lt;span class="nt"&gt;&amp;lt;/field&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;UC-PS-ENC: PowerShell started with -EncodedCommand (lab use case)&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The parent is 67027 on purpose: it is already a child of 60103 and loads first, so a sibling under 60103 would never be evaluated.)&lt;/p&gt;

&lt;p&gt;We replayed the same event three ways. First with &lt;code&gt;CommandLine&lt;/code&gt;. Then with the element missing. Then with it present but empty, which is what you see when "Include command line in process creation events" is off.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Rule that fired&lt;/th&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;with &lt;code&gt;CommandLine&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;100200&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;CommandLine&lt;/code&gt; missing&lt;/td&gt;
&lt;td&gt;67027 "A process was created."&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;CommandLine&lt;/code&gt; empty&lt;/td&gt;
&lt;td&gt;67027&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;[MEASURED]&lt;/strong&gt; &lt;code&gt;wazuh-analysisd -t&lt;/code&gt; exited 0. Each of the three replays added 0 lines to &lt;code&gt;ossec.log&lt;/code&gt;. The decoder drops the empty field rather than keeping an empty string: 13 &lt;code&gt;eventdata&lt;/code&gt; fields with the command line, 12 for both other variants. Downstream, "auditing off" and "field never sent" look the same.&lt;/p&gt;

&lt;p&gt;The event still produces an alert, just a level 3 one, so a dashboard counting Windows events sees no dip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 2: a parser update renames a field
&lt;/h2&gt;

&lt;p&gt;This is a custom decoder and rule for a simple app log. The "parser update" changes one word:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-  &amp;lt;order&amp;gt;app.user, srcip&amp;lt;/order&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+  &amp;lt;order&amp;gt;app.username, srcip&amp;lt;/order&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule keys on &lt;code&gt;&amp;lt;field name="app.user"&amp;gt;^svc_lab_backup$&amp;lt;/field&amp;gt;&lt;/code&gt; under a level-0 grouping rule 100299. We replayed the same 3 lines before and after the change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[MEASURED]&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decoder v1: 3 of 3 lines raised rule 100300, and &lt;code&gt;wazuh-analysisd -t&lt;/code&gt; exited 0.&lt;/li&gt;
&lt;li&gt;Decoder v2: 0 of 3 lines raised an alert, and &lt;code&gt;wazuh-analysisd -t&lt;/code&gt; still exited 0. All 3 events were still decoded by &lt;code&gt;labapp&lt;/code&gt; and reached the archives. &lt;code&gt;wazuh-logtest&lt;/code&gt; showed rule 100299 at level 0.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wazuh-analysisd&lt;/code&gt; logged 0 warnings or errors after either restart.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The log was not quiet, though: &lt;strong&gt;[MEASURED]&lt;/strong&gt; the v1 restart wrote 18 WARNING/ERROR lines, all about our container having no network. A real &lt;code&gt;ossec.log&lt;/code&gt; has noise; a rule that matches nothing adds none.&lt;/p&gt;

&lt;p&gt;So neither the config check nor &lt;code&gt;ossec.log&lt;/code&gt; tells you. Here are two things that can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option A: compare today's count with the rule's own baseline
&lt;/h2&gt;

&lt;p&gt;We indexed synthetic alerts into the &lt;code&gt;wazuh-indexer:4.14.7&lt;/code&gt; image. It contains OpenSearch with plugin version 2.19.5, and we ran it with the security plugin disabled. &lt;strong&gt;[MEASURED]&lt;/strong&gt; We loaded 381 alerts over 8 days for 3 rule IDs. 100300 went from a daily 34, 34, 49, 33, 43, 49, 50 to 0 on the last day.&lt;/p&gt;

&lt;p&gt;The obvious query, "alerts per rule today", returns only rule 100200 &lt;strong&gt;[MEASURED]&lt;/strong&gt;. The silent rule is not a zero row. It is simply absent. You have to ask over a window where the rule still exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"range"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"gte"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-29T00:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-11-06T00:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;}}},&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"aggs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"by_rule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"terms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rule.id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"aggs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"today"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"range"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"gte"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-11-05T00:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-11-06T00:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;}}}},&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"baseline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"range"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"gte"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-29T00:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"lt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-11-05T00:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;}}},&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"aggs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"per_day"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"date_histogram"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"calendar_interval"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"time_zone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UTC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"min_doc_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="nl"&gt;"extended_bounds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"min"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-29T00:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-11-04T00:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;}}},&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"median_per_day"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"percentiles_bucket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"buckets_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"per_day&amp;gt;_count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"percents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;]}}}},&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"gone_quiet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"bucket_selector"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"buckets_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"today"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"today&amp;gt;_count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"median"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"baseline&amp;gt;median_per_day[50.0]"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"script"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"params.today == 0 &amp;amp;&amp;amp; params.median &amp;gt;= 5"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}}}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dates are the ones from our lab. To run it on your own indexer, change all four date ranges: the outer range to your last 8 days, &lt;code&gt;today&lt;/code&gt; to the current day, and both &lt;code&gt;baseline&lt;/code&gt; and the histogram's &lt;code&gt;extended_bounds&lt;/code&gt; to the 7 days before it. Miss &lt;code&gt;extended_bounds&lt;/code&gt; and the median can drop to 0, so a silent rule is not flagged. Run it against your alerts index (&lt;code&gt;wazuh-alerts-*&lt;/code&gt; by default).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[MEASURED]&lt;/strong&gt; Output: exactly one bucket, &lt;code&gt;100300&lt;/code&gt; with &lt;code&gt;today&lt;/code&gt; 0 and median 43.0. The query took 14 ms. Rule 100200 (today 10, median 11) was not flagged. Neither was the rare rule 100401, whose median is 0. We also wrapped the query in an Alerting plugin query monitor and ran &lt;code&gt;_execute?dryrun=true&lt;/code&gt;: it triggered and listed &lt;code&gt;[100300]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two honest notes. We needed 3 failed attempts to get here &lt;strong&gt;[MEASURED]&lt;/strong&gt;: Painless rejected &lt;code&gt;Arrays.copyOf&lt;/code&gt;, then &lt;code&gt;moving_fn&lt;/code&gt; plus &lt;code&gt;bucket_selector&lt;/code&gt; inside the &lt;code&gt;date_histogram&lt;/code&gt; returned no buckets with either gap policy. And the &lt;code&gt;median &amp;gt;= 5&lt;/code&gt; floor that keeps rare rules from paging you also means this method cannot watch rare rules, often the ones that matter most.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option B: a heartbeat per use case
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;[NOT MEASURED, documented]&lt;/strong&gt; Wazuh 4.x rules cannot fire on the absence of an event. The rules XML reference gives &lt;code&gt;frequency&lt;/code&gt; as "Any number from 2 to 9999": it counts up, and we found no option that fires when a count stays below a threshold. So the "missing" logic lives outside the rule engine and comes back in as a normal log line.&lt;/p&gt;

&lt;p&gt;Three pieces, all in our container &lt;strong&gt;[MEASURED]&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A heartbeat writes one harmless canary line every 60 s into the same file, through the same decoder and rule. On a real estate this belongs on the log source, as a cron job or systemd timer.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%b %e %H:%M:%S'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; lab-srv01 labapp[4242]: login_failed user=svc_lab_backup src=192.0.2.250"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/labapp.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;A child rule turns the canary into a low-level heartbeat alert:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;   &lt;span class="nt"&gt;&amp;lt;rule&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"100301"&lt;/span&gt; &lt;span class="na"&gt;level=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;if_sid&amp;gt;&lt;/span&gt;100300&lt;span class="nt"&gt;&amp;lt;/if_sid&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;srcip&amp;gt;&lt;/span&gt;192.0.2.250&lt;span class="nt"&gt;&amp;lt;/srcip&amp;gt;&lt;/span&gt;
     &lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;UC-SVC-LOGIN heartbeat: use case still fires&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;A watchdog runs every 60 s as a &lt;code&gt;wodle name="command"&lt;/code&gt;. It finds the newest 100301 in &lt;code&gt;alerts.json&lt;/code&gt; and writes &lt;code&gt;OK&lt;/code&gt; or &lt;code&gt;MISSING&lt;/code&gt; to a log that Wazuh reads. Rule 100310 (level 12) alerts on &lt;code&gt;MISSING&lt;/code&gt;. The threshold is 180 s.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The timeline we recorded &lt;strong&gt;[MEASURED]&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Healthy: the watchdog reported heartbeat ages of 57, 59 and 59 s.&lt;/li&gt;
&lt;li&gt;09:39:29 UTC: we deployed decoder v2. &lt;code&gt;wazuh-analysisd -t&lt;/code&gt; returned 0, and analysisd logged 0 warnings.&lt;/li&gt;
&lt;li&gt;The canary kept arriving and kept being decoded, 5 times, but raised nothing. The watchdog still said OK at ages 26, 86 and 146 s.&lt;/li&gt;
&lt;li&gt;The first &lt;code&gt;MISSING&lt;/code&gt; line came at age 206 s. Rule 100310 fired 180.0 s after the first canary that no longer matched.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One false alarm &lt;strong&gt;[MEASURED]&lt;/strong&gt;: on startup the watchdog ran before the first heartbeat arrived and raised 100310 with &lt;code&gt;last_seen=never&lt;/code&gt;. Give it a grace period.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same failure mode in Wazuh 5.0
&lt;/h2&gt;

&lt;p&gt;We published the upgrade version of this problem on our &lt;a href="https://vct.atkvn.com/wazuh-5-migration.html" rel="noopener noreferrer"&gt;Wazuh 5.0 Migration page&lt;/a&gt;. &lt;strong&gt;[NOT MEASURED today, quoted]&lt;/strong&gt; "Our 4.x local_rules.xml and local_decoder.xml, dropped into etc/rules and etc/decoders: of the new log lines after restart (124 on rc1, 116 on rc2), not one mentioned them, and the config check still returned success." The cause is different, since 5.0 replaces the rule engine rather than changing a log source. The symptom is the same: success from the config check, silence from the rule. The baseline and heartbeat checks above look at output rather than configuration, so they should catch both. We have not run them on 5.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we did not test
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Real Windows hosts or a real agent: our 4688 events were hand-built XML in the agent's forwarding format. Side observation: our own malformed test events (double-quoted XML attributes) warned twice, and five more decoded with zero &lt;code&gt;eventdata&lt;/code&gt; fields and no warning.&lt;/li&gt;
&lt;li&gt;Sysmon EID 1. We used Security 4688 only.&lt;/li&gt;
&lt;li&gt;A real BAS tool, or a production-sized index. The baseline ran on 381 synthetic alerts.&lt;/li&gt;
&lt;li&gt;A watchdog querying the indexer instead of &lt;code&gt;alerts.json&lt;/code&gt;, or a cluster.&lt;/li&gt;
&lt;li&gt;Seasonality: weekends and maintenance windows will break a plain 7-day median.&lt;/li&gt;
&lt;li&gt;Wazuh 5.0 for either option.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither option proves a use case catches the attack it was written for. They answer something smaller and more urgent: does the rule still fire at all?&lt;/p&gt;

&lt;p&gt;If you want a second pair of eyes, run the query without the &lt;code&gt;gone_quiet&lt;/code&gt; block, so it returns every rule rather than only the silent ones, and send the output to &lt;a href="mailto:support@atkvn.com"&gt;support@atkvn.com&lt;/a&gt;: rule IDs and daily counts only, no hostnames, no logs. We reply within 24 hours, at no charge, with the rules that look silent or are dropping sharply, and the causes worth checking first, and delete the table once we have answered.&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>siem</category>
      <category>detectionengineering</category>
      <category>opensearch</category>
    </item>
    <item>
      <title>Wazuh snort decoder cuts the last octet off srcip and dstip (4.14.x)</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Thu, 05 Nov 2026 07:07:13 +0000</pubDate>
      <link>https://dev.to/xuxu298/wazuh-snort-decoder-cuts-the-last-octet-off-srcip-and-dstip-414x-2243</link>
      <guid>https://dev.to/xuxu298/wazuh-snort-decoder-cuts-the-last-octet-off-srcip-and-dstip-414x-2243</guid>
      <description>&lt;p&gt;If a snort alert has no source port, Wazuh 4.14.x stores srcip without its last octet: 10.4.12.26 becomes 10.4.12. When the destination has a port, the port stays glued to dstip. Anything keyed on those fields, such as active response, CDB lists or correlation by IP, then works on the wrong value.&lt;/p&gt;

&lt;p&gt;Everything below was measured with wazuh-logtest on wazuh-manager 4.14.9, syslog snort: form only. The problem is tracked upstream as #39989, with a fix in #40521 for 4.14.11.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check it in one minute
&lt;/h2&gt;

&lt;p&gt;Paste a snort line into wazuh-logtest and read the decoding phase. Take the samples below, without the syslog prefix, starting at the priority. For the first one, &lt;code&gt;{ICMP} 10.4.12.26 -&amp;gt; 10.4.10.231&lt;/code&gt;, stock 4.14.9 gives srcip 10.4.12; with the merged fix from #40521, srcip is right but dstip becomes 10.4.10. (with a trailing dot).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;Stock is ruleset/decoders/0285-snort_decoders.xml as shipped in 4.14.9. Its snort2 decoder has three regex lines that capture the source and destination addresses, and those are the lines the fix below changes.&lt;/p&gt;

&lt;p&gt;We ran eight samples through three decoders: the stock file, the three regex lines from PR #40521 (merged for 4.14.11, not released), and the same lines with one more change, shown below.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sample (after the priority)&lt;/th&gt;
&lt;th&gt;Stock 4.14.9&lt;/th&gt;
&lt;th&gt;Merged fix&lt;/th&gt;
&lt;th&gt;With the change below&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{ICMP} 10.4.12.26 -&amp;gt; 10.4.10.231&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;srcip 10.4.12&lt;/td&gt;
&lt;td&gt;dstip 10.4.10.&lt;/td&gt;
&lt;td&gt;both correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{TCP} 10.4.12.26:37020 -&amp;gt; 10.4.10.231:162&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;dstip 10.4.10.231:162&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{UDP} 192.168.20.32 -&amp;gt; 192.168.20.2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;srcip 192.168.20&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{TCP} 10.4.12.26:5000 -&amp;gt; 10.4.10.7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{UDP} 10.4.12.26 -&amp;gt; 10.4.10.7:53&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;srcip 10.4.12, dstip 10.4.10.7:53&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{ICMP} 203.113.100.250 -&amp;gt; 198.51.100.200&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;srcip 203.113.100&lt;/td&gt;
&lt;td&gt;dstip 198.51.100.&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{TCP} 203.113.100.250:44321 -&amp;gt; 198.51.100.200:443&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;dstip 198.51.100.200:443&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;td&gt;correct&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;[**]&lt;/code&gt; multi-line form from the file header&lt;/td&gt;
&lt;td&gt;no fields&lt;/td&gt;
&lt;td&gt;no fields&lt;/td&gt;
&lt;td&gt;no fields&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The merged fix still loses the last octet of dstip when there is no destination port and that octet has three digits. We reported that measurement on the PR before 4.14.11 ships: &lt;a href="https://github.com/wazuh/wazuh/pull/40521#issuecomment-6343969794" rel="noopener noreferrer"&gt;https://github.com/wazuh/wazuh/pull/40521#issuecomment-6343969794&lt;/a&gt; The &lt;code&gt;[**]&lt;/code&gt; form is assembled by the snort-full reader across several lines, which a single logtest line does not reproduce, so it is out of scope here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix on 4.14.x today
&lt;/h2&gt;

&lt;p&gt;Copy the stock file and change its three regex lines in the snort2 decoder to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;regex&amp;gt;&lt;/span&gt;^[**] [(\d+:\d+:\d+)] \.+ (\d+.\d+.\d+.\d+)\p*\d* -&amp;gt; &lt;span class="nt"&gt;&amp;lt;/regex&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;regex&amp;gt;&lt;/span&gt;(\d+.\d+.\d+.\d+)\p*\d*|^[(\d+:\d+:\d+)] \.+ &lt;span class="nt"&gt;&amp;lt;/regex&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;regex&amp;gt;&lt;/span&gt;(\d+.\d+.\d+.\d+)\p*\d* -&amp;gt; (\d+.\d+.\d+.\d+)&lt;span class="nt"&gt;&amp;lt;/regex&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save it as /var/ossec/etc/decoders/0285-snort_decoders-fixed.xml, owner wazuh:wazuh.&lt;/p&gt;

&lt;p&gt;Then, in /var/ossec/etc/ossec.conf, inside the ruleset block, stop the stock file from loading so the two copies do not compete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;decoder_exclude&amp;gt;&lt;/span&gt;ruleset/decoders/0285-snort_decoders.xml&lt;span class="nt"&gt;&amp;lt;/decoder_exclude&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the manager. A new wazuh-logtest session picks the change up without a restart, so you can check first; live analysis needs the restart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Confirm it worked
&lt;/h2&gt;

&lt;p&gt;Run the same eight lines again and compare with the last column of the table: srcip and dstip should be whole addresses, with the port gone from dstip.&lt;/p&gt;

&lt;p&gt;After upgrading to 4.14.11 or later, remove both the local file and the exclude line, and re-run your samples. The shipped decoder may or may not include the dstip change above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of what we measured
&lt;/h2&gt;

&lt;p&gt;Measured with the logtest engine on 4.14.9, syslog snort: form only. Your snort output may differ (unified2 via barnyard, alert_fast, IPv6), so run your own lines before you rely on it.&lt;/p&gt;

&lt;p&gt;Full write-up with the lab output: &lt;a href="https://atkvn.com/fix-wazuh-snort-srcip-truncated-last-octet.html" rel="noopener noreferrer"&gt;https://atkvn.com/fix-wazuh-snort-srcip-truncated-last-octet.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>detectionengineering</category>
      <category>wazuh</category>
      <category>siem</category>
      <category>security</category>
    </item>
    <item>
      <title>Wazuh 5.0 rc2: eight things that still change for your 4.x custom rules</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Wed, 04 Nov 2026 07:06:04 +0000</pubDate>
      <link>https://dev.to/xuxu298/wazuh-50-rc2-eight-things-that-still-change-for-your-4x-custom-rules-516i</link>
      <guid>https://dev.to/xuxu298/wazuh-50-rc2-eight-things-that-still-change-for-your-4x-custom-rules-516i</guid>
      <description>&lt;p&gt;Wazuh 5.0.0-rc2 was published on 30 October 2026. If you maintain custom 4.x rules and decoders, the question is whether anything we measured on rc1 moved. We re-ran all eight of those checks on rc2 in a lab with Docker, one manager, one indexer and one 4.14.7 agent. All eight came out the same. GA has not been released, so treat this as a release candidate and not a verdict.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The analysis binary has no 4.x rule keywords
&lt;/h2&gt;

&lt;p&gt;We counted occurrences of if_sid, if_matched_sid, same_srcip and frequency in the rc2 wazuh-manager-analysisd binary with grep -a -o piped to wc -l. All four were 0. We also searched the rest of the rc2 bin and lib directories: if_sid, if_matched_sid and same_srcip appear in no file. The 4.14.7 binary contains all of them. We are not quoting its exact counts, because our counting method was not the same across runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Your XML files load without a word
&lt;/h2&gt;

&lt;p&gt;We dropped 4.x rule, decoder and list XML into both the old etc directories and the new /etc/wazuh-manager, then restarted. The restart produced 116 new log lines (124 on rc1). Zero of them mentioned rules, decoders, lists or xml. wazuh-manager-analysisd -t returned 0 and wazuh-manager-conf validate returned 0. A passing config check does not tell you the rules are doing anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. srcip is rejected as a field
&lt;/h2&gt;

&lt;p&gt;Posting content that uses srcip to the content manager rules endpoint returned HTTP 400: "Error in Security Analytics. The following fields are not part of the Wazuh Common Schema (WCS): [srcip]". Same body, same message as rc1.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Unparsed lines are not indexed by default
&lt;/h2&gt;

&lt;p&gt;index_unclassified_events is false in all four spaces (standard, custom, draft, test). We sent 16 log lines: 12 documents were indexed, and the 4 lines from our home-made app, which had no rewritten decoder, were in no index. On rc1 it was also 4 of 16.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Indexer flush is 20 seconds
&lt;/h2&gt;

&lt;p&gt;From the binary, the default analysisd.indexer_flush_interval is 20 s, allowed range 1 to 3600. Measured delay from event to visible document was at most 20.2 s, with the sawtooth about 20 s apart. Setting it to 0 is now refused at config load ("Allowed range: 1 to 3600"), and afterwards the manager still listed analysisd as running; on rc1 the same setting stopped analysisd. The "5 s flush gives 7.1 s worst case" figure is from rc1 only. We did not re-run it on rc2.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Active response does not reach a 4.14.7 agent
&lt;/h2&gt;

&lt;p&gt;A 4.14.7 agent enrolled and its events were indexed. We then triggered an active response through an indexer monitor. The manager created the task, and remoted logged: "task type 'active_response' not supported for legacy agents, not delivered". The agent's active-responses.log had no active response line. The rc2 release notes add an active response allowlist, which we did not test.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Frequency rules have no working replacement yet
&lt;/h2&gt;

&lt;p&gt;Our 4.x frequency rule, rewritten as a Sigma aggregation rule, saved without complaint (HTTP 201). Creating a detector for it returned HTTP 500: "Cannot invoke "java.util.Map.containsKey(Object)" because "this.fieldMappings" is null", a NullPointerException. We tried three times, once after an attempt to add a field mapping, and got the same error each time, so the rule never ran. Two correlation rules also saved (HTTP 201), but after sending our 16 log lines three times and waiting 200 s there were zero correlation alerts. With the ruleset served on 2 November, the stock ruleset raised no finding for our nine pre-auth SSH disconnects from one IP. (Updated 6 Nov: with the ruleset served on 5 November, a new stock rule raises one low-severity finding for every disconnect event, still with no brute-force or frequency finding.) All of this matches rc1. One small difference: the field-mapping request itself now returns HTTP 500, where rc1 returned 200; the detector fails either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. A rewritten child rule fires next to its parent
&lt;/h2&gt;

&lt;p&gt;In 4.x our child rule raised only the child alert for the root-login line. Rewritten in Sigma, with the parent's condition copied into the child, both matched: logtest on that line listed parent-100510 and child-of-100510-user-root, and the detector produced a finding for each on the same event. Same as rc1. In our runs that meant two findings for one event; we have not yet tested excluding the child's case from the parent.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this does not cover
&lt;/h2&gt;

&lt;p&gt;The "5 s flush gives 7.1 s worst case" figure in item 5 is from rc1 only. Untested on either release candidate: TLS with the security plugin on, multiple nodes, a 5.x agent, CDB lists in the new key-value store, and whether Sigma timeframe is honoured.&lt;/p&gt;

&lt;p&gt;One change that will break lab scripts: the rc2 notes say manager configuration moved from /var/wazuh-manager/etc to /etc/wazuh-manager with no symlink, which broke our rc1 lab scripts until we fixed the paths.&lt;/p&gt;

&lt;p&gt;What we can run for you and what it costs is on one page: &lt;a href="https://vct.atkvn.com/wazuh-5-migration.html" rel="noopener noreferrer"&gt;https://vct.atkvn.com/wazuh-5-migration.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>siem</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Wazuh active response: Cannot read 'srcip' from data</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Tue, 03 Nov 2026 07:04:02 +0000</pubDate>
      <link>https://dev.to/xuxu298/wazuh-active-response-cannot-read-srcip-from-data-i3n</link>
      <guid>https://dev.to/xuxu298/wazuh-active-response-cannot-read-srcip-from-data-i3n</guid>
      <description>&lt;p&gt;The rule fired, the alert is in alerts.json, and the block did not happen. This line is written by a Wazuh active response, not by the rule engine. There are two different cases. In the first, the alert had no usable source IP in the places the script reads. In the second, on Windows agents, measured on 4.14.7, netsh.exe and route-null.exe wrote this line for every alert we sent, including one with a valid data.srcip: the IP was there and was still rejected.&lt;/p&gt;

&lt;p&gt;Case 1 below is built from reading the Wazuh 4.14.7 source and the public documentation. The Windows case was measured on a Windows 11 agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check it in one minute
&lt;/h2&gt;

&lt;p&gt;The message goes to the active response log of the host where the response ran, the agent or the manager depending on location:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Cannot read 'srcip'"&lt;/span&gt; /var/ossec/logs/active-responses.log | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then look at what triggers the response. On the manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A8&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;active-response&amp;gt;'&lt;/span&gt; /var/ossec/etc/ossec.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it is bound by level or rules_group, it runs for every matching rule, including rules whose events carry no IP.&lt;/p&gt;

&lt;p&gt;To test the response directly, with an address you control (we used the documentation range), call it through the manager API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="s2"&gt;"https://&amp;lt;manager&amp;gt;:55000/active-response?agents_list=&amp;lt;agent id&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"command":"!netsh.exe","alert":{"data":{"srcip":"203.0.113.78"}}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The leading ! runs the executable by name. With "command":"netsh" the 4.14.7 API answered "The command used is not defined in the configuration." (error 1652) because no active-response block used that command yet. On Windows the log is C:\Program Files (x86)\ossec-agent\active-response\active-responses.log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens (case 1)
&lt;/h2&gt;

&lt;p&gt;In 4.14.7 the response reads the source IP with get_srcip_from_json() in src/active-response/active_responses.c. Reading the code, it does this, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It needs parameters.alert.data in its input to be an object. If not, there is no IP.&lt;/li&gt;
&lt;li&gt;If data.win.eventdata.ipAddress is a string, that is the candidate. If not, it tries data.win.eventdata.destinationIp.&lt;/li&gt;
&lt;li&gt;Only if neither Windows field is a string does it read data.srcip.&lt;/li&gt;
&lt;li&gt;The candidate must parse as an IPv4 or IPv6 address. If it does not, there is no IP. There is no fall-back to the next field.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When nothing comes back, firewalls/default-firewall-drop.c writes this line to logs/active-responses.log and exits with an error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cannot read 'srcip' from data or invalid IP format
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So there are three practical causes. The event has no srcip: the decoder for that source does not extract one, or it lands under another name. The JSON decoder keeps the key's own name (we saw this in wazuh-logtest on 4.14.7 with Nextcloud and Office 365 events), so an address under a key such as src_ip becomes data.src_ip, which this function does not read. The field exists but is not an IP address, for example a host name. Or, on Windows events, win.eventdata.ipAddress is present as a string but is not a valid address, and the function returns nothing without trying destinationIp or data.srcip.&lt;/p&gt;

&lt;p&gt;The message does not mean your rule failed. The rule matched, the alert was written, and only the response step found nothing to act on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix (case 1)
&lt;/h2&gt;

&lt;p&gt;Bind the response only to rules whose events carry an address. Use rules_id, which the documentation describes as limiting the command to when the listed rules fire, instead of a level or group that also catches rules without an IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;active-response&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;command&amp;gt;&lt;/span&gt;firewall-drop&lt;span class="nt"&gt;&amp;lt;/command&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;location&amp;gt;&lt;/span&gt;local&lt;span class="nt"&gt;&amp;lt;/location&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;rules_id&amp;gt;&lt;/span&gt;100100&lt;span class="nt"&gt;&amp;lt;/rules_id&amp;gt;&lt;/span&gt;  &lt;span class="c"&gt;&amp;lt;!-- your rule ID(s) --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;timeout&amp;gt;&lt;/span&gt;600&lt;span class="nt"&gt;&amp;lt;/timeout&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/active-response&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then make the decoder name the field srcip for the sources you want to block on. For a regex decoder, order maps each capture group to a field name, and srcip is one of the names it accepts. Test the decoder with wazuh-logtest and confirm srcip appears in the decoding phase before relying on the response. Finally, check the value is an address: if the field holds a host name or a placeholder, the response cannot use it, whatever the binding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Windows agents (4.14.7, measured): every IP fails
&lt;/h2&gt;

&lt;p&gt;From a 4.14.7 manager we sent active responses to a Windows 11 agent running 4.14.7, through the API as shown above, and read the agent's active-responses.log. For both netsh.exe and route-null.exe, an alert carrying only data.win.eventdata.ipAddress = 203.0.113.77 gave "Cannot read 'srcip' from data", and so did an alert carrying only data.srcip = 203.0.113.78. Word for word, from the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026/10/03 19:51:29 active-response/bin/netsh.exe: Cannot read 'srcip' from data
2026/10/03 19:51:39 active-response/bin/route-null.exe: Cannot read 'srcip' from data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The data.srcip row rules out the field name: a plain, valid address was rejected too. Afterwards there was no Wazuh firewall rule and no route for either address on the machine. The Windows responses print the short form of the message, without "or invalid IP format".&lt;/p&gt;

&lt;p&gt;Why, from the code: at v4.14.7, netsh.c (line 76) and route-null.c (line 29) both call get_srcip_from_json(), which checks the address with get_ip_version(), which calls getaddrinfo() (active_responses.c, line 576). We found no WSAStartup call in active_responses.c, netsh.c or route-null.c; on Windows, the socket functions need it first. That fits the cause proposed in wazuh/wazuh issue 38739, still open. This is our reading of the code plus the result above, not a debugger run.&lt;/p&gt;

&lt;p&gt;Workaround: we found no central workaround you can apply from the manager. The check fails before netsh runs, whatever field carries the IP, and the binding and decoder fixes above do not change that on Windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of what we measured
&lt;/h2&gt;

&lt;p&gt;For case 1 we have not triggered the message on a running Linux agent or manager, and have not measured which releases print the shorter text. The Windows case was measured on one Windows 11 Pro agent running 4.14.7 with a 4.14.7 manager, by calling the responses through the API; we did not measure 4.14.8 or earlier releases, and the WSAStartup explanation is from reading the code, not a debugger.&lt;/p&gt;

&lt;p&gt;Full write-up with the lab output: &lt;a href="https://atkvn.com/fix-cannot-read-srcip-from-data.html" rel="noopener noreferrer"&gt;https://atkvn.com/fix-cannot-read-srcip-from-data.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>siem</category>
      <category>security</category>
      <category>windows</category>
    </item>
    <item>
      <title>Wazuh: wazuh-analysisd segfault after a ruleset reload (4.13 and later)</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Mon, 02 Nov 2026 07:05:17 +0000</pubDate>
      <link>https://dev.to/xuxu298/wazuh-wazuh-analysisd-segfault-after-a-ruleset-reload-413-and-later-3f5a</link>
      <guid>https://dev.to/xuxu298/wazuh-wazuh-analysisd-segfault-after-a-ruleset-reload-413-and-later-3f5a</guid>
      <description>&lt;p&gt;You edit rules, save, reload the ruleset from the dashboard or the API, and shortly afterwards wazuh-analysisd dies with a segmentation fault. A full restart with the same rules is fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check if it is this
&lt;/h2&gt;

&lt;p&gt;The kernel saw a segfault in analysisd:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;-k&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'wazuh-analysisd.*segfault'&lt;/span&gt;
&lt;span class="c"&gt;# or&lt;/span&gt;
dmesg &lt;span class="nt"&gt;-T&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'wazuh-analysisd.*segfault'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reporter on wazuh/wazuh issue 39928 (4.14.5-1) saw lines like &lt;code&gt;wazuh-analysisd[...]: segfault at ... error 6 in wazuh-analysisd[...]&lt;/code&gt;, with three of four crashes at the same instruction pointer. The address will differ between builds. What matters is that it is wazuh-analysisd and that it repeats.&lt;/p&gt;

&lt;p&gt;Then compare the crash time with when rules were saved and reloaded. A crash a few seconds to minutes after a reload, and none after a full restart, is the pattern in the issue. Your version should be 4.13.0 or later; &lt;code&gt;/var/ossec/bin/wazuh-control info&lt;/code&gt; prints it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;According to the Wazuh maintainer who analysed the reporter's core dump, on 28 October 2026, this is "a race condition in the ruleset hot reload, introduced in 4.13.0". As they describe it, w_hotreload_reload() takes the ruleset lock as writer, switches the global ruleset pointers, releases the lock, and then frees the old ruleset. The event processing threads never take that lock, so events already in flight keep being matched against the old ruleset. os_remove_eventlist() frees that list without taking its mutex and dereferences an event that is no longer valid. The more events in flight during a reload, the wider the window.&lt;/p&gt;

&lt;p&gt;This is the maintainer's analysis, quoted from the issue. We have not verified it against the source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which reload
&lt;/h2&gt;

&lt;p&gt;Per the same comment, only the API path reaches the hot reload: PUT /manager/analysisd/reload and PUT /cluster/analysisd/reload, which the dashboard uses. wazuh-control reload does not hot reload; it restarts the services.&lt;/p&gt;

&lt;p&gt;We measured this on the wazuh-manager 4.14.8 Docker image, which reports revision rc2. After PUT /manager/analysisd/reload, wazuh-analysisd kept the same PID and ossec.log showed "Reloading ruleset" followed by "Ruleset reloaded successfully". After wazuh-control reload, every daemon we checked except wazuh-execd came back with a new PID, and analysisd was down for about 9 seconds. If your crashes line up with the dashboard or the API, it is the hot reload path. If you only ever use wazuh-control reload, it is probably something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix status
&lt;/h2&gt;

&lt;p&gt;Pull request 40907, "Fix analysisd crash when reloading the ruleset under load", was merged on 30 October into the 4.14.11 branch and the issue was closed. According to the pull request, the rule matching threads now take a lock that the reload waits for, so the old ruleset is freed only when no event is still using it. We have not tested the fixed build. The merge commit is not in v4.14.10-rc2, and 4.14.11 has no release yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do until then
&lt;/h2&gt;

&lt;p&gt;Before the fix, the maintainer wrote: "We'll work on a fix for 4.14.x. Until then, apply ruleset changes with a full restart (systemctl restart wazuh-manager) instead of a reload."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/var/ossec/bin/wazuh-analysisd &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; systemctl restart wazuh-manager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The -t test first means a broken rule file stops you before the restart, not after. We changed a custom rule from level 5 to 12, wazuh-analysisd -t printed nothing and exited 0, we restarted, and wazuh-logtest matched the edited rule at level 12. Our lab container has no systemd, so we restarted with /var/ossec/bin/wazuh-control restart.&lt;/p&gt;

&lt;p&gt;Avoid the dashboard and API reload on affected nodes until you run a release that contains the fix. On a cluster, that includes PUT /cluster/analysisd/reload.&lt;/p&gt;

&lt;p&gt;To bring a dead daemon back automatically, a cron entry running /var/ossec/bin/wazuh-control start every minute is meant to do that. With analysisd killed, wazuh-control start started only analysisd, with a new PID, and left every other daemon on its existing PID. We tested this after one simulated crash, not over days. Pause it during planned maintenance, or it will start daemons you stopped on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  A crash is silent
&lt;/h2&gt;

&lt;p&gt;We killed wazuh-analysisd with kill -SEGV in the lab to see what a crash looks like from the outside. In the next 20 seconds ossec.log got no line at all, nothing restarted it, and every other daemon kept running with the same PID. Only wazuh-control status showed that analysisd was gone. The manager looks alive while no rule is being evaluated, and nothing in the log says so. If you cannot avoid reloads yet, alert on analysisd being down.&lt;/p&gt;

&lt;h2&gt;
  
  
  We did not reproduce the crash
&lt;/h2&gt;

&lt;p&gt;We ran 20 API reloads, about 5 seconds apart, while the manager processed about 470 events per second of syslog, measured from wazuh-analysisd.state, with 0 events dropped. analysisd survived all 20. That does not mean your node is safe. The maintainer's analysis says the window grows with the number of events in flight, and our lab was small: one manager, no agents, the default ruleset plus one custom rule. We did not test large rulesets or many CDB lists, more event threads, reloads less than a second apart, more than 500 events per second, a cluster, or events arriving from agents. The crash in the silent-crash test is simulated with kill -SEGV, not the race itself.&lt;/p&gt;

&lt;p&gt;Full write-up, including the fix status as read from GitHub on 31 October 2026: &lt;a href="https://atkvn.com/fix-wazuh-analysisd-segfault-after-rule-reload.html" rel="noopener noreferrer"&gt;https://atkvn.com/fix-wazuh-analysisd-segfault-after-rule-reload.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>siem</category>
      <category>security</category>
      <category>linux</category>
    </item>
    <item>
      <title>Wazuh custom rule never fires: shadowed by a sibling rule (and analysisd -t exits 0)</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Sun, 01 Nov 2026 07:18:02 +0000</pubDate>
      <link>https://dev.to/xuxu298/wazuh-custom-rule-never-fires-shadowed-by-a-sibling-rule-and-analysisd-t-exits-0-4ada</link>
      <guid>https://dev.to/xuxu298/wazuh-custom-rule-never-fires-shadowed-by-a-sibling-rule-and-analysisd-t-exits-0-4ada</guid>
      <description>&lt;p&gt;You wrote a custom rule. wazuh-analysisd -t exits 0, a one-line logtest even shows it matching, and in production it never fires. One cause: another child of the same parent matched the event first.&lt;/p&gt;

&lt;p&gt;When several children of one parent can match an event, one of them takes it. In our measurements, on Wazuh 4.14.7, the higher level went first, and at equal level the rule loaded first went first. If a sibling with a higher level matches your event, your rule never fires, and neither the config check nor a one-line logtest shows it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;We placed one rule in local_rules.xml and changed only its parent and level between runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;group&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"local,syslog,sshd,"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;rule&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"100080"&lt;/span&gt; &lt;span class="na"&gt;level=&lt;/span&gt;&lt;span class="s"&gt;"$LEVEL"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;if_sid&amp;gt;&lt;/span&gt;$PARENT&lt;span class="nt"&gt;&amp;lt;/if_sid&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;match&amp;gt;&lt;/span&gt;Accepted password&lt;span class="nt"&gt;&amp;lt;/match&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;probe&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/group&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two inputs through wazuh-logtest: S1, one Accepted password line; S2, 8 Failed password lines from the same IP within 8 seconds, then the same Accepted password line.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Case&lt;/th&gt;
&lt;th&gt;Parent&lt;/th&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;-T&lt;/th&gt;
&lt;th&gt;S1 lands on&lt;/th&gt;
&lt;th&gt;S2 lands on&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;5715&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;exit 0, no warning&lt;/td&gt;
&lt;td&gt;100080 (ours)&lt;/td&gt;
&lt;td&gt;40112 level 12, ours loses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;5715&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;exit 0, no warning&lt;/td&gt;
&lt;td&gt;100080&lt;/td&gt;
&lt;td&gt;100080 level 13, ours wins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;5700&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;exit 0, no warning&lt;/td&gt;
&lt;td&gt;100080&lt;/td&gt;
&lt;td&gt;100080&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;40112&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;exit 0, no warning&lt;/td&gt;
&lt;td&gt;5715 level 3, ours does not fire&lt;/td&gt;
&lt;td&gt;100080&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;control&lt;/td&gt;
&lt;td&gt;no custom rule&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;5715 level 3&lt;/td&gt;
&lt;td&gt;40112 level 12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We ran the matrix twice; all 8 verdicts matched. Four things follow.&lt;/p&gt;

&lt;p&gt;A against B: same parent, only the level changed from 9 to 13, and the outcome of S2 flipped. Level decides the order among siblings, not if_sid and not the file.&lt;/p&gt;

&lt;p&gt;At equal level, load order decides. On a test ruleset on a throwaway 4.14.7 manager, a sibling at the same level that was loaded earlier took the event. This matches a reading of the loader source: a new child is inserted in front of the first sibling with a lower level, and behind siblings with the same level.&lt;/p&gt;

&lt;p&gt;The config check says nothing about this. wazuh-analysisd -t exited 0 with no warning in all four cases, including the one where our rule lost.&lt;/p&gt;

&lt;p&gt;One line in logtest is not enough. In case A, a one-line test passes. Only the sequence shows the loss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check it in one minute
&lt;/h2&gt;

&lt;p&gt;Feed wazuh-logtest the real sequence of events, not one line, and read which rule took the last one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/var/ossec/bin/wazuh-logtest &amp;lt; sequence.log 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"^[[:space:]]+(id|level|description): "&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the ID is a sibling's rather than yours, your rule is shadowed on that sequence. Keep the 2&amp;gt;&amp;amp;1: logtest on 4.14 prints its results to stderr.&lt;/p&gt;

&lt;p&gt;To find the sequence, look at alerts from siblings of your rule, meaning other children of the same parent. Their full_log is the event that sibling took.&lt;/p&gt;

&lt;h2&gt;
  
  
  Confirm before you fix
&lt;/h2&gt;

&lt;p&gt;A sibling that fired proves only that the sibling matched something. It does not prove your rule would have matched the same event. On a test ruleset we built three candidates this way; one of them, a rule on a user name that none of the events had, looked shadowed and was not.&lt;/p&gt;

&lt;p&gt;The check that separated them: on a throwaway manager with a copy of the rules, raise your rule's level to 15, replay the full_log of the sibling's alerts through wazuh-logtest, and see whether any of them flips to your rule. If some flip, the rule is shadowed on those events. If none flip, your rule does not match them, and the problem is the rule's own conditions. In our test, the two real cases flipped (2 of 2 and 1 of 2 samples) and the false one flipped 0 times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix
&lt;/h2&gt;

&lt;p&gt;Each of these was measured on the same pair of rules (the table above). Which one is right depends on what you want the rule to mean.&lt;/p&gt;

&lt;p&gt;Rank above the sibling. Case B: level 13 over the sibling's 12 won both inputs. This also raises the severity of your alert, which may not be what you want.&lt;/p&gt;

&lt;p&gt;Hang the rule higher in the tree. Case C: with parent 5700 at level 9, our rule took both inputs.&lt;/p&gt;

&lt;p&gt;Hang the rule on the sibling. Case D: with parent 40112, the rule fires only after the burst of failures, and a lone login falls back to 5715. Use this when the correlated case is the one you care about.&lt;/p&gt;

&lt;p&gt;At equal level, the rule loaded first wins. Moving your rule earlier can fix it, but a file that sorts before the parent's file gets your rule dropped at load. Check for 7617/7619 after any rename.&lt;/p&gt;

&lt;p&gt;In B and C, 40112 no longer fires on S2: your rule takes the event instead. If you rely on 40112 as a brute-force alert, use D. After the change, feed the same sequence through logtest again and confirm the last line lands on your rule ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of what we measured
&lt;/h2&gt;

&lt;p&gt;One release (4.14.7), one single-node manager, one parent (5715) and one higher-level sibling (40112) for the level test, replayed through wazuh-logtest. The equal-level case and the replay check ran on test rules we wrote, on a throwaway 4.14.7 manager. We have not measured if_matched_sid or if_group chains, clusters, or live traffic. The replay check needs the full_log of the sibling's alerts; we have not measured it on Windows eventchannel or JSON events. Wazuh 4.14.8 (released 25 September 2026) ships byte-identical source files for rule loading and matching (rules-config.c, rules.c, cleanevent.c, logmsg.h, list_op.c); we compared the files, we did not rerun the tests on 4.14.8.&lt;/p&gt;

&lt;p&gt;Full write-up with the lab output: &lt;a href="https://atkvn.com/fix-rule-shadowed-by-sibling.html" rel="noopener noreferrer"&gt;https://atkvn.com/fix-rule-shadowed-by-sibling.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>siem</category>
      <category>security</category>
      <category>detectionengineering</category>
    </item>
    <item>
      <title>Wazuh alert is in alerts.json but missing from the dashboard: mapper_parsing_exception</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Sat, 31 Oct 2026 12:41:41 +0000</pubDate>
      <link>https://dev.to/xuxu298/wazuh-alert-is-in-alertsjson-but-missing-from-the-dashboard-mapperparsingexception-31of</link>
      <guid>https://dev.to/xuxu298/wazuh-alert-is-in-alertsjson-but-missing-from-the-dashboard-mapperparsingexception-31of</guid>
      <description>&lt;p&gt;You grep alerts.json and the alert is there. You open the dashboard and it is not. The manager looks healthy, the rule fired, and nothing in the UI says anything went wrong.&lt;/p&gt;

&lt;p&gt;The alert was rejected by the indexer. The manager only writes alerts.json. Filebeat then sends each alert through an ingest pipeline into an index whose mapping comes from the Wazuh index template, and the mapping allows one type per field path. When a document carries a different shape at that path, the indexer answers with mapper_parsing_exception and does not store the document. Because the alert is already on disk, everything on the manager side looks normal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;Two things make a clash likely.&lt;/p&gt;

&lt;p&gt;The stock template maps many fields as keyword. In the 4.14.7 template we counted 609 leaf fields, 486 of them keyword, 409 of those under data.*. Directly under data there are 21 scalar fields, among them data.data, data.srcip, data.url and data.id. A log source that sends an object under one of those names can be rejected.&lt;/p&gt;

&lt;p&gt;The JSON decoder also changes shape with the input. In wazuh-logtest on 4.14.7, an object is expanded into subfields, while an array, empty or not, is packed into a single string such as '[]'. We saw this on two integrations, Nextcloud and Office 365. So the same path can be an object in one event and a string in the next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check it in one minute
&lt;/h2&gt;

&lt;p&gt;Take one alert that is in /var/ossec/logs/alerts/alerts.json but missing from the dashboard, and note the field paths it carries under data. Then ask the indexer how it mapped them. For Microsoft Graph sign-ins the two fields are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sk&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;user&amp;gt;:&amp;lt;password&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://&amp;lt;indexer&amp;gt;:9200/wazuh-alerts-*,wazuh-archives-*/_mapping/field/data.ms-graph.status,data.ms-graph.appliedConditionalAccessPolicies?pretty"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a field comes back "type" : "keyword" and the missing alert has an object, or a list of objects, at that path, the indexer rejects it. For any other field, put its path in place of the two above.&lt;/p&gt;

&lt;p&gt;If your Filebeat writes its own log, this counts which fields fail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'failed to parse field \[[^]]*\]'&lt;/span&gt; /var/log/filebeat/filebeat&lt;span class="k"&gt;*&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-rn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have not run a real Filebeat in our tests, so we have not seen that log line ourselves. The mapping query does not depend on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 1: Microsoft Graph sign-ins, data.ms-graph.status
&lt;/h2&gt;

&lt;p&gt;The template maps data.ms-graph.status and data.ms-graph.appliedConditionalAccessPolicies as keyword. We checked the template at release tags v4.8.0, v4.10.0, v4.12.0, v4.14.0, v4.14.3, v4.14.7 and v4.14.8: the same in all seven. Alerts and incidents send status as a string such as "resolved". Sign-ins send it as an object, and send the policies as a list of objects. On a test indexer every sign-in was rejected, in both arrival orders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;failed to parse field [data.ms-graph.status] of type [keyword] ... Can't get text on a START_OBJECT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error names only the first field it trips on. A sign-in with a Conditional Access policy fails on appliedConditionalAccessPolicies; one with an empty policy list fails on status. When we fixed status alone, sign-ins that had a policy applied were still rejected.&lt;/p&gt;

&lt;p&gt;A related detail: the stock 4.14.7 ruleset (0995-microsoft-graph_rules.xml, 99 rules) has no rule for relationship: signIns. A sign-in only matches 99500 at level 0, so it reaches the indexer through the archives, or through alerts once you write your own sign-in rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix: move the object shape to a new field, in the pipeline
&lt;/h3&gt;

&lt;p&gt;Keep the string status of alerts and incidents where it is, because dashboards and saved searches expect it, and move only the sign-in shapes. One script processor, placed right after the json processor, in both the alerts and the archives pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"script"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lang"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"painless"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ignore_failure"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ms-graph signIns fix"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"def d = ctx.get('data'); if (!(d instanceof Map)) { return; } def g = d.get('ms-graph'); if (!(g instanceof Map)) { return; } if (g.get('status') instanceof Map) { g.put('signInStatus', g.remove('status')); } def p = g.get('appliedConditionalAccessPolicies'); if (p instanceof List &amp;amp;&amp;amp; !p.isEmpty() &amp;amp;&amp;amp; p.get(0) instanceof Map) { g.put('conditionalAccessPolicies', g.remove('appliedConditionalAccessPolicies')); }"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter. ignore_failure: true, because the stock pipelines end with on_failure: drop, so a processor that throws would drop the whole event. With this flag the worst case is an event indexed unchanged. And d.get('ms-graph') rather than ctx.data?.ms-graph: Painless reads the hyphen as a minus sign, the dotted form does not compile, and the indexer refuses the whole pipeline. We hit this ourselves.&lt;/p&gt;

&lt;p&gt;What we measured: every sign-in and the alert indexed, in both orders, in a new index and in an index that already existed, so the fix works on today's index. data.ms-graph.status stays keyword and still aggregates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 2: a field that is sometimes an object, sometimes a string, data.data
&lt;/h2&gt;

&lt;p&gt;Nextcloud events can carry data as an object or as an array. Our three test lines differed only in that, and all three matched rule 88207. With the stock template, where data.data is keyword, the object shape fails with failed to parse field [data.data] of type [keyword]. A separate template that makes data.data an object accepts those, and rejects the string shape instead. Changing the template alone only moves which shape fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix: a separate template plus a rename by type
&lt;/h3&gt;

&lt;p&gt;Two changes that only work together. First, a separate legacy template with a higher order that keeps the field an object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;PUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;_template/fixpack-data-data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"index_patterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"wazuh-alerts-4.x-*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"mappings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;}}}}}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, one rename processor right after the json processor, which moves the field only when it arrives as a string, so the new field only ever receives strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"rename"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"data.data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"target_field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"data.data_raw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"ignore_missing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"if"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ctx.data?.data instanceof String"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we measured: every order of object, "[]" and "[{..}]" indexed cleanly, 4 of 4 in two separate orders. The template alone, the rename alone, and a rename by location each still failed. A template only applies when an index is created, so this fix takes effect on the next daily index. Note the side effect: the rename moves every string data.data, from every source, into data.data_raw, so searches and dashboards that read data.data as a string need to follow it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of what we measured
&lt;/h2&gt;

&lt;p&gt;All of this was measured on a throwaway wazuh-indexer 4.14.7 with the stock template and the stock pipelines taken from the wazuh-manager 4.14.7 image. We sent events through the pipeline in the form Filebeat sends them, but not through a real Filebeat and not from a real manager. The sign-ins were built from the Graph v1.0 schema, not taken from a live tenant, and we have not checked fields that only the Graph beta API sends. We did not measure whether a patched pipeline survives a restart or an upgrade, or how any of this behaves on a multi-node cluster. The decoder behaviour was measured on two integrations. For the 21 scalar fields under data, we can say a clash is possible, not that it will happen. The fix template above covers wazuh-alerts-4.x-* only: we measured the alerts index, not the archives index, which the stock template also covers.&lt;/p&gt;

&lt;p&gt;Full write-up with the lab output: &lt;a href="https://atkvn.com/fix-mapper-parsing-exception.html" rel="noopener noreferrer"&gt;https://atkvn.com/fix-mapper-parsing-exception.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>siem</category>
      <category>security</category>
      <category>opensearch</category>
    </item>
    <item>
      <title>Wazuh FIM "not detecting" a changed file usually means it has not looked yet</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Wed, 07 Oct 2026 03:46:49 +0000</pubDate>
      <link>https://dev.to/xuxu298/wazuh-fim-not-detecting-a-changed-file-usually-means-it-has-not-looked-yet-5d1a</link>
      <guid>https://dev.to/xuxu298/wazuh-fim-not-detecting-a-changed-file-usually-means-it-has-not-looked-yet-5d1a</guid>
      <description>&lt;p&gt;You add a directory to Wazuh file integrity monitoring, change a file, and nothing happens. On Wazuh 4.14.7 the usual reason is the schedule, not a bug. We measured it on a manager container.&lt;/p&gt;

&lt;h2&gt;
  
  
  The defaults
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;syscheck&amp;gt;&lt;/code&gt; block in the 4.14.7 manager image's default &lt;code&gt;ossec.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;frequency&amp;gt;&lt;/span&gt;43200&lt;span class="nt"&gt;&amp;lt;/frequency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;scan_on_start&amp;gt;&lt;/span&gt;yes&lt;span class="nt"&gt;&amp;lt;/scan_on_start&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;alert_new_files&amp;gt;&lt;/span&gt;yes&lt;span class="nt"&gt;&amp;lt;/alert_new_files&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;directories&amp;gt;&lt;/span&gt;/etc,/usr/bin,/usr/sbin&lt;span class="nt"&gt;&amp;lt;/directories&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;directories&amp;gt;&lt;/span&gt;/bin,/sbin,/boot&lt;span class="nt"&gt;&amp;lt;/directories&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;43200&lt;/code&gt; seconds is 12 hours. No directory has &lt;code&gt;realtime&lt;/code&gt; or &lt;code&gt;whodata&lt;/code&gt;. So a changed file is reported at the next scheduled scan, up to 12 hours later.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we measured
&lt;/h2&gt;

&lt;p&gt;Two test directories, one plain and one with &lt;code&gt;realtime="yes"&lt;/code&gt;, files changed after the first scan:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What we did&lt;/th&gt;
&lt;th&gt;Alert&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;first scan after start, files already present&lt;/td&gt;
&lt;td&gt;none: the first scan only records a baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;changed a file in the plain directory, default 12 h interval, waited 25 s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;none&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;same, with &lt;code&gt;&amp;lt;frequency&amp;gt;60&amp;lt;/frequency&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;rule 550, level 7, mode &lt;code&gt;scheduled&lt;/code&gt;, after about 64 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;changed a file in the &lt;code&gt;realtime&lt;/code&gt; directory&lt;/td&gt;
&lt;td&gt;rule 550, level 7, mode &lt;code&gt;realtime&lt;/code&gt;, within seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;created a new file there&lt;/td&gt;
&lt;td&gt;rule 554, level 5, mode &lt;code&gt;realtime&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to check in a minute
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'(6003)'&lt;/span&gt; /var/ossec/logs/ossec.log | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 20     &lt;span class="c"&gt;# monitored paths and their options&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'\(600[89]\)'&lt;/span&gt; /var/ossec/logs/ossec.log | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 4   &lt;span class="c"&gt;# last scan start (6008) and end (6009)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your path's &lt;code&gt;(6003)&lt;/code&gt; line does not end in &lt;code&gt;realtime&lt;/code&gt; (or &lt;code&gt;whodata&lt;/code&gt;), changes wait for the next scan. If the path is not listed at all, it is not monitored.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Real-time on the paths you would actually act on, on the agent or in the group's &lt;code&gt;agent.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;syscheck&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;directories&lt;/span&gt; &lt;span class="na"&gt;realtime=&lt;/span&gt;&lt;span class="s"&gt;"yes"&lt;/span&gt; &lt;span class="na"&gt;check_all=&lt;/span&gt;&lt;span class="s"&gt;"yes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;/etc/nginx,/var/www/app/config&lt;span class="nt"&gt;&amp;lt;/directories&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/syscheck&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the restart, the &lt;code&gt;(6003)&lt;/code&gt; line for that path ends in &lt;code&gt;realtime&lt;/code&gt;, followed by &lt;code&gt;(6012): Real-time file integrity monitoring started.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Keep real-time to small, meaningful directories: real-time on large or busy trees is how FIM turns into noise. And when you test, wait for the &lt;code&gt;(6009)&lt;/code&gt; scan-ended line before changing a file, because changes before the baseline exists are not alerts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;Measured on a Wazuh 4.14.7 manager container, with &lt;code&gt;wazuh-syscheckd&lt;/code&gt; monitoring its own Linux filesystem; agents use the same module, but we did not run a separate agent. Not measured: &lt;code&gt;whodata&lt;/code&gt;, Windows, network shares, inotify limits on very large directories, real-time noise on a busy server.&lt;/p&gt;

&lt;p&gt;Full note: &lt;a href="https://atkvn.com/fix-wazuh-fim-not-detecting-file-changes.html" rel="noopener noreferrer"&gt;https://atkvn.com/fix-wazuh-fim-not-detecting-file-changes.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dong Nguyen, ATK New Technology. We check and fix Wazuh rules for people who run it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>security</category>
      <category>linux</category>
    </item>
    <item>
      <title>Sophos Firewall logs and Wazuh: the newer syslog layout matches no decoder</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Tue, 06 Oct 2026 05:03:50 +0000</pubDate>
      <link>https://dev.to/xuxu298/--25b7</link>
      <guid>https://dev.to/xuxu298/--25b7</guid>
      <description>&lt;p&gt;Sophos Firewall can send syslog in two layouts. Wazuh 4.14.7 reads one of them. If your firewall uses the other, the logs arrive and nothing alerts, ever. We measured both, and tested two fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two layouts
&lt;/h2&gt;

&lt;p&gt;Sophos's own syslog guide documents both. The legacy one, which Sophos calls &lt;em&gt;Device standard&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;device="SFW" date=2018-05-30 time=13:14:26 timezone="IST" device_name="XG125w" device_id=SFDemo-763180a log_id=010102600002 log_type="Firewall" log_component="Firewall Rule" log_subtype="Denied" status="Deny" ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;em&gt;Central reporting&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;device_name="SFW" timestamp="2023-12-11T09:02:50-0500" device_model="SF01V" device_serial_id="SFDemo-c07-gl-vm-01" log_id="010101600001" log_type="Firewall" log_component="Firewall Rule" log_subtype="Allowed" ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Wazuh does with each
&lt;/h2&gt;

&lt;p&gt;The stock decoder in &lt;code&gt;ruleset/decoders/0510-sophos_fw_decoders.xml&lt;/code&gt; is anchored on the legacy start of line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;prematch&amp;gt;&lt;/span&gt;^device="\w*"\s+date=\d+-\d+-\d+\s+time=&lt;span class="nt"&gt;&amp;lt;/prematch&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We sent a denied-traffic line in each layout over UDP syslog to a Wazuh 4.14.7 manager:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layout&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;legacy (&lt;code&gt;device="SFW" date=…&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;decoded as &lt;code&gt;sophos-fw&lt;/code&gt; → rule 70021, level 5, &lt;em&gt;Traffic Denied&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Central reporting (&lt;code&gt;device_name="SFW" timestamp=…&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No decoder matched&lt;/strong&gt; → generic rule 1002, level 2 → no alert&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The stock rules key on the decoded &lt;code&gt;status&lt;/code&gt; field, and the Central reporting layout has no &lt;code&gt;status&lt;/code&gt; at all; it carries &lt;code&gt;log_subtype&lt;/code&gt; instead. So a Central reporting line has neither the decoder nor the field the rules need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 1: send the legacy layout
&lt;/h2&gt;

&lt;p&gt;If your firewall's syslog server settings let you pick the format, choose the device-standard one and the stock decoder and rules apply unchanged. Check first whether anything else reading the same stream expects the new layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 2: decode the Central reporting layout
&lt;/h2&gt;

&lt;p&gt;A parent decoder anchored on the new start of line, then one child per field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;decoder&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sophos-fw-crf"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;prematch&amp;gt;&lt;/span&gt;^device_name="\S+" timestamp="&lt;span class="nt"&gt;&amp;lt;/prematch&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/decoder&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;decoder&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sophos-fw-crf-fields"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;parent&amp;gt;&lt;/span&gt;sophos-fw-crf&lt;span class="nt"&gt;&amp;lt;/parent&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;regex&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"pcre2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;log_type="([^"]*)" log_component="([^"]*)" log_subtype="([^"]*)"&lt;span class="nt"&gt;&amp;lt;/regex&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;order&amp;gt;&lt;/span&gt;log_type, log_component, log_subtype&lt;span class="nt"&gt;&amp;lt;/order&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/decoder&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;decoder&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"sophos-fw-crf-fields"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;parent&amp;gt;&lt;/span&gt;sophos-fw-crf&lt;span class="nt"&gt;&amp;lt;/parent&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;regex&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"pcre2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;\bsrc_ip="([^"]*)"&lt;span class="nt"&gt;&amp;lt;/regex&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;order&amp;gt;&lt;/span&gt;srcip&lt;span class="nt"&gt;&amp;lt;/order&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/decoder&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(and the same pattern for &lt;code&gt;dst_ip&lt;/code&gt;, &lt;code&gt;src_port&lt;/code&gt;, &lt;code&gt;dst_port&lt;/code&gt;, &lt;code&gt;protocol&lt;/code&gt;, &lt;code&gt;fw_rule_id&lt;/code&gt;). Rules on &lt;code&gt;log_type&lt;/code&gt; = &lt;code&gt;Firewall&lt;/code&gt; and &lt;code&gt;log_subtype&lt;/code&gt; = &lt;code&gt;Denied&lt;/code&gt; / &lt;code&gt;Allowed&lt;/code&gt;, plus an 18-in-45-seconds rule with &lt;code&gt;&amp;lt;same_source_ip /&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Writing the address as &lt;code&gt;srcip&lt;/code&gt; (the stock decoder uses &lt;code&gt;src_ip&lt;/code&gt;) matters: &lt;code&gt;&amp;lt;same_source_ip&amp;gt;&lt;/code&gt; and the &lt;code&gt;firewall-drop&lt;/code&gt; active response only read &lt;code&gt;srcip&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What we measured with it on 4.14.7:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sent&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 Central reporting denied line&lt;/td&gt;
&lt;td&gt;0 alerts&lt;/td&gt;
&lt;td&gt;level 5, srcip/dstip/dstport read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;the same, 18 times in a few seconds&lt;/td&gt;
&lt;td&gt;no alert&lt;/td&gt;
&lt;td&gt;17 × level 5, then the level 10 rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sophos's own &lt;em&gt;Allowed&lt;/em&gt; example&lt;/td&gt;
&lt;td&gt;no decoder&lt;/td&gt;
&lt;td&gt;level 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 legacy denied line&lt;/td&gt;
&lt;td&gt;70021&lt;/td&gt;
&lt;td&gt;70021, untouched&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;wazuh-analysisd -t&lt;/code&gt;: no warnings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;Measured on a Wazuh 4.14.7 manager container with syslog over UDP and in &lt;code&gt;wazuh-logtest&lt;/code&gt;. We did not run a Sophos Firewall. The &lt;em&gt;Allowed&lt;/em&gt; line is Sophos's published example; the &lt;em&gt;Denied&lt;/em&gt; lines were written by us in the same layout. The decoder covers firewall-rule events only, not admin login, IPS, web filter or VPN. We have not checked which SFOS release made Central reporting the default, or the exact menu name for the format setting.&lt;/p&gt;

&lt;p&gt;The full decoder and rule files, with the one-minute checks: &lt;a href="https://atkvn.com/fix-sophos-firewall-logs-not-showing-in-wazuh.html" rel="noopener noreferrer"&gt;https://atkvn.com/fix-sophos-firewall-logs-not-showing-in-wazuh.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dong Nguyen, ATK New Technology. We check and fix Wazuh rules for people who run it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>sophos</category>
      <category>security</category>
    </item>
    <item>
      <title>FortiGate denied traffic never reaches the Wazuh dashboard. Rule 81618 is level 1.</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Mon, 05 Oct 2026 07:02:34 +0000</pubDate>
      <link>https://dev.to/xuxu298/fortigate-denied-traffic-never-reaches-the-wazuh-dashboard-rule-81618-is-level-1-3a2k</link>
      <guid>https://dev.to/xuxu298/fortigate-denied-traffic-never-reaches-the-wazuh-dashboard-rule-81618-is-level-1-3a2k</guid>
      <description>&lt;p&gt;FortiGate sends its logs to Wazuh, failed admin logins show up, and denied traffic does not. On Wazuh 4.14.7 that is the stock ruleset working as written. We measured it, plus a second problem that only shows up with CSV syslog.&lt;/p&gt;

&lt;p&gt;Setup: FortiOS-style key=value lines sent over UDP syslog to a Wazuh 4.14.7 manager container; we read &lt;code&gt;alerts.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 1: every traffic log stops at level 1
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;ruleset/rules/0391-fortigate_rules.xml&lt;/code&gt;, rule &lt;code&gt;81618&lt;/code&gt; matches any FortiGate log with &lt;code&gt;type=traffic&lt;/code&gt; at &lt;strong&gt;level 1&lt;/strong&gt;. It does not look at &lt;code&gt;action&lt;/code&gt;, so denied and allowed sessions land on the same rule. The default &lt;code&gt;&amp;lt;log_alert_level&amp;gt;&lt;/code&gt; in &lt;code&gt;ossec.conf&lt;/code&gt; is &lt;strong&gt;3&lt;/strong&gt;, and nothing below it is written to &lt;code&gt;alerts.json&lt;/code&gt;. The only traffic rule above the line is &lt;code&gt;81619&lt;/code&gt;, level 3, after 18 traffic events from one source within 45 seconds.&lt;/p&gt;

&lt;p&gt;We sent one denied forward session (&lt;code&gt;action="deny"&lt;/code&gt;, SSH from outside to an internal host) and one &lt;em&gt;Admin login failed&lt;/em&gt; event:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;In &lt;code&gt;alerts.json&lt;/code&gt;?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;traffic, &lt;code&gt;action="deny"&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;81618&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;admin login failed&lt;/td&gt;
&lt;td&gt;81606&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Reason 2: CSV format corrupts &lt;code&gt;srcip&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The stock decoder reads key=value pairs separated by spaces. In CSV format the separator is a comma, so the capture for &lt;code&gt;srcip&lt;/code&gt; runs to the next space. The same login-failed event in CSV produced an alert whose &lt;code&gt;data.srcip&lt;/code&gt; was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="mf"&gt;203.0.113.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;dstip&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;192.168.1.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;action&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;status&lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule still fires, but GeoIP, &lt;code&gt;&amp;lt;same_source_ip&amp;gt;&lt;/code&gt; correlation and the &lt;code&gt;firewall-drop&lt;/code&gt; active response all read that field.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Send the default format&lt;/strong&gt; for the syslog server Wazuh listens on:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="n"&gt;syslogd&lt;/span&gt; &lt;span class="n"&gt;setting&lt;/span&gt;
    &lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;format&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Raise denied traffic to an alert&lt;/strong&gt; in &lt;code&gt;/var/ossec/etc/rules/local_rules.xml&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;group&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"local,fortigate,"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;rule&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"100210"&lt;/span&gt; &lt;span class="na"&gt;level=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;if_sid&amp;gt;&lt;/span&gt;81618&lt;span class="nt"&gt;&amp;lt;/if_sid&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;action&amp;gt;&lt;/span&gt;deny&lt;span class="nt"&gt;&amp;lt;/action&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;FortiGate: traffic denied by policy.&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;group&amp;gt;&lt;/span&gt;firewall_block,&lt;span class="nt"&gt;&amp;lt;/group&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/group&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On 4.14.7 the same denied line that produced nothing produced a level 5 alert with &lt;code&gt;srcip&lt;/code&gt; and &lt;code&gt;dstport&lt;/code&gt; intact. Narrow it (interface, port, destination) if an internet-facing firewall makes it too loud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use the &lt;code&gt;&amp;lt;action&amp;gt;&lt;/code&gt; tag, not &lt;code&gt;&amp;lt;field name="action"&amp;gt;&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;action&lt;/code&gt; is a static field. With the &lt;code&gt;&amp;lt;field&amp;gt;&lt;/code&gt; form, 4.14.7 logged &lt;code&gt;ERROR: Failure to read rule 100210. Field 'action' is static.&lt;/code&gt; and the manager did not start, so there were no alerts at all, not only for FortiGate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;Measured on a Wazuh 4.14.7 manager container (syslog over UDP 514, and &lt;code&gt;wazuh-logtest&lt;/code&gt;). We did not run a FortiGate: the lines were written in the FortiOS key=value layout and converted to CSV by changing the separator. Not measured: &lt;code&gt;cef&lt;/code&gt; and &lt;code&gt;rfc5424&lt;/code&gt; formats, UTM/VPN/IPS logs, other versions. The CLI syntax is from Fortinet's &lt;em&gt;config log syslogd setting&lt;/em&gt; reference, not run on a device.&lt;/p&gt;

&lt;p&gt;Full note with the one-minute checks: &lt;a href="https://atkvn.com/fix-fortigate-logs-not-showing-in-wazuh.html" rel="noopener noreferrer"&gt;https://atkvn.com/fix-fortigate-logs-not-showing-in-wazuh.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dong Nguyen, ATK New Technology. We check and fix Wazuh rules for people who run it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>fortinet</category>
      <category>security</category>
    </item>
    <item>
      <title>A single pfSense block never becomes a Wazuh alert. Here is why, measured.</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Sun, 04 Oct 2026 01:08:44 +0000</pubDate>
      <link>https://dev.to/xuxu298/a-single-pfsense-block-never-becomes-a-wazuh-alert-here-is-why-measured-563m</link>
      <guid>https://dev.to/xuxu298/a-single-pfsense-block-never-becomes-a-wazuh-alert-here-is-why-measured-563m</guid>
      <description>&lt;p&gt;You point pfSense at Wazuh, the logs arrive, and the dashboard stays empty. Most people assume the integration is broken. On Wazuh 4.14.7 it usually is not: there are two separate reasons, and one of them is by design.&lt;/p&gt;

&lt;p&gt;We sent pfSense &lt;code&gt;filterlog&lt;/code&gt; lines over UDP syslog to a Wazuh 4.14.7 manager container and read &lt;code&gt;archives.log&lt;/code&gt; and &lt;code&gt;alerts.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 1: rule 87701 carries &lt;code&gt;no_log&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The stock pfSense rules live in &lt;code&gt;ruleset/rules/0540-pfsense_rules.xml&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Matches&lt;/th&gt;
&lt;th&gt;Becomes an alert?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;87700&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;any &lt;code&gt;filterlog&lt;/code&gt; event decoded as &lt;code&gt;pf&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;no (level 0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;87701&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;action&lt;/code&gt; = block&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;no&lt;/strong&gt;: it has &lt;code&gt;&amp;lt;options&amp;gt;no_log&amp;lt;/options&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;87702&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;18 × 87701 from one source IP within 45 s&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The comment above 87701 says: &lt;em&gt;"We don't log firewall events, because they go to their own log file."&lt;/em&gt; Pass events stop at 87700.&lt;/p&gt;

&lt;p&gt;What we measured:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One block line over UDP 514: it reached &lt;code&gt;archives.log&lt;/code&gt;; &lt;code&gt;alerts.json&lt;/code&gt; stayed empty.&lt;/li&gt;
&lt;li&gt;18 block lines from one address within the window: the 18th came back as 87702, level 10.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So a working integration shows nothing until one address is blocked 18 times in 45 seconds.&lt;/p&gt;

&lt;p&gt;One trap while testing: &lt;code&gt;wazuh-logtest&lt;/code&gt; prints &lt;em&gt;"Alert to be generated"&lt;/em&gt; for 87701 anyway. Logtest does not apply &lt;code&gt;no_log&lt;/code&gt;. Check &lt;code&gt;alerts.json&lt;/code&gt;, not logtest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reason 2: RFC 5424 lines match no decoder
&lt;/h2&gt;

&lt;p&gt;pfSense can log in BSD (RFC 3164) or RFC 5424 format. The stock decoder is keyed on &lt;code&gt;&amp;lt;program_name&amp;gt;filterlog&amp;lt;/program_name&amp;gt;&lt;/code&gt;, which Wazuh's pre-decoder reads from a BSD-style header. Over UDP on 4.14.7:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line sent&lt;/th&gt;
&lt;th&gt;Decoder&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Oct  2 15:00:01 pfSense filterlog[12345]: 5,,,1000000103,igb1,match,block,…&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pf&lt;/code&gt;: srcip, dstip, action read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2026-10-02T15:00:01.123456+07:00 pfSense.home.arpa filterlog[12345]: …&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pf&lt;/code&gt;: read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;134&amp;gt;1 2026-10-02T15:00:01.123456+07:00 pfSense.home.arpa filterlog 12345 - - …&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No decoder matched&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With RFC 5424, &lt;code&gt;archives.log&lt;/code&gt; even shows the manager's own host name instead of the firewall's: the header was never parsed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Send BSD format.&lt;/strong&gt; In pfSense, &lt;em&gt;Status → System Logs → Settings&lt;/em&gt;, set the log message format to BSD (RFC 3164).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decide which blocks you want as alerts.&lt;/strong&gt; To alert on every block, overwrite 87701 without &lt;code&gt;no_log&lt;/code&gt; in &lt;code&gt;/var/ossec/etc/rules/local_rules.xml&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;group&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"local,pfsense,"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;rule&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"87701"&lt;/span&gt; &lt;span class="na"&gt;level=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt; &lt;span class="na"&gt;overwrite=&lt;/span&gt;&lt;span class="s"&gt;"yes"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;if_sid&amp;gt;&lt;/span&gt;87700&lt;span class="nt"&gt;&amp;lt;/if_sid&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;action&amp;gt;&lt;/span&gt;block&lt;span class="nt"&gt;&amp;lt;/action&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;pfSense firewall drop event.&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;group&amp;gt;&lt;/span&gt;firewall_block,pci_dss_1.4,gpg13_4.12,hipaa_164.312.a.1,nist_800_53_SC.7,tsc_CC6.7,tsc_CC6.8,&lt;span class="nt"&gt;&amp;lt;/group&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/rule&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/group&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On 4.14.7 this loaded with no warnings, and the same single block line that produced nothing produced one 87701 alert. An internet-facing firewall blocks a lot, so many people prefer a narrower child rule (one interface, one port) and leave 87701 alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;Measured on a Wazuh 4.14.7 manager container, with syslog over UDP from 127.0.0.1 and in &lt;code&gt;wazuh-logtest&lt;/code&gt;. We did not run a pfSense box: the lines were written in the &lt;code&gt;filterlog&lt;/code&gt; format for one IPv4 TCP block and one pass. Not measured: IPv6, other pfSense programs, OPNsense, other Wazuh versions.&lt;/p&gt;

&lt;p&gt;Full note with the one-minute checks: &lt;a href="https://atkvn.com/fix-pfsense-logs-not-showing-in-wazuh.html" rel="noopener noreferrer"&gt;https://atkvn.com/fix-pfsense-logs-not-showing-in-wazuh.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dong Nguyen, ATK New Technology. We check and fix Wazuh rules for people who run it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>pfsense</category>
      <category>security</category>
    </item>
    <item>
      <title>Wazuh 5.0 will not load your custom XML rules. 82% of one public pack depends on what it drops.</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Sat, 03 Oct 2026 08:58:04 +0000</pubDate>
      <link>https://dev.to/xuxu298/wazuh-50-will-not-load-your-custom-xml-rules-82-of-one-public-pack-depends-on-what-it-drops-1h01</link>
      <guid>https://dev.to/xuxu298/wazuh-50-will-not-load-your-custom-xml-rules-82-of-one-public-pack-depends-on-what-it-drops-1h01</guid>
      <description>&lt;p&gt;If you run Wazuh 4.x with your own rules and decoders, the 5.0 release changes more than the version number. We read the migration guides that ship with the latest 5.0 pre-release (&lt;code&gt;v5.0.0-beta5&lt;/code&gt;) and ran the numbers on a public community ruleset. Here is what applies to custom content, with the source for each point.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Custom XML files are not loaded at all.&lt;/strong&gt; From &lt;code&gt;docs/guide/migration/manager-configuration-migration.md&lt;/code&gt; at &lt;code&gt;v5.0.0-beta5&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Custom rules and decoders from 4.x &lt;strong&gt;cannot&lt;/strong&gt; be migrated by copying XML files to the manager.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The same table lists &lt;code&gt;&amp;lt;ruleset&amp;gt;&lt;/code&gt; as a &lt;strong&gt;startup error&lt;/strong&gt; and says &lt;code&gt;etc/rules/&lt;/code&gt;, &lt;code&gt;etc/decoders/&lt;/code&gt; and &lt;code&gt;etc/lists/&lt;/code&gt; "do not exist in 5.0". Decoders become YAML assets run by the new engine; rules become Sigma-style YAML documents stored in the indexer and managed through its content API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is no converter.&lt;/strong&gt; From &lt;code&gt;docs/guide/migration/rules-4x-to-5x.md&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is no automatic conversion tool. Rules must be manually rewritten following this guide.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The manager migration issue (#39109, closed 28 September) states the scope plainly: "rules, decoders, CDB lists and anything the engine changed structurally" are out of scope, and "the translation is the user's responsibility". The tool merged for it (PR #39197) moves agent keys, groups and API users, not content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rules no longer chain.&lt;/strong&gt; The rules guide marks these as not supported:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;4.x&lt;/th&gt;
&lt;th&gt;5.0 (per the guide)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;if_sid&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;if_group&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;if_level&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;not supported; "rules are self-contained"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;if_matched_sid&amp;gt;&lt;/code&gt;, &lt;code&gt;frequency&lt;/code&gt;/&lt;code&gt;timeframe&lt;/code&gt;, &lt;code&gt;&amp;lt;same_*&amp;gt;&lt;/code&gt;/&lt;code&gt;&amp;lt;different_*&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;if_fts&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;not supported in rules; "correlation handled separately"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;list&amp;gt;&lt;/code&gt; (CDB lookups)&lt;/td&gt;
&lt;td&gt;not supported in rules; move to KVDBs at the decoder level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;time&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;weekday&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;check_diff&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;options&amp;gt;&lt;/code&gt; (&lt;code&gt;no_log&lt;/code&gt;, &lt;code&gt;no_full_log&lt;/code&gt;, …), &lt;code&gt;&amp;lt;var&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;no direct equivalent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And &lt;code&gt;&amp;lt;match&amp;gt;&lt;/code&gt;/&lt;code&gt;&amp;lt;regex&amp;gt;&lt;/code&gt; no longer search the raw log: 5.0 rules match fields of the normalized event, with &lt;code&gt;event.original&lt;/code&gt; as the fallback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decoders lose some elements.&lt;/strong&gt; The decoder guide lists &lt;code&gt;&amp;lt;plugin_decoder&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;accumulate/&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;fts&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;json_null_field&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;type&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;use_own_name&amp;gt;&lt;/code&gt; and the &lt;code&gt;pcre2&lt;/code&gt;/&lt;code&gt;offset&lt;/code&gt; options as unsupported: 5.0 parses with its own field-extraction language, not regex.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much of a real ruleset is affected
&lt;/h2&gt;

&lt;p&gt;We took the public SOCFortress &lt;code&gt;Wazuh-Rules&lt;/code&gt; set (75 XML files, 2,211 rules, 105 decoders) and counted, rule by rule, the elements the 5.0 guide marks as unsupported:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Rules&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Use rule chaining (&lt;code&gt;if_sid&lt;/code&gt;, &lt;code&gt;if_group&lt;/code&gt; or &lt;code&gt;if_level&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;1,804&lt;/td&gt;
&lt;td&gt;82%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use correlation (&lt;code&gt;frequency&lt;/code&gt;, &lt;code&gt;if_matched_*&lt;/code&gt;, &lt;code&gt;same_*&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&amp;lt;1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use CDB lookups or &lt;code&gt;check_diff&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&amp;lt;1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;None of the above (only a rewrite to the new format)&lt;/td&gt;
&lt;td&gt;399&lt;/td&gt;
&lt;td&gt;18%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Chaining is how most 4.x content is written: a child rule refines a stock parent. In 5.0 each of those rules has to carry its own full condition, written against normalized field names instead of a parent's decoded fields.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where things stand
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;v5.0.0-beta5&lt;/code&gt; (1 September) is the latest 5.0 tag. The &lt;code&gt;5.0.0&lt;/code&gt; branch already reads &lt;code&gt;"stage": "rc1"&lt;/code&gt;. No GA date has been announced.&lt;/li&gt;
&lt;li&gt;There is no in-place upgrade from a 4.x manager; the configuration guide says 5.0 is a fresh install.&lt;/li&gt;
&lt;li&gt;Until you move, 4.x keeps loading XML as before, including its quirks: if you are still writing 4.x rules, check them before restarting (one quirk we measured: &lt;a href="https://dev.to/xuxu298/wazuh-analysisd-shows-at-most-50-load-warnings-the-rest-of-your-ignored-rules-are-silent-68i"&gt;wazuh-analysisd shows at most 50 load warnings&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Measure your own
&lt;/h2&gt;

&lt;p&gt;The free checker at &lt;a href="https://atkvn.com/wazuh-rule-precheck.html" rel="noopener noreferrer"&gt;https://atkvn.com/wazuh-rule-precheck.html&lt;/a&gt; runs in the browser and checks 4.x load problems. The counts above came from the same code with a 5.0 mode that lists, per rule, the elements the 5.0 guide marks as unsupported. If you want that list for your own rules before planning a move, send them and your Wazuh version to the address on the checker page and we will send it back, free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;Everything about 5.0 here comes from the migration guides at &lt;code&gt;v5.0.0-beta5&lt;/code&gt; and the linked issue and PR, not from running a 5.0 engine. A pre-release can change before GA. The counts are a static reading of the XML.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dong Nguyen, ATK New Technology. We check and fix Wazuh rules for people who run it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>security</category>
    </item>
  </channel>
</rss>
