Skip to main content
The whatwaf detector system identifies WAFs by analyzing HTTP response patterns. Each detector implements a common Detector trait and uses pattern matching to identify specific WAF vendors.

The Detector trait

All detectors implement the Detector trait defined in src/detectors/mod.rs:3:
pub trait Detector: Sync + Send {
    fn name(&self) -> &'static str;
    fn detect(&self, resp: &HttpResponse) -> bool;
}
The trait requires two methods:
  • name() - Returns the human-readable name of the WAF (e.g., “Cloudflare”, “ArvanCloud”)
  • detect() - Analyzes an HttpResponse and returns true if the WAF is detected

How detectors work

When whatwaf scans a target URL, it:
  1. Sends multiple probe requests with different payloads (plain, XSS, SQL injection, LFI)
  2. Collects HTTP responses containing status codes, headers, and body content
  3. Passes each response through all registered detectors via run_detectors() in src/detector.rs:6
  4. Returns the names of all detectors that returned true
The detection process runs in parallel across all detectors:
pub fn run_detectors(resp: &HttpResponse) -> Option<Vec<&'static str>> {
    let mut matches = Vec::new();
    for d in inventory::iter::<&'static dyn Detector> {
        if d.detect(resp) {
            matches.push(d.name());
        }
    }

    if matches.is_empty() {
        None
    } else {
        Some(matches)
    }
}

Pattern matching capabilities

Detectors use helper methods from HttpResponse to match patterns: Header matching:
  • has_header() - Check if specific headers exist
  • header_has() - Check if a header contains specific text
  • header_matches() - Match headers against regex patterns
Body matching:
  • body_has() - Check if body contains specific text
  • body_matches() - Match body against regex patterns
Status code checks:
  • is_forbidden() - Returns true if status is 403
  • is_not_found() - Returns true if status is 404
  • is_error() - Returns true if status is 4xx or 5xx
Match modes:
  • MatchMode::Any - At least one pattern must match
  • MatchMode::All - All patterns must match

Extensibility

The detector system is designed for easy extension:
  1. Create a new detector - Add a new .rs file in src/detectors/
  2. Implement the trait - Define the Detector trait for your struct
  3. Register with inventory - Use inventory::submit! to auto-register
  4. Add the module - Declare the module in src/detectors/mod.rs
Example minimal detector:
use crate::detectors::Detector;
use crate::utils::checks::MatchMode;
use crate::utils::http::HttpResponse;

pub struct MyWAF;

impl Detector for MyWAF {
    fn name(&self) -> &'static str {
        "MyWAF"
    }

    fn detect(&self, resp: &HttpResponse) -> bool {
        resp.header_has("server", &["MyWAF"], MatchMode::Any)
    }
}

inventory::submit! {
    &MyWAF as &dyn Detector
}
The inventory crate automatically collects all registered detectors at compile time, eliminating the need for manual registration or configuration.

list_detectors() function

The library exposes a list_detectors() function in src/lib.rs:61 that returns all registered detector names:
pub fn list_detectors() -> Vec<&'static str> {
    inventory::iter::<&'static dyn detectors::Detector>
        .into_iter()
        .map(|d| d.name())
        .collect()
}
This allows users to programmatically list all available WAF detectors without scanning a target.

Build docs developers (and LLMs) love