waf_crs_curated/lib.rs
1// SPDX-FileCopyrightText: 2026 0x00spor3
2// SPDX-License-Identifier: Apache-2.0
3
4//! Curated OWASP CRS / ModSecurity rule content for [Light WAF](https://github.com/0x00spor3/Light-WAF).
5//!
6//! The core ships the `SecRule` parser and engine (`waf_detection::crs`); this crate ships only
7//! **content**: high-confidence CVE, application and evasion signatures that **add** to the core's
8//! native detection modules and contribute to the same cumulative anomaly score. It never
9//! re-implements the baseline (SQLi/XSS/traversal/RCE/LFI-RFI stay in the core).
10//!
11//! - The `.conf` files are embedded at compile time. Build a module from [`curated_source`] with
12//! `waf_detection::crs::CrsModule::from_source` and inject it with
13//! `Proxy::builder(&cfg).add_module(..)` (or a `module_factory`).
14//! - Only constructs supported by the core engine are used. A test loads the content through that
15//! engine and asserts that exactly [`EXPECTED_LOADED`] rules load with **zero** skipped, so a rule the
16//! engine could not run fails the build instead of silently shrinking coverage.
17//! - Rule ids live in the reserved `9000000+` range.
18
19/// The curated `.conf` files, embedded at compile time in include order.
20const FILES: &[&str] = &[
21 include_str!("../rules/913-recon.conf"),
22 include_str!("../rules/920-protocol.conf"),
23 include_str!("../rules/930-lfi.conf"),
24 include_str!("../rules/932-rce.conf"),
25 include_str!("../rules/934-generic.conf"),
26 include_str!("../rules/944-java.conf"),
27 include_str!("../rules/cve.conf"),
28];
29
30/// How many rules the curated content is expected to load under the OPEN CRS engine's supported
31/// subset. The compat gate asserts the engine's `loaded_count()` equals this **exactly**: any edit
32/// that introduces an unsupported construct (which is *skipped*, not loaded) then fails the test
33/// rather than silently reducing coverage — the lesson of the B2 setvar-scoring bug.
34pub const EXPECTED_LOADED: usize = 28;
35
36/// The full curated ruleset as one `seclang` source: the embedded `.conf` files concatenated in
37/// include order, ready for `waf_detection::crs::CrsModule::from_source`.
38pub fn curated_source() -> String {
39 FILES.join("\n")
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 #[test]
47 fn embeds_every_rule_file() {
48 // Dep-free sanity that `include_str!` picked up all files (the gate proves they LOAD).
49 // One representative id per file (913 / 920 / 930 / 932 / 934 / 944 / cve).
50 let s = curated_source();
51 for id in [
52 "id:9000001", "id:9000020", "id:9000031", "id:9000040", "id:9000050", "id:9000003",
53 "id:9100001",
54 ] {
55 assert!(s.contains(id), "curated_source missing {id}");
56 }
57 }
58}