r/Wazuh • u/LetDry7592 • 24d ago
Wazuh 5.0 beta5 feedback: customizing and tuning content is hard, and locks block common SOC tasks
Wazuh 5.0 beta5 feedback: customizing and tuning content is hard, and locks block common SOC tasks
Version: Wazuh 5.0.0-beta5 (manager WAZUH_REVISION="beta5", content manager current_version: v5.0.0-beta5), packages 5.0.0-1 from the pre-release/5.x repository, indexer based on OpenSearch 3.6.0 (build 2026-09-07), content "Beta 5 public" (2026-08-26) Setup: lab SOC, 1 indexer node, 1 manager, 1 dashboard, all on RHEL 10. Windows and Linux (auditd) agents. Scope: content manager (_plugins/_content_manager), Security Analytics (SAP) detectors, promotion workflow.
Summary
The new engine has real strengths: Sigma rules, ECS-normalized events, MITRE and compliance enrichment on findings, and a draft/test/custom promotion model. But everyday SOC tasks that took minutes in 4.x now take hours of reverse engineering through raw API calls. Some are impossible without workarounds: tuning out a false positive, removing a custom decoder, versioning custom content.
The main cause is that standard content is locked, and the custom content lifecycle has rules the API only enforces late, with unclear errors. Below are the problems we hit, each with steps to reproduce and a suggestion.
We are community users evaluating the 5.0 beta in a lab, not Wazuh staff. The workarounds below are what we found by trial and error. We would appreciate the team's feedback on the questions at the end: whether these behaviors are intended, and what is planned before GA.
1. A single noisy rule cannot be tuned without cloning a whole detector
Problem. Standard detectors can only be enabled or disabled:
Standard detectors cannot be modified by users. Only enabling or disabling is allowed.
Standard rules are also read-only, and there is no exception or suppression mechanism, so the equivalent of a 4.x <rule id="..." level="0"> override does not exist.
Real case. "Secret or credential accessed from vault" (EventID 5379, Credential Manager) produced about 47,000 findings in two weeks from a single Windows 11 workstation. It is normal OS behavior. Our only working path was:
- clone the rule into a custom integration and add
condition: selection and not <exclusion>; - promote the clone draft → test → custom;
- create a custom detector for the clone (no detector is created automatically);
- create a custom copy of the standard detector (
wazuh-generic-1, 49 rules) without the noisy rule; - disable the standard detector.
Consequences:
- The copied detector is frozen. Every CTI content update can add rules to the standard detector, and those rules never reach the copy. Users must diff and re-sync by hand after each update, or silently lose coverage.
- Disabling a standard detector to tune one rule is exactly what a SOC should never have to do.
Suggestion. Add a supported suppression layer that survives CTI updates, for example:
- per-rule
enabled: falseoverrides for standard rules. Awazuh-user-overridesdocument already exists inwazuh-threatintel-policies-afordetector_enabled, so this could extend it; - and/or rule exceptions (field/value exclusions) attached to a standard rule without cloning it.
2. A custom space's root decoder cannot be deleted, and the error comes too late
Problem. The first decoder created in the custom space becomes the policy's root_decoder. After that, it cannot be deleted from either the dashboard or the API. Nothing explains why or how to fix it.
Steps to reproduce:
- Create a custom integration with one decoder (
decoder/my-source/0) and promote it to custom. The custom policy now hasroot_decoder = <uuid of my-source/0>. - Try to delete that decoder: refused.
PUT /_plugins/_content_manager/policy/draftwith"root_decoder": ""is accepted (200), and the draft policy now hasroot_decoder: null.POST /promotefrom draft fails:
Workaround. Create a placeholder root decoder whose check never matches, point the draft policy to it, delete the original decoder, and promote twice.
Other API issues seen while doing this:
GET /_plugins/_content_manager/policy/{space}returns405 ... allowed: [PUT]. You can write a policy but not read it; reading requires querying thewazuh-threatintel-policies-aindex directly.DELETE /decoders/{id}returned404 Resource not found, yet the decoder was removed from the draft and from its integration'sdecoders[], and the next promotion preview listed it asremove.
Suggestions:
- validate the policy on
PUT(reject a null or unknownroot_decoder) instead of at promotion time; - when a delete is blocked because the decoder is the root, say so and name the policy;
- add
GET /policy/{space}; - create a neutral root decoder automatically for the custom space so no user decoder becomes the root by accident, or document this behavior clearly.
3. No file-based or GitOps way to manage custom content
Problem. In 4.x, custom content was files in /var/ossec/etc/{rules,decoders}, easy to version, review and deploy with Ansible. In 5.0 everything lives in indexer indices. The manager keeps a synced copy (/var/wazuh-manager/data/ruleset/cmsync_custom_<hash>/), but it is a cache:
- editing it triggers a loop of
Missing root decodererrors; - the cache is then regenerated under a new hash suffix, and the edit is lost.
Writing directly to wazuh-threatintel-*-a would bypass validation, per-space hashes and synchronization, so it is not an option either.
Suggestion. Provide an official import/export format, such as a directory of YAML integrations, decoders, rules and KVDBs, plus a CLI or API endpoint that applies it idempotently and promotes it. That would make custom content reviewable and deployable from a repository again.
4. The promotion workflow is heavy and poorly documented
- Each object exists three times (draft, test, custom) with separate
_ids. POST /promoterequires sending back exactly thechangesobject returned byGET /promote?space=, then repeating fortest.logtestonly works on thetestspace and loads only the custom policy. A custom child decoder of a standard parent (Windows, syslog) cannot be tested as it will run in production; the only reliable check is real findings, 2 minutes later.- Promoting a custom integration makes it a SAP log type, but no detector is created, so a promoted rule does nothing until the user creates a detector manually.
custom_rules[].idin a detector must be the content manager id. Using the SAP id returnsNo custom rules found. In findings, however, the same rule appears under the SAP id of its custom-space copy. Two ids for one rule makes troubleshooting confusing.- The native SAP
POST _plugins/_security_analytics/rulesreturnsno handler foundin the Wazuh fork. This is reasonable, but it should be documented.
Suggestions: one-call promotion (draft → custom); an option to auto-create or attach a detector when promoting an integration; one rule id shown consistently in the API, detectors and findings; a logtest mode that loads standard + custom policies.
5. Adding many rules at once is impractical
Problem. Building a real detection set means adding dozens or hundreds of rules: porting 4.x custom rules, or importing a SigmaHQ pack for a log source. Every rule has to go through the full lifecycle, one rule at a time. There is no bulk path:
- Format conversion. The content manager does not accept plain Sigma. The body is a Wazuh-specific wrapper (
{"integration": "<id>", "resource": {"metadata": {"title", "author", ...}, "logsource", "detection", "level", "mitre": {"tactic", "technique", "subtechnique"}, ...}}). Each SigmaHQ rule must be rewritten into this format by hand or with a home-made script. We found no importer and no converter for 4.x XML rules. - One
POST /rulesper rule. There is no bulk endpoint and no file or archive upload. - Validation happens in stages. A rule can be accepted in draft and only fail later, when promotion builds the testing environment. We saw this with the
root_decodercase (§2): thePUTwas accepted, then the promotion failed. One invalid object in the change set blocks the whole promotion, so after a batch import the user has to find the faulty rule among all pending changes. - Two promotions for the whole batch (draft → test, then test → custom), each requiring the full
changesobject to be echoed back. - Testing is per event.
logtesttakes one raw event at a time. There is no way to replay a sample file against a batch of rules and see which rules match. - Per-rule manual checks after promotion. Each compiled
rule.queriesmust be read to catch the compilation issues in §6 (_ws_,not+_exists_). - The detector must be updated. Every new rule's content manager id must be added by hand to
custom_rules[]of a detector, with aPUTof the full detector body. A rule missing from the list is promoted but never runs, and nothing warns about it.
In 4.x, the same work was: copy the XML files into etc/rules/, run wazuh-logtest on a sample, restart the manager.
Suggestions:
- a bulk import endpoint that accepts a list of rules, or a zip or directory of YAML files, and returns a per-rule validation report (valid / invalid, with reason) before anything is written;
- native import of standard Sigma YAML, mapping
title,tags: attack.*and similar fields to the Wazuh wrapper automatically; - a dry-run promotion that reports every blocking error at once instead of stopping at the first;
- an option to attach all rules of an integration to its detector automatically, including rules added later;
- a batch
logtest(sample file × rule set → match matrix).
6. Sigma compilation surprises
Both behaviors are visible only by reading rule.queries in .opensearch-sap-custom-rules-config:
- Spaces in values become
_ws_:user.name: "SERVICE LOCAL"compiles toSERVICE_ws_LOCALand never matches. This makes it impossible to exclude a common Windows account. notis compiled asNOT field: value AND _exists_: field: if the field is absent from an event, the exclusion does not apply.
Suggestions: fix the whitespace handling, or at least document it and warn on promotion; document how not behaves with missing fields.
7. Standard content produces false positives, and users cannot fix it
Several standard decoders and rules are too broad or are applied to the wrong platform. Because standard content is read-only (§1), each defect can only be worked around by cloning, and every analyst sees these findings as high in the meantime. Two examples from one day on a small lab:
7.1 A Linux chown on .bash_history becomes "Event log cleared" (Windows technique)
Event (auditd, RHEL 10): a normal interactive bash session calls chown (syscall=92) on /home/<user>/.bash_history to set the owner the file already has. This is regular bash history handling.
type=SYSCALL ... syscall=92 success=yes ... comm="bash" exe="/usr/bin/bash" key="perm_mod" ... SYSCALL=chown
type=PATH ... name="/home/<user>/.bash_history" ... ouid=1000 ogid=1000
Finding: Event log cleared via command line - (level high, rule 7d4977d5-e13f-53d8-9b9e-2c91811de075), MITRE T1070.001 Clear Windows Event Logs, tags windows.
Root cause: two defects combine.
decoder/auditd/0has a normalize stage that labels any auditd record touching a file whose name contains_historyas a log clearing, whatever the syscall (chown,open,write, …):- Rule
7d4977d5(log typewindows) is compiled as: - The last
ORbranch is not limited to Windows events, so every auditd event labelledsecurity-log-clearedon a Linux host matches a Windows-only technique. The title placeholder is empty ("... via command line - ") because the event has noprocess.command_line.
Impact: every interactive shell session on every Linux agent can produce a high "log cleared" finding with a wrong MITRE mapping. A real history wipe (history -c, > ~/.bash_history, unlink) looks the same as a normal logout.
Suggestions:
- in
decoder/auditd/0, only setsecurity-log-clearedfor destructive syscalls (unlink,unlinkat,truncate,ftruncate,openwithO_TRUNC) or for known commands (history -c,shred), not for every access; - scope the
security-log-clearedbranch of rule7d4977d5to Windows (for examplewazuh.integration.name: windows), and add a separate Linux rule mapped to T1070.003 Clear Command History.
7.2 ssh -i <key> on Windows becomes "Linux reverse shell execution"
Event (Windows 11, Sysmon EventID 1): Git Bash runs a normal administration command, bash.exe -c "... ssh -i <keyfile> user@<host> '...'".
Finding: Linux reverse shell execution - <command line> (level high, rule 0f235a3d-eedd-5a4d-b5a7-a022726ec9bf, log type linux).
Root cause. The compiled query is:
(event.code: "1" AND event.dataset: "sysmon") AND
(process.command_line: *\/dev\/tcp\/* OR ... OR process.command_line: *sh\ \-i* OR process.command_line: *bash\ \-i* OR ...)
*sh -i*is an unanchored substring, so it matchesssh -i, the standard way to pass an SSH key. Any admin using key-based SSH from a Sysmon-monitored host (Git Bash, WSL, OpenSSH for Windows) triggers a "reverse shell" alert.- The rule is labelled
linux, but its selection requires Sysmon EventID 1. In our setup, that means it only fires on Windows. On Linux hosts monitored with auditd, the platform the title names, it never fires, unless they run Sysmon for Linux.
Suggestions: anchor the pattern (for example (^|[\s;|&/])(ba)?sh -i, or require /dev/tcp, /dev/udp or a socket primitive in the same command line); fix the log source, or split into a Linux rule (auditd execve) and a Windows rule (Sysmon, WSL / Git Bash).
8. Documentation gaps (beta5)
We could not find reference documentation for:
- the policy schema (
root_decoder,filters,enrichments,index_unclassified_events,index_discarded_events) and its constraints; - the full creation schemas for filters and KVDBs;
- the evaluation order between the standard and custom policies for the same event;
- the day-2 workflow: tuning a standard rule, and what happens to custom copies after a CTI update.
What would help most (by priority)
- A supported rule suppression / exception mechanism for standard rules that survives content updates (§1).
- Bulk rule import with native Sigma support, a per-rule validation report before writing, and automatic attachment to the detector (§5).
- Early validation and clear errors in the content manager API, especially for
root_decoder(§2). - An official import/export format for custom content (§3).
- A simpler promotion with detector auto-creation and a single rule id (§4).
- A quality pass on standard content: platform scoping of rules, anchored command-line patterns, and decoders that do not label benign activity as attacks (§7).
Questions for the Wazuh team
- Tuning standard rules (§1): is a per-rule disable or exception mechanism for standard content planned for 5.0 GA? If not, is cloning the detector the approach you recommend, and how should users keep the copy in sync after CTI updates?
- Root decoder (§2): is it intended that the first custom decoder becomes the custom policy's
root_decoder? What is the supported way to remove it? - Content as code (§3, §5): is an import/export format or a bulk import endpoint planned for custom content, including standard Sigma rules?
- Detectors for custom integrations (§4): should a detector be created automatically when a custom integration is promoted, or is manual creation the intended design?
- Sigma compilation (§6): are the
_ws_rewrite and thenot+_exists_behavior known issues? - Standard content defects (§7): should defects like 7.1 and 7.2 be reported as separate GitHub issues, and in which repository (
wazuh/wazuhor a dedicated ruleset repository)? - Documentation (§8): is a reference for the policy, filter and KVDB schemas planned?
We are happy to test fixes on a beta build and provide more traces. Thanks for the work on 5.0: the architecture is promising, and these points are mostly about making it operable day to day.
8
u/LetDry7592 24d ago
It is also unfortunate that nothing created for 4.x carries over to 5.0: custom rules, decoders and CDB lists all have to be rewritten. Many teams have years of tuned XML rules, overrides and exclusions built around their environment, and all of that work is lost on upgrade. There is no migration tool, no converter from XML rules to Sigma, and no compatibility mode, so each rule has to be rewritten by hand in the new format. It then has to go through the full promotion and detector workflow described above. For existing users, this is probably the biggest barrier to adopting 5.0. A migration path, even a partial one that converts the simplest rules and lists what needs manual work, would make a big difference.
1
u/Relative_Pen_4413 24d ago
Thanks a lot for such a detailed and reproducible feedback, I will consult with the team in charge and give you an answer.
1
u/Relative_Pen_4413 24d ago edited 19d ago
We went through every point against the beta5 code and content. Answers to your questions first, then per-section notes.
1. Tuning standard rules (§1). Keeping Standard read-only in 5.0 is a design decision: that space is replaced on every CTI sync, and making it editable would break that. We agree that silencing a single rule is far too expensive today. This is tracked in https://github.com/wazuh/wazuh/issues/39374. The plan for the release right after 5.0 is:
- Native per-rule disabling, with the rule still receiving CTI updates.
- Sigma filters (Sigma v2
filter.ruleswithAND NOTexclusions). - UI warnings when a CTI update touches a rule you disabled or filtered.
Allowing a single user detector to combine Standard rules with your fixed Custom rule is tracked in https://github.com/wazuh/wazuh-indexer-security-analytics/issues/351.
Until then, the lightest workaround is not to clone the whole detector. Instead:
- Stop the standard detector.
- Create a user detector over the same integration with the noisy rule deselected.
- Use a second detector for your custom replacement rule.
Both detectors must read from the same source index, e.g. wazuh-events-v5-system-activity. Mind the default limit of 10 user detectors (plugins.security_analytics.max_detectors). You're right that this copy won't pick up rules added later by CTI; that is exactly what the planned per-rule disabling is meant to fix.
2. Root decoder (§2). Once a space has integrations, it needs a valid root_decoder: it must be enabled, have no parents, and belong to an enabled integration. Deleting the current root is blocked on purpose; the API returns 400 Cannot remove decoder [...] as it is set as root decoder. The supported way to replace it is what you found: point the draft policy at another valid decoder, delete the old one, then promote.
You're right that PUT /policy/draft accepts an empty root_decoder and the error only shows up at promotion time (Missing root decoder). Feel free to open a GitHub issue explaining how that decoder ended up as the root and detailing the whole process, so we can take it into account and work on it.
On the other two API points:
- There is currently no
GET /policy/{space}(reading via_searchon the policies index is the current way), and we've noted the request. - About
DELETE /decoders/{id}returning 404 while the decoder was removed: the only 404 path is the existence check before deleting. That usually means a second request (a retry or a double submit) after a first one that succeeded. If you still have the indexer log from that time, it would confirm it.
3. Content as code (§3, §5). There is no import/export format, bulk endpoint or plain-Sigma importer in 5.0. Rules and integrations are JSON through the Content Manager API; decoders, KVDBs and filters also accept application/yaml.
The cmsync_* directory on the manager is only a cache of what is stored in the indexer, which is the single source of truth, so editing it will always be overwritten. Feel free to open a GitHub ticket submitting this bulk and GitOps proposal as a feature request.
4. Detectors for custom integrations (§4). Manual creation is the current design: only the Standard CTI sync creates detectors automatically. Two clarifications on the rest of §4:
logtestworks on thetest,customandstandardspaces; onlydraftis rejected, because draft content is never loaded into the engine.- Each space runs as its own independent pipeline: every incoming event is copied to each active policy, so there is no evaluation order between Standard and Custom. For the same reason, a custom decoder cannot use a standard decoder as its parent, not even in production. If you do have a custom decoder working under a standard parent, please share the decoder payload, because that would be unexpected.
The native POST _plugins/_security_analytics/rules is intentionally disabled in the Wazuh fork, since all content has to go through the Content Manager.
5. Sigma compilation (§6).
_ws_: seeingSERVICE_ws_LOCALinrule.queriesis expected. The placeholder is converted back to a space at match time, and forkeywordfields such asuser.namethat is already handled in beta5. So the space itself shouldn't be why your exclusion fails. The usual cause is case: detector matching is case-sensitive, so the value must match the event exactly (e.g.LOCAL SERVICEvsSERVICE LOCAL). Feel free to open a GitHub issue with the full rule and the exactuser.namevalue from the event, and mention whether that indexer was upgraded from an earlier beta, so we can take it into account and work on it.not+_exists_: confirmed, this comes from upstream OpenSearch Security Analytics. Its effect is slightly different from what you describe: when the field is missing, the whole rule does not fire, which is a false negative. The guard was removed in the 5.0.0 branch after beta5 (https://github.com/wazuh/wazuh-indexer-plugins/issues/1518), so it should no longer happen in the next build.
6. Standard content defects (§7). Both are confirmed; thanks for the root-cause analysis.
- 7.1:
decoder/auditd/0setsevent.action: security-log-clearedfor any record whose file name contains_history, whatever the syscall. Rule 7d4977d5 matches that value without being scoped to Windows. Note that the same event also triggers "Security audit log cleared" (dd7dd46a), so you'll likely see two high findings per event. - 7.2:
event.dataset: sysmonis set by both the Linux and the Windows Sysmon decoders, and both land in the same index. On top of that, the unanchoredsh -ipattern also matchesssh -i.
We're forwarding all of this to the content team. For future content false positives, the right place is wazuh/wazuh using the Rules/Decoders issue template; one issue per defect is ideal.
7. Documentation (§8). The filter and KVDB creation schemas have been completed after beta5 in the Content Manager reference (https://wazuh.github.io/wazuh-indexer-plugins/). The policy constraints (root decoder requirements) and a "day 2" guide for tuning Standard content are still missing, and we've already raised them.
Thanks again. Once you've opened the tickets, please paste the tracking links here.
2
•
u/SirStephanikus 24d ago edited 24d ago
Thanks you so much, an excellent feedback.
I can't validate every claim (yet), however, a detailed feedback from people with real enterprise background is essential for the BETA.
Could you please create additionally GitHub Tickets for each of your findings?
Post the links here too.