How to check in one minute
Feed wazuh-logtest the real sequence of events, not one line, and read which rule took the last one:
/var/ossec/bin/wazuh-logtest < sequence.log 2>&1 | grep -E "^[[:space:]]+(id|level|description): " | tail -3
If the ID is a sibling's rather than yours, your rule is shadowed on that sequence. Keep the 2>&1: logtest on 4.14 prints its results to stderr.
To find the sequence, look at alerts from siblings of your rule, meaning other children of the same parent. Their full_log is the event that sibling took. If you do not know whether your rule would have matched those events, see "Confirm before you fix" below.
Why it happens
We placed one rule in local_rules.xml and changed only its parent and level between runs:
<group name="local,syslog,sshd,">
<rule id="100080" level="$LEVEL">
<if_sid>$PARENT</if_sid>
<match>Accepted password</match>
<description>probe</description>
</rule>
</group>
Two inputs through wazuh-logtest: S1, one Accepted password line; S2, 8 Failed password lines from the same IP within 8 seconds, then the same Accepted password line.
| case | parent | level | -t | S1 lands on | S2 lands on |
|---|---|---|---|---|---|
| A | 5715 | 9 | exit 0, no warning | 100080 (ours) | 40112 level 12, ours loses |
| B | 5715 | 13 | exit 0, no warning | 100080 | 100080 level 13, ours wins |
| C | 5700 | 9 | exit 0, no warning | 100080 | 100080 |
| D | 40112 | 12 | exit 0, no warning | 5715 level 3, ours does not fire | 100080 |
| control | no custom rule | 5715 level 3 | 40112 level 12 | ||
We ran the matrix twice; all 8 verdicts matched.
- A against B: same parent, only the level changed from 9 to 13, and the outcome of S2 flipped. Level decides the order among siblings, not
if_sidand not the file. - At equal level, load order decides. On a test ruleset on a throwaway 4.14.7 manager, a sibling at the same level that was loaded earlier took the event. This matches Francisco Sousa's reading of the loader: a new child is inserted in front of the first sibling with a lower level, and behind siblings with the same level.
- The config check says nothing about this.
wazuh-analysisd -texited 0 with no warning in all four cases, including the one where our rule lost. - One line in logtest is not enough. In case A, a one-line test passes. Only the sequence shows the loss.
Fix
Each of these was measured on the same pair of rules (the table above). Which one is right depends on what you want the rule to mean:
- Rank above the sibling. Case B: level 13 over the sibling's 12 won both inputs. This also raises the severity of your alert, which may not be what you want.
- Hang the rule higher in the tree. Case C: with parent
5700at level 9, our rule took both inputs. - Hang the rule on the sibling. Case D: with parent
40112, the rule fires only after the burst of failures, and a lone login falls back to5715. Use this when the correlated case is the one you care about. - At equal level, the rule loaded first wins. Moving your rule earlier can fix it, but a file that sorts before the parent's file gets your rule dropped at load. Check for 7617/7619 after any rename.
In B and C, 40112 no longer fires on S2: your rule takes the event instead. If you rely on 40112 as a brute-force alert, use D.
After the change, feed the same sequence through logtest again and confirm the last line lands on your rule ID.
Confirm before you fix
A sibling that fired proves only that the sibling matched something. It does not prove your rule would have matched the same event. On a test ruleset we built three candidates this way; one of them, a rule on a user name that none of the events had, looked shadowed and was not.
The check that separated them: on a throwaway manager with a copy of the rules, raise your rule's level to 15, replay the full_log of the sibling's alerts through wazuh-logtest, and see whether any of them flips to your rule. If some flip, the rule is shadowed on those events. If none flip, your rule does not match them, and the problem is the rule's own conditions. In our test, the two real cases flipped (2 of 2 and 1 of 2 samples) and the false one flipped 0 times.
Limits of what we measured
One release (4.14.7), one single-node manager, one parent (5715) and one higher-level sibling (40112) for the level test, replayed through wazuh-logtest. The equal-level case and the replay check ran on test rules we wrote, on a throwaway 4.14.7 manager. We have not measured if_matched_sid or if_group chains, clusters, or live traffic. Our own lab showed no shadowed rules, but it had almost no traffic, so that says nothing about a busy manager. The replay check needs the full_log of the sibling's alerts; we have not measured it on Windows eventchannel or JSON events.