How to check in one minute
- Take one false alert and copy the field value exactly. If it is byte-for-byte a key in your list (no different case, no invisible character), and the same process usually passes, it fits this note.
- Look at the time of the false alerts. Ours came only when events arrived fast; at 1,000 events per second there were none.
- If the value differs from the key, even invisibly, see misses that are real below.
What we measured
The rule, from a real allow-list question on the Wazuh mailing list:
<group name="allowapplication,">
<rule id="100500" level="16">
<if_sid>61603</if_sid>
<list field="win.eventdata.company" lookup="not_match_key">etc/lists/software-vendors</list>
<description>Sysmon - Event 1: Process $(win.eventdata.description) started but not allowed by the software policy</description>
<group>software_policy</group>
</rule>
</group>
List etc/lists/software-vendors with the keys Microsoft Corporation:, Amazon.com Inc.: and Google LLC:. We sent Sysmon Event 1 events into analysisd the way an agent delivers Windows event channel events, alternating the company Microsoft Corporation and Amazon.com Inc., so no event should alert:
| Events sent | Dropped | False 100500 alerts |
|---|---|---|
| 20,000 at 1,000 per second | 0 | 0 |
| 20,000 at 5,000 per second | 0 | 10 |
| 50,000 in about 1.5 s | 0 | 27 |
| 50,000 in about 1 s (earlier run) | 5,770 (decoder queue full) | 24 |
50,000, analysisd.rule_matching_threads=1, two runs | 0 | 0 and 0 |
50,000, negated <field> instead of the list, two runs | 0 | 0 and 0 |
The false alerts carried the exact vendor string: "company": "Amazon.com Inc.", decoder windows_eventchannel, rule 100500, level 16.
Why, from the code. In src/analysisd/lists_list.c at v4.14.7, OS_DBSeachKey, which serves match_key and not_match_key, releases the rule's mutex and then calls cdb_find() on the list's shared CDB handle. The key/value lookup calls cdb_find() while still holding it. A lookup that runs at the same time as another one on the same handle can come back as "not found", and for not_match_key that means an alert. That one thread removed the false alerts fits this reading.
Looking further, cdb_find() in src/analysisd/cdb/cdb.c resets the search state and then searches under two separate locks, so another lookup on the same list can change that state in between. We built analysisd from the v4.14.7 source with cdb_find() made one locked step, and the same build without the change as a control. In 50,000-event bursts the control gave 69 false alerts with one rule on the list and 41 + 32 with two rules sharing it; the patched build gave 0 in every run, including two rules on one list and 5,000 events per second. This is our build, not a Wazuh release.
Workarounds
1. Put the allow list in the rule. Same rule, negated field instead of the list:
<rule id="100500" level="16">
<if_sid>61603</if_sid>
<field name="win.eventdata.company" negate="yes" type="pcre2">^(Microsoft Corporation|Amazon\.com Inc\.|Google LLC)$</field>
<description>Sysmon - Event 1: Process $(win.eventdata.description) started but not allowed by the software policy</description>
<group>software_policy</group>
</rule>
On 11 test values (exact, trailing space, different case, invisible characters, a comma variant, -, empty, missing field, an unknown vendor) it alerted on exactly the same ones as the list. Escape regex characters in vendor names (the dots above). Practical for tens of names, not thousands.
2. Keep the list, use one rule-matching thread. In /var/ossec/etc/local_internal_options.conf:
analysisd.rule_matching_threads=1
Restart the manager. This reduces how many events analysisd can match per second; we did not measure by how much, so watch the event queue on a busy manager before keeping it.
Misses that are real
Separate from the race, this is how the list treated values close to a key, at a low event rate. Some of the ones that alert look identical to the key in the dashboard:
| Company value | Result |
|---|---|
Microsoft Corporation with a trailing space | no alert (matched) |
microsoft corporation | alert: keys are case-sensitive |
| non-breaking space, or a zero-width character at the end | alert, though it looks the same |
Amazon.com, Inc. | alert: comma variant |
-, empty, or no company field | no alert |
Also measured: if the <list> line is missing from ossec.conf (for example on a cluster worker), the manager logs WARNING: (7616): List 'etc/lists/software-vendors' could not be loaded. Rule '100500' will be ignored. and the rule never fires at all. A list file with Windows line endings worked normally.
Limits of what we measured
Wazuh 4.14.7 manager container on an 8-CPU host; the event rates where false alerts start depend on hardware and on the rest of your ruleset. Events were built in the Sysmon Event 1 layout and sent into analysisd's queue as an agent sends event channel events; we did not run a Windows agent. We tested not_match_key only, not match_key, the address lookups or two rules sharing one list. The patched analysisd was built by us from the v4.14.7 source to confirm the cause; we did not measure its throughput or run Wazuh's unit tests, and it is not something to deploy in place of an official release.