vaultwarden-crs-plugin

A drop-in OWASP CRS 4.0+ plugin that makes the Core Rule Set play nicely with Vaultwarden — the Rust, Bitwarden-compatible server — and optionally locks the host down to Vaultwarden’s known route map.

GitHub  ·  Self-hosted Vaultwarden guide  ·  All CRS plugins

Why Vaultwarden needs a different approach

Vaultwarden is a JSON API: the web vault, browser extensions, mobile, desktop and the Bitwarden CLI all POST application/json bodies whose argument names are JSON keys that vary per endpoint and per client version. A vimbadmin-style ARGS_NAMES allowlist would false-block real clients, so this plugin deliberately ships no arg-name allowlist for JSON bodies. Its positive-security layer is a path allowlist only. Bodies are end-to-end encrypted (EncString 2.<iv>|<ct>|<mac> blobs + base64), so the before-rules strip the known-noisy base64/SQLi/PHP target families on the encrypted write paths rather than weakening the whole engine.

What it does

  • False-positive exclusions (vaultwarden-before.conf) — surgical, host-scoped exclusions so legitimate inputs don’t trip CRS: the Argon2 admin token, the OAuth password-grant hashes on /identity/connect/token, the EncString cipher/account/send blobs under /api, and user-supplied icon domains.
  • Positive security / path allowlist (vaultwarden-after.conf, opt-in) — allow Vaultwarden’s real mount points (/api, /identity, /admin, /events, /icons, /notifications, /attachments, static routes and the web-vault tree), deny everything else. Stops /.env / /wp-login.php scanner noise before it reaches the backend. The route map is derived from Vaultwarden’s source (src/api/mod.rs + src/api/web.rs), not guessed.

Hardening since 1.1.0

The positive-security layer also enforces an HTTP-method allowlist (only GET/POST/PUT/DELETE/HEAD/OPTIONS; TRACE/CONNECT/PATCH/junk verbs denied), requires application/json on /api writes (except multipart sends and attachment uploads), anchors static-file extensions to known prefixes so a deep fake path like /x/y/z.json no longer slips through, and feeds the CRS inbound anomaly score on every block so fail2ban / CRS DoS layers see the probe.

Optional arg-name allowlist (1.2.0, experimental)

JSON bodies are never name-allowlisted, but the two non-JSON surfaces are stable and fully enumerable, so 1.2.0 adds an optional, separately-gated allowlist for them: the /identity/connect/token form fields (fixed by the ConnectData struct) and GET query-string parameter names. These live in before.conf so they evaluate before CRS’s anomaly-blocking rule. Enable only after a DetectionOnly burn-in.

Install & enable

Copy the three files (vaultwarden-config.conf, vaultwarden-before.conf, vaultwarden-after.conf) into your CRS plugins/ directory. The plugin is OFF by default — it weakens CRS on Vaultwarden’s routes, so enable it per vhost only, never globally:

server {
    server_name vault.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 in the matching <Location>/<VirtualHost>. Roll the path allowlist out in CRS DetectionOnly first, then flip to blocking. Requires CRS 4.0+ on any ModSecurity-compatible WAF.