<?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>Wazuh custom rule never fires, and analysisd -t still exits 0: the file name decides</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Sat, 26 Sep 2026 06:21:21 +0000</pubDate>
      <link>https://dev.to/xuxu298/wazuh-custom-rule-never-fires-and-analysisd-t-still-exits-0-the-file-name-decides-58od</link>
      <guid>https://dev.to/xuxu298/wazuh-custom-rule-never-fires-and-analysisd-t-still-exits-0-the-file-name-decides-58od</guid>
      <description>&lt;p&gt;You write a child rule for a stock Wazuh rule, drop it into &lt;code&gt;/var/ossec/etc/rules/&lt;/code&gt;, and run the config check. &lt;code&gt;wazuh-analysisd -t&lt;/code&gt; exits 0. You restart the manager. The rule never fires, and &lt;code&gt;wazuh-logtest&lt;/code&gt; shows the event landing on the parent instead.&lt;/p&gt;

&lt;p&gt;One common cause has nothing to do with the rule itself. It is the name of the file you put it in.&lt;/p&gt;

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

&lt;p&gt;On a throwaway &lt;code&gt;wazuh-manager:4.14.7&lt;/code&gt; container we used one test rule, a child of the stock sshd rule &lt;code&gt;5715&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;group&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"test,"&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;"9"&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;5715&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;test child of 5715&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;&lt;code&gt;5715&lt;/code&gt; lives in &lt;code&gt;/var/ossec/ruleset/rules/0095-sshd_rules.xml&lt;/code&gt;. We put the same rule in five differently named files in &lt;code&gt;/var/ossec/etc/rules/&lt;/code&gt;, ran &lt;code&gt;wazuh-analysisd -t&lt;/code&gt;, restarted, and sent one line through &lt;code&gt;wazuh-logtest&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Sep 26 10:00:00 host1 sshd[1234]: Accepted password for root from 203.0.113.5 port 22 ssh2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;file&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;-t&lt;/code&gt; exit&lt;/th&gt;
&lt;th&gt;7617 / 7619 warnings&lt;/th&gt;
&lt;th&gt;logtest lands on&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0094-test.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;5715&lt;/code&gt; (our rule ignored)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0096-test.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;100080&lt;/code&gt; (fires)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;local_rules.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;100080&lt;/code&gt; (fires)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0095-aaa.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;5715&lt;/code&gt; (our rule ignored)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0095-zzz.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;100080&lt;/code&gt; (fires)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two warnings, word for word:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WARNING: (7617): Signature ID '5715' was not found and will be ignored in the 'if_sid' option of rule '100080'.
WARNING: (7619): Empty 'if_sid' value. Rule '100080' will be ignored.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it means
&lt;/h2&gt;

&lt;p&gt;Files in &lt;code&gt;etc/rules&lt;/code&gt; are not loaded after the stock ruleset. They are merged with &lt;code&gt;ruleset/rules&lt;/code&gt; and loaded in order of the &lt;strong&gt;full file name&lt;/strong&gt;. The last two rows show it: &lt;code&gt;0095-aaa.xml&lt;/code&gt; sorts before &lt;code&gt;0095-sshd_rules.xml&lt;/code&gt;, so when our rule is read, &lt;code&gt;5715&lt;/code&gt; does not exist yet and the rule is dropped. &lt;code&gt;0095-zzz.xml&lt;/code&gt; sorts after it, and the same rule fires.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;local_rules.xml&lt;/code&gt; is safe for this reason: letters sort after digits, and all 168 stock rule files in 4.14.7 start with a digit (&lt;code&gt;0010&lt;/code&gt; to &lt;code&gt;0999&lt;/code&gt;), so it loads after every one of them.&lt;/p&gt;

&lt;p&gt;The trap is the config check. &lt;code&gt;wazuh-analysisd -t&lt;/code&gt; exits 0 in all five cases. It prints the two warnings, but a warning is not a failure, and nothing in a deploy script that checks the exit code will stop. The manager then starts cleanly with one rule fewer than you think.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check yours in a minute
&lt;/h2&gt;

&lt;p&gt;After a restart, the warnings are also in the manager log:&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;-E&lt;/span&gt; &lt;span class="s1"&gt;'\((7617|7619)\)'&lt;/span&gt; /var/ossec/logs/ossec.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every rule id you see in a 7619 line is a rule that is not running. The 7617 line on the same second tells you which parent it was waiting for.&lt;/p&gt;

&lt;p&gt;Before a restart, run the check yourself and read its output instead of trusting the exit code:&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; 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="s1"&gt;'\((7617|7619)\)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a single rule, &lt;code&gt;wazuh-logtest&lt;/code&gt; with a real sample line is the final word: if the output shows the parent's id and not yours, your rule did not load or did not match.&lt;/p&gt;

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

&lt;p&gt;Move the child rule into a file whose name sorts &lt;strong&gt;after&lt;/strong&gt; the file that holds its parent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;put it in &lt;code&gt;local_rules.xml&lt;/code&gt;, or&lt;/li&gt;
&lt;li&gt;give your file a prefix above the parent's file (&lt;code&gt;0096-...&lt;/code&gt; or higher for children of sshd rules), or a name that starts with a letter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To find the parent's file:&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;-l&lt;/span&gt; &lt;span class="s1"&gt;'id="5715"'&lt;/span&gt; /var/ossec/ruleset/rules/&lt;span class="k"&gt;*&lt;/span&gt;.xml /var/ossec/etc/rules/&lt;span class="k"&gt;*&lt;/span&gt;.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the check again and confirm the 7619 line is gone, and that logtest lands on your rule.&lt;/p&gt;

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

&lt;p&gt;One Wazuh version (4.14.7), a single manager, one parent rule, and only &lt;code&gt;if_sid&lt;/code&gt;. We did not test &lt;code&gt;if_matched_sid&lt;/code&gt;, &lt;code&gt;if_group&lt;/code&gt;, decoders, or a cluster. If you run a cluster, check the warnings on every node.&lt;/p&gt;

&lt;p&gt;Credit to Francisco Sousa, who found the file name half of this in his own container before we did.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If one of your rules loads and never fires and you would rather not dig into it yourself, send us the rule, your Wazuh version and a sample event: we find why, then write and test the fix on that version. USD 490 per rule case, paid only after it runs clean on your side. &lt;a href="https://vct.atkvn.com/#fix-pack" rel="noopener noreferrer"&gt;vct.atkvn.com/#fix-pack&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dong Nguyen, ATK New Technology&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>detectionengineering</category>
      <category>security</category>
      <category>linux</category>
    </item>
    <item>
      <title>Wazuh ms-graph: why Entra ID sign-ins never reach the indexer, and a one-processor fix</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Sat, 26 Sep 2026 02:58:36 +0000</pubDate>
      <link>https://dev.to/xuxu298/wazuh-ms-graph-why-entra-id-sign-ins-never-reach-the-indexer-and-a-one-processor-fix-596k</link>
      <guid>https://dev.to/xuxu298/wazuh-ms-graph-why-entra-id-sign-ins-never-reach-the-indexer-and-a-one-processor-fix-596k</guid>
      <description>&lt;p&gt;You enable the Wazuh &lt;code&gt;ms-graph&lt;/code&gt; module for &lt;code&gt;auditLogs/signIns&lt;/code&gt;. The module logs clean scans. With &lt;code&gt;wazuh_modules.debug=2&lt;/code&gt; it even logs &lt;code&gt;Sending log: {"integration":"ms-graph", ... "relationship":"signIns"}&lt;/code&gt;. And yet there is not a single sign-in in &lt;code&gt;wazuh-alerts-*&lt;/code&gt; or &lt;code&gt;wazuh-archives-*&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are two separate reasons, and on a stock install you hit both.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The stock ruleset has no rule for sign-ins
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;0995-microsoft-graph_rules.xml&lt;/code&gt; in 4.14.7 has 99 rules. The parent rule &lt;code&gt;99500&lt;/code&gt; (level 0) catches every ms-graph event. Its children cover alerts, incidents, audit events, devices, apps and risk detections. None matches &lt;code&gt;relationship: signIns&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So on the stock ruleset a sign-in stops at level 0 and never becomes an alert. It can only reach the indexer through the archives (&lt;code&gt;&amp;lt;logall_json&amp;gt;yes&amp;lt;/logall_json&amp;gt;&lt;/code&gt;). That is where the second problem waits.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The stock template rejects every sign-in
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;wazuh-template.json&lt;/code&gt; applies to both &lt;code&gt;wazuh-alerts-4.x-*&lt;/code&gt; and &lt;code&gt;wazuh-archives-4.x-*&lt;/code&gt;. It maps two ms-graph fields as &lt;code&gt;keyword&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;field&lt;/th&gt;
&lt;th&gt;template&lt;/th&gt;
&lt;th&gt;alerts_v2 / incidents send&lt;/th&gt;
&lt;th&gt;signIns send&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data.ms-graph.status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;keyword&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a string (&lt;code&gt;"resolved"&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;an object (&lt;code&gt;{errorCode, failureReason, additionalDetails}&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data.ms-graph.appliedConditionalAccessPolicies&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;keyword&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;not sent&lt;/td&gt;
&lt;td&gt;a list of objects&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A &lt;code&gt;keyword&lt;/code&gt; field cannot hold an object, so the indexer rejects the document:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Three details that make this harder to see than it should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It does not depend on which event arrives first.&lt;/strong&gt; "First shape wins" only holds for unmapped fields. These two are mapped explicitly, so every object-shaped sign-in is rejected, in any order, even if &lt;code&gt;signIns&lt;/code&gt; is the only relationship you enabled. We checked the template at tags &lt;code&gt;v4.8.0&lt;/code&gt;, &lt;code&gt;v4.10.0&lt;/code&gt;, &lt;code&gt;v4.12.0&lt;/code&gt;, &lt;code&gt;v4.14.0&lt;/code&gt;, &lt;code&gt;v4.14.3&lt;/code&gt; and &lt;code&gt;v4.14.7&lt;/code&gt;: the same in all six.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The error names only the first bad field.&lt;/strong&gt; A sign-in with at least one Conditional Access policy fails on &lt;code&gt;appliedConditionalAccessPolicies&lt;/code&gt;. One with an empty policy list fails on &lt;code&gt;status&lt;/code&gt;. Fix &lt;code&gt;status&lt;/code&gt; alone and the sign-ins that had a policy applied are still rejected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing on the manager says so.&lt;/strong&gt; &lt;code&gt;ossec.log&lt;/code&gt; is clean and &lt;code&gt;archives.json&lt;/code&gt; has the event. The rejection happens inside the indexer, after Filebeat has shipped the event. One of the reporters found nothing in the Filebeat log either.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the sign-in is missing from &lt;code&gt;archives.json&lt;/code&gt; itself, you have a different problem, upstream of the indexer. Look at &lt;code&gt;time_delay&lt;/code&gt; first: sign-in records show up in Graph with a delay, and a scan window that closes at "now" can miss them. Issue 39460 in wazuh/wazuh has both problems, one after the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check yours in a minute
&lt;/h2&gt;

&lt;p&gt;On the stock template timing does not matter, so the mapping tells you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;GET wazuh-archives-*,wazuh-alerts-*/_mapping/field/data.ms-graph.status,data.ms-graph.appliedConditionalAccessPolicies
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If either field comes back &lt;code&gt;"type": "keyword"&lt;/code&gt; and you have &lt;code&gt;signIns&lt;/code&gt; enabled, every sign-in that reaches the indexer is rejected: through the archives, or through the alerts index once you add your own sign-in rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: one &lt;code&gt;script&lt;/code&gt; processor, by type
&lt;/h2&gt;

&lt;p&gt;The idea is to leave the string &lt;code&gt;status&lt;/code&gt; of alerts and incidents where it is, because dashboards and saved searches on &lt;code&gt;data.ms-graph.status&lt;/code&gt; expect that string. Only the sign-in shapes move to new fields. The rules are not affected either way: they run on the manager, before the indexer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt;, when it is an object → &lt;code&gt;data.ms-graph.signInStatus&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;appliedConditionalAccessPolicies&lt;/code&gt;, when it is a list of objects → &lt;code&gt;data.ms-graph.conditionalAccessPolicies&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is one &lt;code&gt;script&lt;/code&gt; processor (&lt;code&gt;FIX&lt;/code&gt; in the script at the end), right after the &lt;code&gt;json&lt;/code&gt; processor, in the &lt;strong&gt;alerts and the archives&lt;/strong&gt; pipeline. Two details matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ignore_failure: true&lt;/code&gt;. The stock Wazuh pipelines end with &lt;code&gt;on_failure: drop&lt;/code&gt;, so a processor that throws does not just skip its own step; the whole event is dropped. With &lt;code&gt;ignore_failure&lt;/code&gt;, the worst case is an event indexed unchanged.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;d.get('ms-graph')&lt;/code&gt;, not &lt;code&gt;ctx.data?.ms-graph&lt;/code&gt;. Painless reads the hyphen as a minus sign, the dotted form does not compile, and the indexer refuses the whole pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two new fields get a dynamic mapping on first use (&lt;code&gt;signInStatus.errorCode&lt;/code&gt; becomes &lt;code&gt;long&lt;/code&gt;, the text fields &lt;code&gt;keyword&lt;/code&gt;). No template change is needed, and the fix works on today's index too; you do not wait for the next daily index.&lt;/p&gt;

&lt;p&gt;The script at the end of this post does this for you, with a backup and a rollback:&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;export &lt;/span&gt;&lt;span class="nv"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://&amp;lt;indexer&amp;gt;:9200 &lt;span class="nv"&gt;AUTH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;user&amp;gt;:&amp;lt;password&amp;gt;'&lt;/span&gt;
python3 msgraph_signins_fix.py verify     &lt;span class="c"&gt;# 4 test events through your live pipelines&lt;/span&gt;
python3 msgraph_signins_fix.py apply      &lt;span class="c"&gt;# saves both pipelines to ./backup, installs the processor&lt;/span&gt;
python3 msgraph_signins_fix.py verify     &lt;span class="c"&gt;# expect 0 problems&lt;/span&gt;
python3 msgraph_signins_fix.py rollback   &lt;span class="c"&gt;# restores ./backup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What we measured, and what we did not
&lt;/h2&gt;

&lt;p&gt;Measured on a throwaway &lt;code&gt;wazuh-indexer:4.14.7&lt;/code&gt; with the stock template and both stock pipelines, taken from the &lt;code&gt;wazuh-manager:4.14.7&lt;/code&gt; image. We sent Graph v1.0-shaped sign-ins, with and without a Conditional Access policy, plus an &lt;code&gt;alerts_v2&lt;/code&gt; alert, the way Filebeat sends them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stock: every sign-in rejected, in both pipelines and in both arrival orders; the alert indexes fine&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt; fixed alone: sign-ins that had a policy applied still rejected, now on the second field&lt;/li&gt;
&lt;li&gt;the processor: every sign-in and the alert indexed, in both orders, in a fresh index and in an index that already existed; &lt;code&gt;data.ms-graph.status&lt;/code&gt; stays &lt;code&gt;keyword&lt;/code&gt; and still aggregates; events without &lt;code&gt;data&lt;/code&gt; are untouched&lt;/li&gt;
&lt;li&gt;the script: &lt;code&gt;verify&lt;/code&gt; 4 errors → &lt;code&gt;apply&lt;/code&gt; → &lt;code&gt;verify&lt;/code&gt; 0 of 8 (including a non-ms-graph event) → a second &lt;code&gt;apply&lt;/code&gt; skips → &lt;code&gt;rollback&lt;/code&gt; restores both pipelines byte for byte → &lt;code&gt;verify&lt;/code&gt; 4 errors again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not measured: a real tenant, the Graph beta API (it adds sign-in fields we have not checked against the template), a real Filebeat in the path, and whether the patch survives a restart or upgrade. Two things to check on your side:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If your &lt;code&gt;filebeat.yml&lt;/code&gt; sets &lt;code&gt;filebeat.overwrite_pipelines: true&lt;/code&gt;, Filebeat reloads the pipeline from disk and the patch has to go into the pipeline file instead.&lt;/li&gt;
&lt;li&gt;After a Wazuh upgrade, the pipeline id can change. Run &lt;code&gt;verify&lt;/code&gt; again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And to get sign-in &lt;strong&gt;alerts&lt;/strong&gt; rather than archives only, you still need your own rule on &lt;code&gt;ms-graph.relationship&lt;/code&gt; = &lt;code&gt;signIns&lt;/code&gt;. The stock ruleset does not ship one.&lt;/p&gt;

&lt;p&gt;Credit to the reporters of wazuh/wazuh issues 38330 and 39460: the first described the collision, the second narrowed the loss down to the indexer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The script
&lt;/h2&gt;

&lt;p&gt;Save it as &lt;code&gt;msgraph_signins_fix.py&lt;/code&gt;. Standard library only; it changes nothing but the two pipelines, and &lt;code&gt;verify&lt;/code&gt; writes only to indices dated &lt;code&gt;2099.01.01&lt;/code&gt;, then deletes them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
# Wazuh ms-graph sign-in fix: verify | apply | rollback. ATK New Technology, vct.atkvn.com/#fix-pack
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AUTH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AUTH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TAG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ms-graph signIns fix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;SRC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;def d = ctx.get(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;); if (!(d instanceof Map)) { return; } &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
       &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;def g = d.get(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ms-graph&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;); if (!(g instanceof Map)) { return; } &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
       &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;if (g.get(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;) instanceof Map) { g.put(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;signInStatus&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, g.remove(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)); } &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
       &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;def p = g.get(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;appliedConditionalAccessPolicies&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;); &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
       &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;if (p instanceof List &amp;amp;&amp;amp; !p.isEmpty() &amp;amp;&amp;amp; p.get(0) instanceof Map) &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
       &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{ g.put(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;conditionalAccessPolicies&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, g.remove(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;appliedConditionalAccessPolicies&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)); }&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;FIX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;script&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lang&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;painless&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SRC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ignore_failure&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;es&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                                 &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;AUTH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Basic &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AUTH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# stock Wazuh indexers use self-signed certificates
&lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_create_unverified_context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pipelines&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;es&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/_ingest/pipeline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cannot list pipelines: HTTP %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alerts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;archives&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;allp&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filebeat-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-wazuh-%s-pipeline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no filebeat-*-wazuh-%s-pipeline found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;allp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;pipelines&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;procs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;processors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TAG&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;script&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;procs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;already patched&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;procs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%s has no json processor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;es&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PUT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/_ingest/pipeline/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;processors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;procs&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;FIX&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;procs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]))&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;pipelines&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;restored, HTTP&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;es&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PUT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/_ingest/pipeline/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)))[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no backup, left as is&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;  &lt;span class="c1"&gt;# writes only to indices dated 2099.01.01, then deletes them
&lt;/span&gt;    &lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;integration&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ms-graph&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ms-graph&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;errorCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50126&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failureReason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid username or password.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;pol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;displayName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notApplied&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
    &lt;span class="n"&gt;cases&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;other event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;srcip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;203.0.113.7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
             &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alerts_v2, string status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resolved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alerts_v2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
             &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signIns + policies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appliedConditionalAccessPolicies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signIns&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
             &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signIns, no policies&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;appliedConditionalAccessPolicies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;signIns&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
    &lt;span class="n"&gt;bad&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;pipelines&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;pre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wazuh-%s-4.x-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2099-01-01T00:00:00.000+0000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rule&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;99500&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;es&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/%sverify/_doc?pipeline=%s&amp;amp;refresh=true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                         &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;index_prefix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;
            &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2099.01.01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;bad&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%-8s %-26s %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERROR %s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="nf"&gt;es&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DELETE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/%s2099.01.01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;problem(s)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bad&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verify&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apply&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rollback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usage: URL=https://&amp;lt;indexer&amp;gt;:9200 AUTH=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;user&amp;gt;:&amp;lt;password&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; %s verify|apply|rollback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nf"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;]()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;If you would rather not patch this yourself, we write and test the fix for your exact Wazuh version and send you the files: USD 490, paid only after it runs clean on your side. &lt;a href="https://vct.atkvn.com/#fix-pack" rel="noopener noreferrer"&gt;vct.atkvn.com/#fix-pack&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dong Nguyen, ATK New Technology&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>microsoft365</category>
      <category>opensearch</category>
    </item>
    <item>
      <title>One field path, two shapes: how Wazuh alerts get dropped with a 400 nobody sees</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Thu, 24 Sep 2026 11:12:36 +0000</pubDate>
      <link>https://dev.to/xuxu298/one-field-path-two-shapes-how-wazuh-alerts-get-dropped-with-a-400-nobody-sees-1flf</link>
      <guid>https://dev.to/xuxu298/one-field-path-two-shapes-how-wazuh-alerts-get-dropped-with-a-400-nobody-sees-1flf</guid>
      <description>&lt;p&gt;In the last 24 hours, two people running Wazuh 4.14.7 in production hit the same defect from different directions, and neither of them saw it on a dashboard. They found it by going looking in a log.&lt;/p&gt;

&lt;p&gt;The defect is simple to state. The alerts index template gives each field path one type. Some integrations send more than one shape of data under the same path. When an event arrives in the shape the template did not expect, the indexer rejects that document with &lt;code&gt;mapper_parsing_exception&lt;/code&gt;, Filebeat gets a 400 back, and a 400 is not the kind of error a shipper retries. The event is gone. Nothing alerts on it, because the thing that would alert is the thing that never got stored.&lt;/p&gt;

&lt;p&gt;Three paths so far. &lt;code&gt;data.ms-graph.status&lt;/code&gt; is mapped as &lt;code&gt;keyword&lt;/code&gt;, and some Microsoft Graph events send an object there. &lt;code&gt;data.office365.ModifiedProperties&lt;/code&gt; is also &lt;code&gt;keyword&lt;/code&gt;, and some Office 365 events send an object; one operator counted 35 of those in a single Filebeat log. The third runs the other way: an override that mapped &lt;code&gt;data.data&lt;/code&gt; as an object then met events that send the string &lt;code&gt;"[]"&lt;/code&gt; in the same place.&lt;/p&gt;

&lt;p&gt;The useful part is that this is one class of bug, not three. Any path that can carry two shapes will drop the minority shape silently, so the audit is a count of failures by field name, not a hunt for a specific field:&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;An empty result is only meaningful if Filebeat logs at a level that records the rejection, so check that one known-bad event shows up before trusting a zero.&lt;/p&gt;

&lt;p&gt;On the fix, put the override in a template of its own with a higher &lt;code&gt;order&lt;/code&gt; instead of editing the &lt;code&gt;wazuh&lt;/code&gt; template, which the documented upgrade procedure reinstalls. On 4.14.7 two legacy templates with the same pattern merge and the higher &lt;code&gt;order&lt;/code&gt; wins the field; that part I measured. Remember a template only applies when an index is created, so today's index keeps the old mapping and keeps dropping until tomorrow's index exists. And before you flip a path from &lt;code&gt;keyword&lt;/code&gt; to &lt;code&gt;object&lt;/code&gt;, run an &lt;code&gt;exists&lt;/code&gt; query on it: every hit is an event that indexed fine as a string, and flipping the type moves the 400s onto those. If both shapes are real, &lt;code&gt;enabled: false&lt;/code&gt; accepts both (one of the two operators measured this on &lt;code&gt;status&lt;/code&gt;), at the price of not being able to query inside that field.&lt;/p&gt;

&lt;p&gt;If you run the grep and get a path that is not on this list, I'd like to hear which one.&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>security</category>
      <category>opensearch</category>
    </item>
    <item>
      <title>All 4052 if_sid anchors in the Wazuh ruleset resolve, so that's not why your rule is silent</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Wed, 23 Sep 2026 03:43:48 +0000</pubDate>
      <link>https://dev.to/xuxu298/all-4052-ifsid-anchors-in-the-wazuh-ruleset-resolve-so-thats-not-why-your-rule-is-silent-1f42</link>
      <guid>https://dev.to/xuxu298/all-4052-ifsid-anchors-in-the-wazuh-ruleset-resolve-so-thats-not-why-your-rule-is-silent-1f42</guid>
      <description>&lt;p&gt;I kept seeing the same guess from Wazuh users: my rule never fires, so the shipped ruleset must be broken. That's checkable, so I checked it instead of guessing.&lt;/p&gt;

&lt;p&gt;At commit 3bf1912 (measured 23 Sep 2026) the shipped ruleset is 166 files and 4420 rules.&lt;/p&gt;

&lt;p&gt;Every if_sid anchor resolves to a rule that exists: 4052 of 4052. Every if_matched_sid: 205 of 205. Every if_group: 129 of 129. No dangling parents anywhere.&lt;/p&gt;

&lt;p&gt;That zero is only worth reading because the detector was checked in both directions. A SID that can't exist (99999999) is reported as dangling, and SIDs that do ship (1002, 5701) are reported as defined. XML comments are stripped before parsing; three rule definitions live inside comments and would otherwise have been counted as real.&lt;/p&gt;

&lt;p&gt;The number that does explain silence is different: 635 of 4420 rules, 14.4 percent, carry level="0". They never alert by design, they exist to be matched on. A chain that terminates on one of them is silent and correct at the same time.&lt;/p&gt;

&lt;p&gt;So when a rule doesn't fire, the shipped anchors aren't where the fault is. Your own local rules, your ordering, and the level 0 parents in the chain are.&lt;/p&gt;

&lt;p&gt;Limits worth stating. This is ruleset/rules/*.xml at one commit and nothing else. It doesn't cover decoders, custom rules, or whether a rule that resolves is one that matches your logs. It proves the anchors are reachable, and it doesn't prove the detection is right.&lt;/p&gt;

&lt;p&gt;Re-run it against any commit:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const defined = new Set(), refs = [];
for (const p of ruleFiles) {
  const src = fetchText(p).replace(/&amp;lt;!--[\s\S]*?--&amp;gt;/g, '');
  const re = /&amp;lt;rule\b[^&amp;gt;]*\bid\s*=\s*["'](\d+)["'][^&amp;gt;]*&amp;gt;([\s\S]*?)&amp;lt;\/rule&amp;gt;/g;
  let m;
  while ((m = re.exec(src)) !== null) {
    defined.add(m[1]);
    let k; const ifRe = /&amp;lt;if_sid&amp;gt;([\s\S]*?)&amp;lt;\/if_sid&amp;gt;/g;
    while ((k = ifRe.exec(m[2])) !== null)
      k[1].split(',').map(s =&amp;gt; s.trim()).filter(Boolean).forEach(t =&amp;gt; refs.push(t));
  }
}
refs.filter(r =&amp;gt; !defined.has(r));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>security</category>
      <category>devops</category>
      <category>opensource</category>
      <category>linux</category>
    </item>
    <item>
      <title>Sixteen detection vendors, one TLS check, and nobody is near the 47-day floor</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Mon, 21 Sep 2026 06:07:38 +0000</pubDate>
      <link>https://dev.to/xuxu298/sixteen-detection-vendors-one-tls-check-and-nobody-is-near-the-47-day-floor-2e9f</link>
      <guid>https://dev.to/xuxu298/sixteen-detection-vendors-one-tls-check-and-nobody-is-near-the-47-day-floor-2e9f</guid>
      <description>&lt;p&gt;On 2026-09-21 I opened the public front door of sixteen detection and SOC vendors, one browser session per host, and kept two things from the TLS handshake: the negotiated key exchange group, and the certificate validFrom and validTo pair. The question was narrow. Not "is this vendor secure". Only this: what lifetime does the certificate on their own hostname actually carry, now that the CA/Browser Forum schedule has started to bite?&lt;/p&gt;

&lt;p&gt;Issued lifetime below is validTo minus validFrom, in days.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;host&lt;/th&gt;
&lt;th&gt;issued lifetime&lt;/th&gt;
&lt;th&gt;key exchange group&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;elastic.co&lt;/td&gt;
&lt;td&gt;397&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expel.com&lt;/td&gt;
&lt;td&gt;396&lt;/td&gt;
&lt;td&gt;P-256, X25519, X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;arcticwolf.com&lt;/td&gt;
&lt;td&gt;376&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;splunk.com&lt;/td&gt;
&lt;td&gt;364&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;crowdstrike.com&lt;/td&gt;
&lt;td&gt;198&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wazuh.com&lt;/td&gt;
&lt;td&gt;197&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rapid7.com&lt;/td&gt;
&lt;td&gt;197&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;blumira.com&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;exabeam.com&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;graylog.org&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;huntress.com&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redcanary.com&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;X25519, X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;securityonionsolutions.com&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sentinelone.com&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sophos.com&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;todyl.com&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;X25519MLKEM768&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three clusters, and nothing between them. Nine hosts sit at exactly 90 days. Three sit at 197 to 198. Four sit at 364 to 397. There is no host at 120 days, none at 150, none at 250. This population isn't a spread. It is three points.&lt;/p&gt;

&lt;p&gt;The number that made me write this down: zero of sixteen is at or under 47 days.&lt;/p&gt;

&lt;p&gt;The CA/Browser Forum schedule caps issuance at 200 days from 2026-03-15, at 100 days from 2027-03-15, and at 47 days from 2029-03-15. Seven of these sixteen currently carry a certificate longer than 100 days, so seven renewal processes have to change shape before March 2027. All sixteen have to change again before March 2029.&lt;/p&gt;

&lt;p&gt;The set reconstructs that schedule without being asked to. Every certificate here issued before 2026-03-15 runs 364 to 397 days. Every certificate issued after it runs 90 to 198 days. Sixteen out of sixteen, no exception. I didn't select for that. It fell out of the dates.&lt;/p&gt;

&lt;p&gt;Two hosts served more than one key exchange group inside a single page load. On redcanary.com some responses came back classical X25519 while others came back hybrid. On expel.com three groups appeared at once, including P-256 over TLS 1.2. A single label per hostname hides that, so the table prints every group observed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controls, because a measurement without one is a story
&lt;/h2&gt;

&lt;p&gt;Three of them, same sitting, same tool, and two are hosts anyone can hit.&lt;/p&gt;

&lt;p&gt;Direction on group and protocol: sha256.badssl.com returned P-256 over TLS 1.2. Nothing collapses into a single hybrid label and nothing is forced into a TLS 1.3 bucket.&lt;/p&gt;

&lt;p&gt;Fail closed: expired.badssl.com returned zero matching responses and therefore zero rows. A host whose certificate the browser rejects produces an absence, never an invented date. That matters more than it sounds, because it means a missing row in the table above would read as missing rather than as a clean result.&lt;/p&gt;

&lt;p&gt;Discrimination inside the set: two of the sixteen came back with classical and hybrid groups on different responses of the same page load. The tool splits them apart on live data, not only on a rigged host.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this doesn't measure
&lt;/h2&gt;

&lt;p&gt;The marketing hostname only. Not the product plane, not the customer portal, not the API endpoint.&lt;/p&gt;

&lt;p&gt;One client, one vantage point, one day. No repeat run, no second observer.&lt;/p&gt;

&lt;p&gt;Several of these hosts sit behind a CDN. For those, the certificate describes the automation of whoever operates that CDN, not the engineering practice of the vendor. The issuers observed across the set were a mix of large cloud and CDN certificate authorities plus two commercial ones, which is roughly the shape you would predict from the 90-day cluster.&lt;/p&gt;

&lt;p&gt;The sample is sixteen names chosen by hand. It isn't random and it isn't the whole market. One host redirected to a different hostname and was measured at the destination it landed on. Another redirected as well and was dropped instead of being silently attributed.&lt;/p&gt;

&lt;p&gt;Lifetime is read from what the browser reports for the certificate it actually received. Certificate Transparency logs were not pulled.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to run it yourself
&lt;/h2&gt;

&lt;p&gt;Any TLS client that reports both the negotiated group and the certificate dates will do. The browser developer tools print both under Security. A scripted version attaches a Network.responseReceived listener over the debugging protocol, keeps only the responses whose hostname matches the host under test, and prints the matched count next to the total response count on every row, so that an empty result is visibly empty rather than quietly missing.&lt;/p&gt;

&lt;p&gt;If you run the same check across your own vendor list, post the counts in the three buckets. I want to know whether the three cluster shape holds outside these sixteen names, or whether it's an artifact of picking well known ones.&lt;/p&gt;

</description>
      <category>security</category>
      <category>tls</category>
      <category>devops</category>
      <category>sysadmin</category>
    </item>
    <item>
      <title>Two coverage zeros, two different causes</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Thu, 17 Sep 2026 11:18:21 +0000</pubDate>
      <link>https://dev.to/xuxu298/two-coverage-zeros-two-different-causes-gla</link>
      <guid>https://dev.to/xuxu298/two-coverage-zeros-two-different-causes-gla</guid>
      <description>&lt;p&gt;On 16 September 2026 we wrote detection rules for two classes our bench had never&lt;br&gt;
covered, industrial control (OT) and Kubernetes, and loaded them onto our own&lt;br&gt;
Wazuh rig. Both classes had measured zero before. Loading the rules told us&lt;br&gt;
something the coverage count never could: &lt;strong&gt;the two zeros do not have the same&lt;br&gt;
cause, and only one of them is fixable by writing rules.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The zeros themselves were measured and published before any of these rules existed: &lt;a href="https://github.com/xuxu298/siem-replay-24-techniques/blob/main/ot-k8s-rule-layer-2026-09-15.md" rel="noopener noreferrer"&gt;ot-k8s-rule-layer-2026-09-15.md&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What we ran
&lt;/h2&gt;

&lt;p&gt;Rig: a stock Wazuh manager, &lt;code&gt;v4.14.7&lt;/code&gt; (revision &lt;code&gt;rc1&lt;/code&gt;), with the shipped ruleset.&lt;br&gt;
Two agents, both &lt;code&gt;v4.14.7&lt;/code&gt;: one Linux host running a PLC runtime, one running k3s.&lt;br&gt;
We wrote our own rules in a separate file and did not modify a byte of the stock&lt;br&gt;
ruleset. &lt;code&gt;wazuh-analysisd -t&lt;/code&gt; exits 0; the rules load.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Kubernetes zero
&lt;/h2&gt;

&lt;p&gt;We tagged a rule with &lt;code&gt;T1611&lt;/code&gt; (Escape to Host, from the ATT&amp;amp;CK Containers matrix).&lt;br&gt;
The tool accepted it and echoed &lt;code&gt;mitre.id: ['T1611']&lt;/code&gt;. No warning.&lt;/p&gt;

&lt;p&gt;We replayed a real event against it (a pod requesting &lt;code&gt;privileged&lt;/code&gt;, &lt;code&gt;hostPID&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;hostNetwork&lt;/code&gt;) and the alert came back at the level we assigned.&lt;/p&gt;

&lt;p&gt;So the Containers technique exists in the platform's technique database. Nothing in&lt;br&gt;
the platform was missing. What was missing was a rule, and a rule is something you&lt;br&gt;
can hire someone to write.&lt;/p&gt;
&lt;h2&gt;
  
  
  The OT zero
&lt;/h2&gt;

&lt;p&gt;We tagged a rule with &lt;code&gt;T0886&lt;/code&gt; (Remote Services, from the ATT&amp;amp;CK ICS matrix). The&lt;br&gt;
same tool, in the same run, printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;** Wazuh-Logtest: WARNING: Mitre Technique ID 'T0886' not found in database.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule still loads, and replaying a matching log line through the tool produces the&lt;br&gt;
alert. But the technique it claims to cover is not a technique the platform knows&lt;br&gt;
about. You can write the rule; you cannot make the&lt;br&gt;
platform carry the tag.&lt;/p&gt;

&lt;p&gt;That is a different kind of zero. Writing rules does not close it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we are reporting the control, not just the finding
&lt;/h2&gt;

&lt;p&gt;A tool that prints "not found in database" for every identifier you hand it would&lt;br&gt;
produce exactly the output above, and would mean nothing. The &lt;code&gt;T1611&lt;/code&gt; result is the&lt;br&gt;
control: same tool, same run, same rule file, with one identifier accepted silently&lt;br&gt;
and one rejected with a warning. Without that pair, the warning is not evidence.&lt;/p&gt;

&lt;p&gt;We also counted what the stock ruleset does carry: 97 distinct &lt;code&gt;T1xxx&lt;/code&gt; identifiers&lt;br&gt;
across 168 rule files. The ruleset is not untagged. It is tagged, thoroughly, in a&lt;br&gt;
matrix that does not include ICS.&lt;/p&gt;

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

&lt;p&gt;Both results above were produced by &lt;code&gt;wazuh-logtest&lt;/code&gt;, replaying a log line through the&lt;br&gt;
rule engine. They are not measurements of a live event stream. On this rig the OT&lt;br&gt;
source had stopped emitting entirely by the time the rules were loaded, so the OT&lt;br&gt;
rule has produced no alert from live traffic at all: a separate problem, and one a&lt;br&gt;
rule cannot fix.&lt;/p&gt;

&lt;p&gt;This is one rig, one version, one revision: &lt;code&gt;4.14.7 rc1&lt;/code&gt;, not a GA build. We have&lt;br&gt;
not tested another SIEM, and we have not checked whether a later release ships the&lt;br&gt;
ICS matrix. We are not saying the product does not support OT; we are saying that on&lt;br&gt;
this build, an ICS technique identifier is not in the technique database, and that&lt;br&gt;
this is a different problem from an empty ruleset even though both show up as a zero&lt;br&gt;
on a coverage chart.&lt;/p&gt;

&lt;p&gt;We also found stock rules that mention Kubernetes: 61 of them, in the AWS Security&lt;br&gt;
Hub file, covering EKS. Their groups are compliance groups (CIS, PCI DSS, and an&lt;br&gt;
800-53 control set). Across all 61 rules we count 7 MITRE blocks and 6 distinct technique&lt;br&gt;
identifiers, none of them from the Containers matrix. We mention this because a reader checking our claim&lt;br&gt;
will find those 61 rules in about thirty seconds, and we would rather name them than&lt;br&gt;
have them look like something we missed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question worth asking your own stack
&lt;/h2&gt;

&lt;p&gt;If you run a stock install against OT or Kubernetes log sources, the useful number&lt;br&gt;
is not how many rules you have. It is: &lt;strong&gt;when your coverage says zero, which of the&lt;br&gt;
two zeros is it?&lt;/strong&gt; One of them a rule author can close. The other one cannot.&lt;/p&gt;

&lt;p&gt;If you have run this and got a different result, particularly on a GA build, the&lt;br&gt;
identifier and the tool output would be useful to us. Send it: &lt;a href="https://github.com/xuxu298/siem-replay-24-techniques/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; or email &lt;a href="mailto:dongnx.biz@gmail.com"&gt;dongnx.biz@gmail.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>security</category>
      <category>kubernetes</category>
      <category>devops</category>
    </item>
    <item>
      <title>I scanned 37 security job posts tonight. Zero were posted in the last 24 hours.</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Wed, 09 Sep 2026 00:01:27 +0000</pubDate>
      <link>https://dev.to/xuxu298/i-scanned-37-security-job-posts-tonight-zero-were-posted-in-the-last-24-hours-119a</link>
      <guid>https://dev.to/xuxu298/i-scanned-37-security-job-posts-tonight-zero-were-posted-in-the-last-24-hours-119a</guid>
      <description>&lt;p&gt;I measure whether security monitoring actually fires. Tonight I checked something I had been assuming instead of measuring: how fast does the security slice of one large freelance marketplace actually produce work?&lt;/p&gt;

&lt;p&gt;Method: five one-word anchors (wazuh, splunk, siem, qradar, "soc 2"), sorted by recency, no time filter, page one, read at 2026-09-08 23:57Z.&lt;/p&gt;

&lt;p&gt;Result: 37 cards. That is 33 unique jobs, because four cards were the same posting showing up under two or three anchors. Zero posted in the last 24 hours. The youngest card in the whole slice was a day old.&lt;/p&gt;

&lt;p&gt;Two things there are worth more than the zero.&lt;/p&gt;

&lt;p&gt;First, my own age parser lied to me. Five of the 37 cards came back with an unreadable age, and I nearly wrote that down as the platform being quiet. It wasn't. The parser matched the string "Posted last week" and returned null, because it only converted "yesterday" into a number. The platform stated the age; my ruler couldn't read it. One of those five sat at position zero of a recency-sorted list, which is exactly where a fresh post would sit, so that zero was one bad string away from being wrong. An unmeasurable cell doesn't prove the thing you measured is silent. Sometimes it's accusing your ruler.&lt;/p&gt;

&lt;p&gt;Second, the only anchor that returned buyers with money wasn't a product name. "soc 2" gave 6 of 10 cards with payment verified and at least 1K spent, one of them past 300K. The four product-name anchors together gave 8 of 27. That's one slice on one night, so it's a hypothesis and not a finding. But it points somewhere I didn't expect: the people spending money here type the name of the paperwork they owe, not the name of the tool they run.&lt;/p&gt;

&lt;p&gt;If you sell detection or SOC work on a marketplace like this, I'd like to know whether your own counts say the same thing, or whether my five anchors are just the wrong five.&lt;/p&gt;

</description>
      <category>security</category>
      <category>freelance</category>
    </item>
    <item>
      <title>Your SIEM has never been asked a question it could fail</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Mon, 07 Sep 2026 05:21:20 +0000</pubDate>
      <link>https://dev.to/xuxu298/your-siem-has-never-been-asked-a-question-it-could-fail-56d0</link>
      <guid>https://dev.to/xuxu298/your-siem-has-never-been-asked-a-question-it-could-fail-56d0</guid>
      <description>&lt;p&gt;You run your own SIEM instead of buying a managed service because the maths worked. Licences, one engineer, a screen that stays green. That saving is real and it shows up on your P&amp;amp;L every month.&lt;/p&gt;

&lt;p&gt;The exposure sits on a line nobody prints. What you are paying for is detection. What the console actually proves is ingestion. Those are two different facts, and during a real incident the first one keeps looking healthy while the second one is the only one that matters.&lt;/p&gt;

&lt;p&gt;Here is the measurement I have. Default Wazuh build, stock rules, nothing tuned. I replayed 24 ATT&amp;amp;CK techniques against it (measured 01/09/2026). Three raised an alert. Discovery, collection, exfiltration and command-and-control produced nothing at all.&lt;/p&gt;

&lt;p&gt;It is my own number and not an independent benchmark, and it carries a correction I published against myself: four of the 21 silent rows turned out to be measuring my observation window rather than the rule. Those four are marked in the data. A number that moves after someone checks it should show who moved it and why, otherwise you are being asked to trust a person instead of to read a measurement.&lt;/p&gt;

&lt;p&gt;Raw data, host set and reproduction steps: &lt;a href="https://github.com/xuxu298/siem-replay-24-techniques/" rel="noopener noreferrer"&gt;https://github.com/xuxu298/siem-replay-24-techniques/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run it against your own build rather than believing mine. What comes back is a number about your estate, which is the only version of this with any value to you.&lt;/p&gt;

&lt;p&gt;The thing I do not know: for those of you running a self-hosted SIEM with no dedicated detection engineer, when someone above you asks whether you would actually see an intrusion, what do you put in front of them today?&lt;/p&gt;

</description>
      <category>security</category>
      <category>wazuh</category>
      <category>devops</category>
    </item>
    <item>
      <title>A rule that is present, enabled, and silent: what Wazuh 92900 shows about coverage reports</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Sun, 06 Sep 2026 13:17:57 +0000</pubDate>
      <link>https://dev.to/xuxu298/a-rule-that-is-present-enabled-and-silent-what-wazuh-92900-shows-about-coverage-reports-625</link>
      <guid>https://dev.to/xuxu298/a-rule-that-is-present-enabled-and-silent-what-wazuh-92900-shows-about-coverage-reports-625</guid>
      <description>&lt;p&gt;Wazuh rule 92900 is meant to catch LSASS credential dumping. It matches on an enumerated list of GrantedAccess masks: 0x1010 and 0x40.&lt;/p&gt;

&lt;p&gt;Run the classic living-off-the-land dump:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nb"&gt;rundll32.exe&lt;/span&gt; &lt;span class="kd"&gt;C&lt;/span&gt;:\windows\system32\comsvcs.dll&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;MiniDump&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kd"&gt;lsass&lt;/span&gt; &lt;span class="kd"&gt;pid&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;path&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;full&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sysmon Event ID 10 fires with GrantedAccess 0x1fffff. That value is not on the list, so 92900 produces nothing. The dump succeeds. The rule is still present, still enabled, and still counts as covered in any report that counts rules.&lt;/p&gt;

&lt;p&gt;That is the part worth sitting with. An enumerated-list rule does not fail loudly. It fails silent, and the silence is indistinguishable from "nothing happened."&lt;/p&gt;

&lt;h2&gt;
  
  
  The discriminator, and why the obvious one is the wrong one
&lt;/h2&gt;

&lt;p&gt;Widening the mask list does not fix it: the Wazuh agent itself opens LSASS, so 0x1fffff on its own is benign traffic. The thing that separates the malicious open from the benign one is CallTrace.&lt;/p&gt;

&lt;p&gt;The obvious key is comsvcs.dll, since that is what the command loads. It is the wrong key. comsvcs' MiniDumpW hands off to MiniDumpWriteDump, which lives in dbgcore.dll. Key on dbgcore instead and you catch the same comsvcs dump plus every dumper that reaches MiniDumpWriteDump by another path.&lt;/p&gt;

&lt;p&gt;This is not a guess. In the public thread the reporter tested both against a live endpoint: dbgcore.dll appears in the CallTrace for the comsvcs dump, and also for ProcDump. comsvcs does not appear in ProcDump's CallTrace at all. A comsvcs-only rule would have missed ProcDump entirely.&lt;/p&gt;

&lt;p&gt;The cost is real and should be stated: dbgcore and dbghelp also turn up in benign crash-handling traces, so this trades a silent miss for noise you have to tune.&lt;/p&gt;

&lt;h2&gt;
  
  
  The general shape
&lt;/h2&gt;

&lt;p&gt;Two things travel together here, and only one of them is about Wazuh.&lt;/p&gt;

&lt;p&gt;A rule written as a list of known-bad values answers "is this value on my list?", not "did this technique happen?". Any value off the list is silence. The rule is intact, the report is green, and the technique lands.&lt;/p&gt;

&lt;p&gt;So the check that matters is not "is the rule there?" but "does it fire when I run the thing?" — and then run a second tool down the same code path before calling the gap closed. Here, one tool would have produced a rule that looked fixed and still missed the more common dumper.&lt;/p&gt;

&lt;p&gt;The full test output is in the upstream tracker, in the wazuh/wazuh issue numbered 38716. I am not linking it directly here, but the number is exact and the thread is public, so you can check every claim above against what the reporter actually ran.&lt;/p&gt;

</description>
      <category>security</category>
      <category>wazuh</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>We replayed 24 ATT&amp;CK techniques against a default Wazuh install. Three raised an alert.</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Thu, 03 Sep 2026 09:52:19 +0000</pubDate>
      <link>https://dev.to/xuxu298/we-replayed-24-attck-techniques-against-a-default-wazuh-install-three-raised-an-alert-3ppj</link>
      <guid>https://dev.to/xuxu298/we-replayed-24-attck-techniques-against-a-default-wazuh-install-three-raised-an-alert-3ppj</guid>
      <description>&lt;p&gt;Not three percent. Three techniques.&lt;/p&gt;

&lt;p&gt;The four chains you would most want to catch — discovery, collection, exfiltration, C2 — produced&lt;br&gt;
&lt;strong&gt;zero&lt;/strong&gt; alerts between them.&lt;/p&gt;

&lt;p&gt;I want to be precise about what that number is and is not, because the number is easy to&lt;br&gt;
misread in both directions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it is
&lt;/h3&gt;

&lt;p&gt;A default install. Stock rules, no Sysmon, no ScriptBlock logging, no tuning. We replayed 24&lt;br&gt;
techniques and counted how many produced an alert a human would see. Denominator: 24. Numerator: 3.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it is not
&lt;/h3&gt;

&lt;p&gt;It is &lt;strong&gt;not&lt;/strong&gt; a claim that Wazuh is a bad SIEM. It is a claim that a &lt;em&gt;default&lt;/em&gt; SIEM is close to&lt;br&gt;
blind, and that most of the detection you think you bought is configuration you have not done yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same technique, three SIEMs
&lt;/h2&gt;

&lt;p&gt;T1110.001 (brute force) is the one technique we have run end-to-end on all three platforms.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SIEM&lt;/th&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;MTTD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wazuh 4.14.2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SSH brute force, 20+ failed logins, custom rule &lt;code&gt;100113&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;ALERTED ×2, coverage 100%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;25s / 45s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IBM QRadar CE 7.3.3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;hydra, 50 SSH login attempts against a live victim host&lt;/td&gt;
&lt;td&gt;ALERTED&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~4s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Splunk Enterprise&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;winsrv01&lt;/code&gt;, 12× EventCode 4625 — &lt;strong&gt;events replayed into Splunk, not generated by a live attack&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;ALERTED&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Read the third row differently from the first two.&lt;/strong&gt; Wazuh and QRadar were measured against a real&lt;br&gt;
attack on a real host: hydra and a live SSH brute force, with the alert coming back out of the&lt;br&gt;
platform. The Splunk row is a real Splunk Enterprise, a real detection-as-code rule, and a real&lt;br&gt;
query — but the 4625 events were replayed in rather than produced by an attack we ran. That makes&lt;br&gt;
it a valid test of the connector and the rule. It is not a test of Splunk's ingest path, and we are&lt;br&gt;
not going to let it read as one.&lt;/p&gt;

&lt;p&gt;🔴 &lt;strong&gt;Do not read this table as a race either.&lt;/strong&gt; The three numbers are not comparable and we are not&lt;br&gt;
going to pretend they are. Splunk's &lt;code&gt;0s&lt;/code&gt; is not Splunk being six times faster than QRadar — it is&lt;br&gt;
Windows 4625 events already sitting in the index before the clock started, while the Wazuh figure&lt;br&gt;
includes agent-to-manager-to-indexer propagation on a technique we had to write a rule for.&lt;br&gt;
Different telemetry, different clocks, different work. Published anyway, with the caveats attached,&lt;br&gt;
because a table with an honest asterisk is worth more than three separate write-ups with none.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;MISSED&lt;/code&gt; row is the one we care about most. Alongside &lt;code&gt;winsrv01&lt;/code&gt;, we ran &lt;code&gt;cleanhost&lt;/code&gt; — a host&lt;br&gt;
with no events at all — and the connector correctly returned &lt;strong&gt;MISSED&lt;/strong&gt;. A benchmark where nothing&lt;br&gt;
can fail is not a benchmark. That negative control is what makes the other rows mean anything. A&lt;br&gt;
third host, &lt;code&gt;dbsrv02&lt;/code&gt;, carried events timestamped 4s late and returned MTTD 4s, so the clock is&lt;br&gt;
measuring something real rather than returning zero by construction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two more findings from the same lab — and two things withheld
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Real ransomware encrypting real files → caught by Wazuh file integrity monitoring in real-time
mode. Two things are withheld here on purpose: the detection time, because the figure in our
internal notes does not appear in any published report we can point you at, and the rule id, on
the same ground.&lt;/li&gt;
&lt;li&gt;SSH brute force → alert &lt;strong&gt;and an actual firewall drop&lt;/strong&gt; that reached the host and stayed there.
The rule ids for that one are withheld on the same ground.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We had both sets of ids written down and we have taken them back out. An id you cannot trace to a&lt;br&gt;
source file is a claim nobody can check, which is worse than no id at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dates, because they matter
&lt;/h2&gt;

&lt;p&gt;These are lab measurements taken in June 2026, not last week. The Wazuh lab run is 15 June, the&lt;br&gt;
Splunk connector proof 26 June, the QRadar proof 29 June. Nothing here has been re-run since. If&lt;br&gt;
any of these platforms has changed its default rule set in the meantime, our numbers are stale and&lt;br&gt;
we would like to know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we can publish this and a vendor cannot
&lt;/h2&gt;

&lt;p&gt;Every SIEM vendor can measure their own product. None of them can publish a table where their&lt;br&gt;
product is one row among three, because the row that looks worst is the one paying for the&lt;br&gt;
marketing team. We sell neither Wazuh, QRadar, nor Splunk. That is the entire reason this table&lt;br&gt;
exists.&lt;/p&gt;

&lt;p&gt;We will also tell you what we have not measured. We have not run this against Sentinel or&lt;br&gt;
Elastic on live systems. We have no third-party audit of any of it — every number above is our own&lt;br&gt;
measurement of our own lab, and you should discount it accordingly until someone outside our&lt;br&gt;
company reproduces one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;The detection library behind this is 71 scenarios, 84 Sigma rules, 84 techniques, with zero gap&lt;br&gt;
between scenarios and rules, plus 6 named threat-actor campaigns. Rules translate to Splunk SPL,&lt;br&gt;
Sentinel KQL, Elastic and Wazuh from the same detection-as-code source.&lt;/p&gt;

&lt;p&gt;If you run any of those SIEMs and want your own number instead of ours, say so — we would rather&lt;br&gt;
argue with your data than with our own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corrections wanted.&lt;/strong&gt; If you think the 3/24 is wrong, tell us which technique should have fired&lt;br&gt;
and on what rule. We will re-run it and publish the correction with the same prominence as the&lt;br&gt;
original claim.&lt;/p&gt;

</description>
      <category>wazuh</category>
      <category>security</category>
    </item>
    <item>
      <title>In April, banking was the least post-quantum-ready sector we measured. It is now second from the top.</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Thu, 03 Sep 2026 08:26:38 +0000</pubDate>
      <link>https://dev.to/xuxu298/in-april-banking-was-the-least-post-quantum-ready-sector-we-measured-it-is-now-second-from-the-29ad</link>
      <guid>https://dev.to/xuxu298/in-april-banking-was-the-least-post-quantum-ready-sector-we-measured-it-is-now-second-from-the-29ad</guid>
      <description>&lt;h1&gt;
  
  
  Global PQ Readiness Index — September 2026
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Probe date:&lt;/strong&gt; 2026-09-03 UTC · &lt;strong&gt;Previous runs:&lt;/strong&gt; 2026-05-10 and 2026-04-23 UTC&lt;br&gt;
&lt;strong&gt;Method:&lt;/strong&gt; active TLS 1.3 ClientHello probe for NIST-track hybrid key agreement&lt;br&gt;
(codepoints &lt;code&gt;0x11EC&lt;/code&gt; X25519MLKEM768 and &lt;code&gt;0x11EB&lt;/code&gt; SecP256r1MLKEM768)&lt;br&gt;
&lt;strong&gt;Scope:&lt;/strong&gt; 350 public hosts, 8 sectors, 28 region tags — same list all three runs, unchanged&lt;br&gt;
&lt;strong&gt;Published alongside:&lt;/strong&gt; aggregate counts per sector and per run, plus the probe method in full.&lt;br&gt;
&lt;strong&gt;Not in this report:&lt;/strong&gt; per-host results. Earlier runs of this index did publish host-level data,&lt;br&gt;
and those files are still in this repository — see "Why we are not publishing the host list" below.&lt;/p&gt;




&lt;h2&gt;
  
  
  In April, banking was the least post-quantum-ready sector we measured. It is now second from the top.
&lt;/h2&gt;

&lt;p&gt;Four and a half months ago we probed 350 public TLS endpoints and found that banking sat at the&lt;br&gt;
bottom of eight sectors. We re-ran it in May, and again this week — identical probe, identical host&lt;br&gt;
list, all three times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Banking moved from 28% to 94%.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is not a typo, and it is the largest sector move in the dataset. It is still not the highest&lt;br&gt;
figure in the table: news &amp;amp; media finished at 96.4%. Banking travelled the furthest, not the&lt;br&gt;
highest, and those are two different claims.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number we trust, and why
&lt;/h2&gt;

&lt;p&gt;The headline comparison people will want is 42.6% → 73.1% across all 350 targets. &lt;strong&gt;We are not&lt;br&gt;
going to lead with that number&lt;/strong&gt;, because the set of hosts that answered changed between runs and&lt;br&gt;
a shifting denominator can manufacture a trend out of nothing.&lt;/p&gt;

&lt;p&gt;Instead: &lt;strong&gt;293 of 350 hosts returned a usable answer in &lt;em&gt;both&lt;/em&gt; runs.&lt;/strong&gt; That fixed cohort is the&lt;br&gt;
only honest basis for a delta, so every number below is computed on it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Cohort PQ-safe&lt;/th&gt;
&lt;th&gt;Banking&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;23 Apr 2026&lt;/td&gt;
&lt;td&gt;147 / 293 = &lt;strong&gt;50.2%&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;14 / 50 = &lt;strong&gt;28.0%&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 May 2026&lt;/td&gt;
&lt;td&gt;150 / 293 = &lt;strong&gt;51.2%&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;15 / 50 = &lt;strong&gt;30.0%&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 Sep 2026&lt;/td&gt;
&lt;td&gt;252 / 293 = &lt;strong&gt;86.0%&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;47 / 50 = &lt;strong&gt;94.0%&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The shape matters more than the endpoints.&lt;/strong&gt; In the 17 days from April to May, the cohort moved&lt;br&gt;
&lt;strong&gt;+1.0 point&lt;/strong&gt;. In the 116 days from May to September it moved &lt;strong&gt;+34.8&lt;/strong&gt;. This was not a gradual&lt;br&gt;
drift; something changed over the northern summer. We do not know what, and we are not going to&lt;br&gt;
guess in a document like this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;105 hosts moved from classical to post-quantum hybrid. Across all three runs, zero moved the other way.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That zero is the control. A measurement where nothing can move backwards is not measuring&lt;br&gt;
anything; here something &lt;em&gt;could&lt;/em&gt; have regressed, and nothing did. The shift is directional, not&lt;br&gt;
noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  By sector, same-host cohort
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sector&lt;/th&gt;
&lt;th&gt;n&lt;/th&gt;
&lt;th&gt;April&lt;/th&gt;
&lt;th&gt;September&lt;/th&gt;
&lt;th&gt;change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Banking&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;28.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;94.0%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+66.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Government&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;36.1%&lt;/td&gt;
&lt;td&gt;86.1%&lt;/td&gt;
&lt;td&gt;+50.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E-commerce&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;30.3%&lt;/td&gt;
&lt;td&gt;78.8%&lt;/td&gt;
&lt;td&gt;+48.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Critical infrastructure&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;43.3%&lt;/td&gt;
&lt;td&gt;80.0%&lt;/td&gt;
&lt;td&gt;+36.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;News &amp;amp; media&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;60.7%&lt;/td&gt;
&lt;td&gt;96.4%&lt;/td&gt;
&lt;td&gt;+35.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN &amp;amp; cloud&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;64.7%&lt;/td&gt;
&lt;td&gt;85.3%&lt;/td&gt;
&lt;td&gt;+20.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control group (known PQ-shipping)&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;69.6%&lt;/td&gt;
&lt;td&gt;87.0%&lt;/td&gt;
&lt;td&gt;+17.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Big-tech SaaS&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;71.2%&lt;/td&gt;
&lt;td&gt;81.4%&lt;/td&gt;
&lt;td&gt;+10.2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sectors everyone assumes are slowest — banking, government — moved fastest. The sector&lt;br&gt;
everyone assumes is fastest — big-tech SaaS — moved least, because it had the least room left&lt;br&gt;
to move.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We are not naming hosts.&lt;/strong&gt; Sector, region, denominator and delta are the whole product here.&lt;br&gt;
A named list would be a different thing with a different purpose, and this is not that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we are not publishing the host list
&lt;/h2&gt;

&lt;p&gt;Earlier runs of this index named individual hosts. This one does not, and neither will the ones&lt;br&gt;
after it. Those earlier files are still in this repository. We are not going to quietly delete&lt;br&gt;
them and imply the policy was always this one — and deleting them would not unpublish them anyway.&lt;/p&gt;

&lt;p&gt;A sector percentage with a stated denominator answers the question people actually have — &lt;em&gt;is my&lt;br&gt;
industry moving?&lt;/em&gt; A named list answers a different question, one that turns a measurement into a&lt;br&gt;
scoreboard about specific organisations who did not ask to be on it. We would rather publish&lt;br&gt;
something narrower and be able to keep publishing it.&lt;/p&gt;

&lt;p&gt;The method is stated in full and the aggregate counts are attached. Anyone who wants to check us&lt;br&gt;
can assemble their own list and run the same probe — that is a better reproducibility guarantee&lt;br&gt;
than taking ours on trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things this does not prove
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;We measured a front door, not an estate.&lt;/strong&gt; One public TLS endpoint per host. A bank whose
www hostname negotiates ML-KEM may still run classical crypto everywhere that matters —
internal PKI, batch file transfer, VPN, database links, HSM integrations. Front-door PQ is the
cheapest possible indicator, and cheap indicators move first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We cannot separate deliberate migration from vendor default.&lt;/strong&gt; A large share of this shift is
almost certainly CDN and load-balancer defaults rolling forward underneath organisations that
made no decision at all. That is still real protection against harvest-now-decrypt-later, but
it is not evidence of a migration programme, and it can be rolled back by a config change
nobody announces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;57 hosts are missing from the cohort.&lt;/strong&gt; 46 errored in September, 55 in April. The most common
failure is a server alert rejecting our ClientHello outright. We count those as &lt;em&gt;not measured&lt;/em&gt;,
never as classical. If a systematic bias hides in there, it is the weakest part of this dataset,
and it is the first place we would look for a correction.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Corrections wanted
&lt;/h2&gt;

&lt;p&gt;The method and the per-sector aggregate counts for all three runs are published here. If you can&lt;br&gt;
show that a sector figure is wrong, that the cohort method hides an artefact, or that the &lt;code&gt;0x11EB&lt;/code&gt;&lt;br&gt;
codepoint should be counted differently, tell us and we will re-run and publish the correction with&lt;br&gt;
the same prominence as the original claim.&lt;/p&gt;

&lt;p&gt;We will run this again in December on the same list. The delta is the point; a single snapshot of&lt;br&gt;
this is close to worthless.&lt;/p&gt;

</description>
      <category>postquantum</category>
      <category>cryptography</category>
      <category>security</category>
      <category>tls</category>
    </item>
    <item>
      <title>HIDS + NIDS: Why Your SMB Needs Both (And How We Integrated Wazuh + Suricata in a Single Platform)</title>
      <dc:creator>Nguyen Dong</dc:creator>
      <pubDate>Tue, 10 Mar 2026 12:09:42 +0000</pubDate>
      <link>https://dev.to/xuxu298/hids-nids-why-your-smb-needs-both-and-how-we-integrated-wazuh-suricata-in-a-single-platform-29bf</link>
      <guid>https://dev.to/xuxu298/hids-nids-why-your-smb-needs-both-and-how-we-integrated-wazuh-suricata-in-a-single-platform-29bf</guid>
      <description>&lt;p&gt;&lt;em&gt;Most SMBs think they're "covered" with just antivirus. Here's why that's like locking the front door but leaving every window wide open.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Blind Spot in SMB Security
&lt;/h2&gt;

&lt;p&gt;I've talked to dozens of SMB owners about their security setup. The conversation usually goes like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Me&lt;/strong&gt;: "What security monitoring do you have?"&lt;br&gt;
&lt;strong&gt;Them&lt;/strong&gt;: "We have antivirus on every computer."&lt;br&gt;
&lt;strong&gt;Me&lt;/strong&gt;: "What about network traffic? Can you see what's going in and out?"&lt;br&gt;
&lt;strong&gt;Them&lt;/strong&gt;: &lt;em&gt;...silence...&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is the blind spot. &lt;strong&gt;Antivirus checks what's ON your computers. But nobody checks what's FLOWING THROUGH your network.&lt;/strong&gt; A hacker stealing data over DNS tunneling, a compromised device beaconing to a C2 server, lateral movement between machines — antivirus won't catch any of it.&lt;/p&gt;

&lt;p&gt;You need two types of monitoring. And no, you don't need a $200K/year SIEM to get them.&lt;/p&gt;


&lt;h2&gt;
  
  
  HIDS vs. NIDS: A 60-Second Primer
&lt;/h2&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;HIDS (Host-based IDS)&lt;/th&gt;
&lt;th&gt;NIDS (Network-based IDS)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What it watches&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Individual devices (endpoints)&lt;/td&gt;
&lt;td&gt;Network traffic flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Detects&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;File changes, process anomalies, login attempts, malware&lt;/td&gt;
&lt;td&gt;Port scans, intrusion attempts, data exfiltration, C2 beaconing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Wazuh Agent&lt;/td&gt;
&lt;td&gt;Suricata IDS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Where it runs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;On each endpoint&lt;/td&gt;
&lt;td&gt;On a network sensor or device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Analogy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Security camera inside each room&lt;/td&gt;
&lt;td&gt;Guard at the building entrance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;HIDS tells you &lt;em&gt;what happened on a machine&lt;/em&gt;. NIDS tells you &lt;em&gt;what's happening on the wire&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You need both. Here's a real example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A Wazuh alert says "3 failed SSH logins from 182.23.XX.XX". That's HIDS.&lt;br&gt;
Suricata simultaneously sees "182.23.XX.XX is port-scanning 47 services on your network". That's NIDS.&lt;/p&gt;

&lt;p&gt;Combined? You know it's not a typo — it's an active attacker probing your infrastructure. Block them instantly.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  What We Built: Wazuh + Suricata → ClickHouse → AI
&lt;/h2&gt;

&lt;p&gt;In VRadar, we integrated both HIDS and NIDS into a single pipeline. Here's how the data flows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    HIDS Pipeline
Windows/Linux/Mac ──→ Wazuh Agent ──→ Wazuh Manager
                                            │
                                            ▼ webhook
                                      VRadar Backend ──→ ClickHouse
                                            │              (security_logs)
                                            ▼
                                      AI Operator ──→ Triage + Auto-Response

                    NIDS Pipeline  
Network Traffic ──→ Suricata IDS ──→ eve.json
                                        │
                                  Wazuh Agent (monitors eve.json)
                                        │
                                  Wazuh Manager ──→ Custom Rules 100100-100104
                                        │
                                        ▼ webhook
                                  VRadar Backend ──→ ClickHouse
                                                     (nta_events)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both pipelines converge into the same backend. One dashboard. One AI engine analyzing everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Technical Integration (For the Engineers)
&lt;/h3&gt;

&lt;p&gt;Getting Suricata to talk to Wazuh cleanly wasn't trivial. Here are the challenges we solved:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Interface Detection on Windows&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suricata crashes if you pass it a friendly interface name like "Wi-Fi" or "Ethernet". It needs the NPF device path: &lt;code&gt;\Device\NPF_{GUID}&lt;/code&gt;. Our installation script auto-detects this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Convert friendly name → NPF device path (Suricata requirement)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$adapter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-NetAdapter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&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="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Up'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InterfaceDescription&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-notmatch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Loopback|Virtual|Hyper-V'&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="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-First&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$npcapDevice&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"\\Device\\NPF_&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$adapter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InterfaceGuid&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Rule File Auto-Detection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suricata ships different rule files depending on version. Our script scans the actual &lt;code&gt;rules/&lt;/code&gt; directory and rewrites &lt;code&gt;suricata.yaml&lt;/code&gt; to match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$rules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;ProgramFiles&lt;/span&gt;&lt;span class="s2"&gt;\Suricata\rules"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*.rules"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Where-Object&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="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-notmatch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'dnp3|modbus|ipsec'&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="c"&gt;# Exclude ICS rules&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. JSON Decoder Limit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suricata's &lt;code&gt;eve.json&lt;/code&gt; events are large (800+ bytes). Wazuh's default JSON decoder limit (256) truncates them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;analysisd.decoder_order_size&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1024  # Increased from 256&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Custom Wazuh Rules for Suricata&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suricata alerts come through Wazuh's rule 86600 at level 0 (ignored by default). We created custom rules 100100-100104 to elevate them:&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;"100100"&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;86600&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;"event_type"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;^flow$&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;Suricata: Network flow event&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;&lt;strong&gt;5. One-Click Installation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The biggest challenge: making all of this install with &lt;strong&gt;one command&lt;/strong&gt;. Our agent script handles 6 steps automatically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clean up any existing Wazuh/Suricata installation&lt;/li&gt;
&lt;li&gt;Register with Wazuh Manager&lt;/li&gt;
&lt;li&gt;Install Wazuh Agent (version-matched to Manager)&lt;/li&gt;
&lt;li&gt;Configure HIDS monitoring&lt;/li&gt;
&lt;li&gt;Install Npcap + Suricata IDS&lt;/li&gt;
&lt;li&gt;Wire Suricata → Wazuh → VRadar pipeline&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Works on Windows, Linux, and macOS.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You See in the Dashboard
&lt;/h2&gt;

&lt;p&gt;Once both HIDS and NIDS are running, the VRadar dashboard shows:&lt;/p&gt;

&lt;h3&gt;
  
  
  HIDS Tab (System Alerts)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Security alerts from Wazuh (3,000+ detection rules)&lt;/li&gt;
&lt;li&gt;Alert severity distribution (Critical/High/Medium/Low)&lt;/li&gt;
&lt;li&gt;AI-powered triage decisions with confidence scores&lt;/li&gt;
&lt;li&gt;One-click IP blocking via Wazuh Active Response&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  NIDS Tab (Network Monitoring)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Suricata IDS events (flow, DNS, HTTP, TLS)&lt;/li&gt;
&lt;li&gt;Severity breakdown over 7 days&lt;/li&gt;
&lt;li&gt;Protocol distribution and traffic patterns&lt;/li&gt;
&lt;li&gt;Source/destination IP analysis with geolocation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Threat Map
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Real-time world map showing attacks hitting your network&lt;/li&gt;
&lt;li&gt;SVG-based Mercator projection with animated attack lines&lt;/li&gt;
&lt;li&gt;Data from both HIDS (login attempts, malware) and NIDS (port scans, intrusion attempts)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Security Score
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;9-factor scoring including both HIDS and NIDS health&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NIDS Monitoring&lt;/strong&gt; is one of the 9 scoring factors (10% weight)&lt;/li&gt;
&lt;li&gt;Getting both working pushes your score above 80 (Grade B → A territory)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Cost Argument
&lt;/h2&gt;

&lt;p&gt;Here's what dual HIDS + NIDS monitoring costs at scale:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vendor&lt;/th&gt;
&lt;th&gt;HIDS + NIDS&lt;/th&gt;
&lt;th&gt;Monthly Cost (50 devices)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Arctic Wolf&lt;/td&gt;
&lt;td&gt;Managed SOC&lt;/td&gt;
&lt;td&gt;$3,500+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blumira&lt;/td&gt;
&lt;td&gt;SIEM + IDS&lt;/td&gt;
&lt;td&gt;$850+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SentinelOne + Darktrace&lt;/td&gt;
&lt;td&gt;EDR + NDR&lt;/td&gt;
&lt;td&gt;$2,500+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;VRadar&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Wazuh + Suricata + AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1,250&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We can offer this pricing because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Wazuh and Suricata are open-source&lt;/strong&gt; — $0 licensing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI triage via GPT-4o-mini&lt;/strong&gt; — $0.15/1M tokens (we spend ~$2-5/tenant/month)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ClickHouse for log storage&lt;/strong&gt; — handles millions of events on a single server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solo operation&lt;/strong&gt; — no sales team, no marketing department (yet)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Lessons for Other Builders
&lt;/h2&gt;

&lt;p&gt;If you're building security tooling for SMBs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don't make users choose between HIDS and NIDS.&lt;/strong&gt; They need both. Bundle them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-install everything.&lt;/strong&gt; If setup takes more than one command, adoption drops to near zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI triage is table stakes now.&lt;/strong&gt; GPT-4o-mini costs almost nothing. Use it to reduce alert fatigue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suricata on Windows is possible&lt;/strong&gt; but painful. Budget extra time for NPF device paths, rule-file compatibility, and threshold configs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log everything to a columnar DB.&lt;/strong&gt; ClickHouse handles millions of events for $0 and queries complete in milliseconds.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;VRadar is live at &lt;strong&gt;&lt;a href="https://vradar.io" rel="noopener noreferrer"&gt;vradar.io&lt;/a&gt;&lt;/strong&gt; — plans start at $25/device/month for dual HIDS + NIDS monitoring with AI-powered threat analysis.&lt;/p&gt;

&lt;p&gt;If you're running an SMB with no security monitoring (or just antivirus), you're exactly who we built this for.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Dong, a solo developer from Vietnam building affordable security tools. If you have questions about integrating Wazuh + Suricata or building security products for the SMB market — ask me anything in the comments.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Tags&lt;/strong&gt;: #cybersecurity #wazuh #suricata #HIDS #NIDS #ai #SOC #startup #opensource #buildinpublic&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>wazuh</category>
      <category>suricata</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
