ModSecurity & the OWASP Core Rule Set

The OWASP Core Rule Set (CRS) running on ModSecurity v3 is the WAF stack we put in front of every app we self-host. The engine and the rule set are generic by design — they don’t know what your application’s legitimate traffic looks like. That gap is exactly what a CRS plugin fills: per-app false-positive exclusions so real requests aren’t blocked, plus optional positive-security allowlists so everything the app doesn’t use is denied.

We write and maintain three CRS plugins for the apps on our own infrastructure. All three are CRS 4.0+ plugins, drop into the CRS plugins/ directory, are scoped per-vhost (never global), and are CI-tested on both Apache + mod_security2 and Angie/nginx + libmodsecurity3.

Our three CRS plugins

  • wordpress-hardening-plugin — 40+ rules that add the semantic, typed-parameter validation CRS lacks for WordPress: ORDER BY allowlisting, integer-typed core query vars, xmlrpc / user-enumeration / scanner blocks, login rate limiting and GeoIP. Stops the 2025–2026 wave of AI-discovered plugin SQLi/XSS CVEs before PHP boots.
  • vaultwarden-crs-plugin — makes CRS play nicely with Vaultwarden (the Rust Bitwarden server). Surgical exclusions for the encrypted JSON API plus an opt-in path allowlist derived from Vaultwarden’s real route map.
  • vimbadmin-crs-plugin — exclusions and an opt-in name/route allowlist for the ViMbAdmin mailbox admin panel, so CRS can signature-scan argument values without false-blocking passwords, CSRF tokens or alias lists.

What a CRS plugin actually does

Every CRS plugin has the same three-file shape. CRS auto-loads them in order:

  • <name>-config.conf — the master enable flag and tunables. All three plugins ship disabled by default; you turn each on per-vhost, because a plugin deliberately weakens CRS on its app’s routes and must not run globally.
  • <name>-before.conf — runs before the CRS rules. This is where the false-positive exclusions live (e.g. “don’t run the base64 detector on this encrypted blob”).
  • <name>-after.conf — runs after the CRS rules. This is the opt-in positive-security layer: allow the app’s real paths/arguments, deny the rest.

Scoping is done entirely by a per-vhost SecAction that sets the plugin’s enable variable — there is no Host gate, so the same CRS engine can serve many vhosts and only the one you flag gets the plugin.

Install (any of the three)

Copy the plugin’s three files into your CRS plugins/ directory, then enable it only on the matching vhost. Example on Angie / nginx + libmodsecurity3:

server {
    server_name app.example.com;
    modsecurity on;
    modsecurity_rules '
        SecAction "id:9530001,phase:1,nolog,pass,setvar:tx.vaultwarden-plugin_enabled=1"
    ';
}

On Apache / mod_security2, set the same variable inside the matching <Location> or <VirtualHost> block. Always roll out positive-security allowlists in CRS DetectionOnly first, watch the audit log for allowlist misses, then flip back to blocking.

Get the engine and rules

The WAF stack itself ships from this repository:

Related reading