A post-filter keeps what it matches
This is the part that can cost you data, so it comes first. We created a post-filter in the standard space, then pushed two events through the manager's engine: one sshd line and one sudo line. We counted what reached wazuh-events-v5-*. Two runs, one per filter, through the local ingest socket:
| filter check | sshd event | sudo event |
|---|---|---|
string_equal($process.name, 'sshd') | indexed | dropped |
NOT string_equal($process.name, 'sshd') | dropped | indexed |
The filter that "matches sshd" did not drop sshd. It kept sshd and dropped the sudo event, which it did not mention at all. A filter written as contains($service.name, 'sysstat-collect') would keep the sysstat events and drop every other event the manager receives. To drop something, the check has to say NOT.
Nothing warns you. The filter passes the engine's validation, and the manager log shows only a normal reload line ([Orchestrator] Hot swapped namespace).
How to check your install
All three are read-only. Set these first:
IDX=http://localhost:9200 # your indexer. Ours had the security plugin off: plain HTTP, no login.
CURL="curl -s" # normal install: CURL="curl -sk -u admin:YOUR_PASSWORD" and IDX=https://...
1. Is sysstat-collect on the rule's list?
$CURL "$IDX/_cat/indices/wazuh-threatintel-rules*?h=index" # ours: wazuh-threatintel-rules-a
$CURL "$IDX/wazuh-threatintel-rules-a/_search?size=2" -H 'Content-Type: application/json' \
-d '{"query":{"terms":{"document.id":["653da773-9e1b-5f39-a662-e5fafe3b1516","1e9dc454-ce81-5df4-a95b-2ab4d8bde5d3"]}}}' \
| python3 -m json.tool | grep -E '"title"|"modified"|sysstat|filter_benign'
Those are the IDs of "System service started" and "System service stopped" in our content. If your content has a newer version of the rules, the IDs or the list may differ: search by title instead. No sysstat in the output means the service is not on the list.
2. Does your event match? (rule test, no agent needed)
$CURL -XPOST "$IDX/_plugins/_content_manager/logtest/detection" -H 'Content-Type: application/json' -d '{
"space": "standard",
"integration": "7f21dc98-7e96-579d-89bd-5e2cb6e51801",
"input": { "event": { "action": "service-started", "category": ["host"] },
"service": { "name": "sysstat-collect.service" } }
}'
7f21dc98… is the integration wazuh-generic-2, which holds the "started" rule. The "stopped" rule is in wazuh-generic-1, e7743934-ddd9-5e7d-8c07-a25a30352d1d: test service-stopped there. If you test it against the wrong integration, you get 0 matches and a false all-clear; that happened to us once.
3. Do you already have a filter?
$CURL "$IDX/wazuh-threatintel-policies-a/_search?size=10&_source=space.name,document.filters,user_overrides" | python3 -m json.tool
Ours returned five documents: one policy per space (draft, test, custom, standard) and a registry document, wazuh-user-overrides. Any ID under filters for the standard space is a filter that runs on every event. If someone added one and events went missing at the same time, read its check before anything else.
Why it happens
Both rules have the same shape:
condition: selection and not filter_benign_services
selection:
event.action: [ service-started ] # service-stopped in the second rule
event.category|contains: host
filter_benign_services:
service.name|contains: [ fprintd, geoclue, spice-vdagentd, fwupd-refresh, setvtrgb,
systemd-localed, systemd-timedated, NetworkManager-dispatcher, modprobe, dmesg,
packagekit, systemd-tmpfiles-clean, user-runtime-dir ]
Any service that starts or stops and is not on those 13 names matches. According to #40801, the default sysstat-collect.service on Ubuntu 24 and 26 runs every 10 minutes, so it matches every time it runs; we did not run sysstat ourselves. Both rules are level informational, tagged attack.t1543.
In 5.0 these rules live in the indexer, in the standard space that Wazuh ships and updates. You cannot change them in place. We tried to update the "started" rule through the content manager API and got:
Rule with ID '653da773-9e1b-5f39-a662-e5fafe3b1516' is not in draft space.
The request in #40801 is for Wazuh to add sysstat-collect to the list. When we checked on 28 October it was open, with no reply.
How to drop the sysstat events (tested, with one gap)
Create a post-filter in the standard space that keeps every event except a sysstat-collect start or stop:
$CURL -XPOST "$IDX/_plugins/_content_manager/filters" -H 'Content-Type: application/json' -d '{
"space": "standard",
"resource": {
"name": "filter/drop-sysstat-collect/0",
"metadata": { "title": "Drop sysstat-collect service start/stop events", "author": "you",
"description": "Benign timer service, see wazuh/wazuh#40801",
"references": [], "documentation": "" },
"enabled": true,
"type": "post-filter",
"check": "NOT (contains($service.name, '"'"'sysstat-collect'"'"') AND (string_equal($event.action, '"'"'service-started'"'"') OR string_equal($event.action, '"'"'service-stopped'"'"')))"
}
}'
(The '"'"' pieces put a single quote inside the shell's single-quoted body. The check the engine stores is NOT (contains($service.name, 'sysstat-collect') AND (string_equal($event.action, 'service-started') OR string_equal($event.action, 'service-stopped'))).)
- The reply is the new filter's ID. It was added to the standard policy's
filtersby itself, and to thewazuh-user-overridesregistry, and the manager reloaded about 40 seconds later (look forHot swapped namespacein the manager log). - Run it once. We ran it twice by accident and got two identical filters with different IDs; the API does not refuse a duplicate name. Run check 3 before you add one.
- Then check that other events still arrive. Count recent events by
process.namebefore and after.event.originalis not indexed in 5.0, so you cannot search for a raw line:
$CURL "$IDX/wazuh-events-v5-*/_search?size=0" -H 'Content-Type: application/json' -d '{
"query": { "range": { "@timestamp": { "gte": "now-5m" } } },
"aggs": { "p": { "terms": { "field": "process.name", "missing": "(none)" } } } }'
With this filter on, our sshd and sudo test events were both indexed, and both produced findings about a minute later.
The gap: we did not see the filter drop a real sysstat event, because our lab could not produce one (see limits). What we did measure is that a NOT filter dropped the event it matched (the table above) and that this exact check kept events it does not match.
To remove it: $CURL -XDELETE "$IDX/_plugins/_content_manager/filters/FILTER_ID" -H 'Content-Type: application/json' -d '{"space":"standard"}'
Trade-off: a post-filter drops the event itself, not only the alert. Those sysstat start and stop events will not be in wazuh-events-v5-* either. If you need them kept for audit, this is the wrong tool. We did not test any way to suppress only the finding.
What we measured
Wazuh manager 5.0.0-rc1 (commit aff7a0b) and one indexer node, OpenSearch 3.6.0, security plugin off, our test containers, 28 October 2026. Times are UTC.
Rule test (logtest/detection)
| integration | event.action | service.name | result |
|---|---|---|---|
wazuh-generic-2 | service-started | sysstat-collect.service | 1 of 31 rules matched: System service started |
wazuh-generic-2 | service-started | fwupd-refresh.service (on the list) | 0 of 31 |
wazuh-generic-2 | service-started | nginx.service | 1 of 31: System service started |
wazuh-generic-1 | service-stopped | sysstat-collect.service | 1 of 50 rules matched: System service stopped |
wazuh-generic-1 | service-stopped | fwupd-refresh.service (on the list) | 0 of 50 |
Filter test (events through the engine)
Each run: one line sshd[...]: Server listening on 0.0.0.0 port 222xx. (the system-auth decoder maps it to service-started) and one sudo line, pushed into the manager's engine through its local ingest socket (engine-ingest-http.sock, POST /events/enriched). We waited for the manager to reload after each filter change, then counted documents in wazuh-events-v5-* by process.name from the time of the push.
| run | post-filter check | sshd | sudo |
|---|---|---|---|
| 03:31:02 | string_equal($process.name, 'sshd') | 1 | 0 |
| 03:34:50 | NOT string_equal($process.name, 'sshd') | 0 | 1 |
| 03:36:59 | the sysstat check from the fix | 1 | 1 |
We removed every test filter afterwards; the standard policy's filters list was empty again.
Limits of what we measured
Not measured: which decoder produces service-started for a systemd service on Ubuntu. In our content, the inventory decoders (dbsync_services) produce only service-installed, service-deleted and config-modified. The decoders that produce service-started are for sshd, containerd, Redis, WebLogic, SQL Server, Sysmon for Linux, macOS, Windows events, Juniper and Sophos. So the sysstat events in #40801 come from content newer than ours or from a source we do not have, and we could not reproduce them end to end. The rule match above uses an event we wrote by hand in the normalized form the rule reads.
Also not measured: whether a filter in the standard space survives the next content update from Wazuh (we saw it recorded in the wazuh-user-overrides registry, which is how the content manager keeps user changes to the standard space, but we did not run an update), 5.0 GA, the dashboard, the security plugin's permissions for the content manager API, more than one indexer node, real agents and real load. Two filter runs per check, one event of each kind per run.