What is validated, and when
Every supported format (YARA, Sigma, Suricata, Zeek, Wazuh, NSE, CRS, Nova, KQL, Splunk, Elastic, ...) implements its own validate(content) — the same contract, one plug-in per format, documented in rule_type_abstract.py. It runs at three moments:
Errors vs. warnings
validate() returns two separate lists, and a rule's page shows them as two visually distinct boxes — mixing them together used to make a routine documentation suggestion look like the same kind of problem as genuinely broken content, which this page (and that distinction) exists to fix.
Required identity or logic fields are missing, malformed, or hold a value outside a short, fixed set (a status enum, a UUID shape, a risk-score range, ...). A rule with any error is not created at submission time — it becomes a bad rule instead. Seeing this on an already-existing rule's page means the format's validator was tightened after that rule was imported (e.g. a GitHub sync brought in older or looser content) — worth reviewing and fixing.
Shown as: "Rejected pattern present"Everything the format considers good practice but not mandatory — an optional field, a recommended structure, an ID that doesn't quite look right. A rule with only warnings is accepted exactly as submitted — nothing is blocked or rejected. It mainly affects the rule's Quality Score and how easy it is for someone else to review/reuse.
Shown as: "Format & documentation suggestions"Common warning messages
Every format has its own wording, but almost all of its warnings fall into one of these families:
| Missing recommended field: X |
An optional-but-conventional field for this format — author,
references, how_to_implement,
known_false_positives, rule.false_positives,
rule.note, rule.setup, analytic_story, and
similar — is empty or absent. These fields don't change what the rule does; they
document why it exists, how to deploy it, and what to expect from it, which
is exactly what the next person reviewing or reusing the rule needs. This directly
feeds the documentation portion of the Quality Score.
|
| '<id>' doesn't look like an ATT&CK technique/tactic ID |
A MITRE ATT&CK identifier was found but doesn't match the expected shape
(T1059, T1059.001 for a technique, TA0002 for a
tactic). It's kept as-is — nothing is silently corrected — but it likely won't be
recognized by the ATT&CK matrix/auto-tagging. Worth double-checking the ID against
attack.mitre.org.
|
| Missing or empty rule.threat / mitre_attack_id / analytic_story | No ATT&CK mapping or campaign grouping was found at all. Some formats (Splunk, Elastic) group individual detections into a broader "story"/campaign for easier discovery — this flags a rule that stands alone with no such grouping. Not required, but it's how someone browsing by technique or by campaign would ever find this rule. |
Structural hints (e.g. Splunk's type 'TTP' normally requires a 'finding' block, Sigma's query-pipe check, NSE's luac not found/Missing action() function) |
Format-specific best practices — a detection type that usually pairs
with a particular metadata block, a query shape that looks incomplete, a script
missing the entry point its engine expects. Each of these is documented in that
format's own adapter (app/features/rule/rule_format/available_format/)
and reflects a convention from that format's own upstream project, not a
Rulezet-invented rule.
|
Common error messages
| Missing or empty required field: X |
A field every rule of this format must have to even be meaningfully identified or
executed — a title/name, the actual detection logic (search,
query, a YARA rule block, ...), a description. Without it,
the rule can't be reliably titled, searched, deduplicated, or run.
|
| '<value>' is not one of: [...] | A field is restricted to a short, fixed set of values by that format's own specification — a status, a severity, a query-language identifier, a rule category — and the value found isn't one of them. These enums are only hard-enforced when they're small and stable; large or evolving catalogs (ATT&CK technique IDs, story names, tags) are never hard-enforced this way, since that would reject legitimate rules that simply use a value outside this project's own knowledge of the format. |
| rule_id/id must be a valid UUID, got: ... | The format expects a UUID as its own external identifier and what's there doesn't match that shape (wrong length, invalid characters, ...). This is used for cross-referencing the rule to its upstream source and for duplicate detection — a malformed one usually means a typo in the original file itself. |
| TOML/YAML/XML parse error: ... | The content isn't even syntactically valid in the format's own file format — a structural typo (unclosed quote/bracket, bad indentation, ...) that prevents parsing the rest of the rule at all. |
How this affects the Quality Score
Separately from validate(), each format also implements documentation_signals(content) — a small checklist of whether the rule documents its references, false positives, implementation notes, and ATT&CK mapping. A missing signal there is exactly the kind of thing a Missing recommended field warning above is telling you about — fixing the warning is generally how a rule's documentation-completeness score goes up.
Corpus-wide checks (Suricata datasets & flowbits)
One check is genuinely about interference between rules rather than a single
rule's own content: Suricata rules can reference a shared dataset
filename or a flowbit/xbit/hostbit name that other,
independently authored rules also read or write. Two rules silently disagreeing about the
same resource name can crash the detection engine at load time or corrupt each other's
state. This is checked once, at submission time only (not re-run on every page view like
the per-rule warnings above), and surfaces as its own rejection/warning right when a
conflicting Suricata rule is submitted.
False-positive gate & risk tag proposals
The other corpus-wide check, reachable from Tags → Rule Validation,
is entirely separate from validate() and doesn't run on
submission — it's a deliberate, on-demand admin action powered by
rulezet-validation:
every rule is scanned against a corpus of known-clean binaries, and any rule that fires on
one is quarantined — a concrete false-positive risk, demonstrated against
real files rather than inferred from the rule's own text.
high, 5-19 medium, 1-4 low; a rule quarantined
with no hits on record is cannot-be-judged, never guessed as "low"), in the
exact vocabulary of the MISP false-positive taxonomy already available as
tags here (false-positive:risk="high", etc.).
false-positive:risk tag of its own, both values are shown side by side.
Agreement needs no action; a disagreement between what a rule says about
itself and what was actually observed is the case most worth a reviewer's
attention, and is surfaced first.