Skip to main content
Glama
xuxu298

wazuh-rule-precheck

by xuxu298
README.md
# ATK Rule Pre-check for Wazuh: find ignored rules before you restart

Paste or point it at your custom Wazuh rule and decoder files. It tells you what `wazuh-analysisd` will report when it loads them: rules it will **ignore** because a parent is missing or loads later, duplicate IDs, and the mistakes that **stop the manager from starting**. It does not run Wazuh, reads files only, and sends nothing anywhere.

Three ways to run the same core:

- **In the browser:** https://atkvn.com/wazuh-rule-precheck.html (nothing is uploaded; the check runs in the tab)
- **Command line:** `node cli.js /var/ossec/etc/rules /var/ossec/etc/decoders`
- **MCP server** for any MCP client: `mcp-server.js`

Node.js 18 or newer. No dependencies.

## Command line

```bash
git clone https://github.com/xuxu298/wazuh-rule-precheck
cd wazuh-rule-precheck
node cli.js /var/ossec/etc/rules /var/ossec/etc/decoders     # text report
node cli.js --json local_rules.xml                           # JSON
node cli.js --v5 /var/ossec/etc/rules                        # Wazuh 5.0 readiness (see below)
```

Exit code: `2` something stops the manager, `1` rules will be ignored, `0` nothing found.

## MCP server

```json
{ "mcpServers": { "wazuh-rule-precheck": { "command": "node", "args": ["/path/to/wazuh-rule-precheck/mcp-server.js"] } } }
```

Tools: `wazuh_rule_precheck` (files, wazuh_version) and `wazuh_v5_readiness` (files).

Or in Docker (stdio, runs with no network):

```bash
docker build -t wazuh-rule-precheck .
docker run -i --rm --network none wazuh-rule-precheck
```

Offline, read-only, no credentials. A Claude skill is in `SKILL.md`.

## What it checks (Wazuh 4.x)

| Finding | What Wazuh 4.14.7 does | Message |
|---|---|---|
| `<field name="srcip">` or any of the 17 static fields | rejects the file, manager does not start | `Field 'srcip' is static` + `CRITICAL (1220)` |
| XML not well formed | rejects the file, manager does not start | `(1226) … XMLERR` |
| decoder `<parent>` that does not exist | configuration error, manager does not start | `(2101)` |
| `if_sid` missing, or defined only later in the load order | drops it; with none left, ignores the rule | `(7617)`, `(7619)` |
| `if_matched_sid` missing | ignores the rule | `(7620)` |
| `if_group` matching no rule loaded so far | ignores the rule | `(7610)` |
| rule ID already loaded, no `overwrite="yes"` | keeps the first one | `(7612)` |
| `overwrite="yes"` on an ID that does not exist | loads it as a new rule | `(7613)` |

Load order matters: Wazuh loads the stock rule files and your files from `etc/rules` together, sorted by file name, and a parent must already be loaded when its child is read. `etc/rules/0010-my_rules.xml` with `<if_sid>5715</if_sid>` is ignored, because it sorts before `0095-sshd_rules.xml`; the same rule in `local_rules.xml` loads.

## How it was checked

Each finding was produced first by the real `wazuh-analysisd -t` 4.14.7 on a test file, then predicted from the same file: **15 cases, 15 matches** (`node test/test-calib.js`, cases in `test/cases/`). That includes how `if_group` matches: a case-insensitive substring of a loaded rule's groups, with `|` alternatives and `^` anchors, the way `OS_WordMatch` does it.

On the public SOCFortress `Wazuh-Rules` set (69 rule files, 6 decoder files), `wazuh-analysisd -t` printed 52 warnings of these kinds and this tool found 55. The 3 extra are real: `wazuh-analysisd` keeps only the last **50** load messages (`ERRORLIST_MAXSIZE 50` in `src/analysisd/logmsg.h`, oldest dropped first). With 60 broken rules in one file, `-t` and `ossec.log` named only the last 25 (`test/cases/cap50`). On a large ruleset, some ignored rules never show up in the logs.

## Wazuh 5.0 readiness (`--v5`)

Wazuh 5.0 does not load XML rules or decoders and ships no converter. `--v5` lists, per rule and decoder, the elements that the 5.0 migration guides (wazuh/wazuh `v5.0.0-beta5`, `docs/guide/migration/rules-4x-to-5x.md` and `xml-decoders-migration.md`) mark as unsupported: rule chaining (`if_sid`, `if_group`, `if_level`), correlation (`frequency`, `if_matched_*`, `same_*`), CDB `<list>`, `<time>`, `pcre2`, `<plugin_decoder>`, `<accumulate/>` and others. **Based on the 5.0 migration guide, not run on a 5.0 engine.** A pre-release can change before GA.

## Limits

It knows only the files you give it and the stock ruleset index for the chosen version (4.14.7 for now). Rules in files you did not pass are invisible to it. It does not run Wazuh: it does not test regex syntax, decoder extraction, CDB lists (`7616`), rule matching on real events, or whether a rule ever fires. Stock groups are treated as always loaded.

## Data

`data/stock-4.14.7.json` holds metadata only: stock rule file names and the rule IDs in each, rule group names, decoder names, and the list of static field names. It was extracted from the `ruleset/` directory of `wazuh/wazuh` at tag [`v4.14.7`](https://github.com/wazuh/wazuh/tree/v4.14.7/ruleset); the static field list comes from `src/analysisd/rules.c` at the same tag. The Wazuh ruleset is © Wazuh, Inc. and licensed under [GPLv2](https://github.com/wazuh/wazuh/blob/v4.14.7/LICENSE). No rule or decoder text (regex, match, description) is copied into this repository.

## License

Apache-2.0 for the code in this repository. See `LICENSE`.

---

Built by Dong Nguyen, ATK New Technology. If a rule still does not do what you need, we fix Wazuh rules for people who run it: https://atkvn.com/wazuh-rule-precheck.html