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 admintoken, 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.phpscanner noise before it reaches the backend. The route map is derived from Vaultwarden’s source (src/main.rsmount points +src/api/web.rsstatic routes) and the bundled web-vault static tree extracted from the official Docker image — not guessed.
Additional hardening
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 paths to a single path segment 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.
What’s new in 2.0.0
Version 2.0.0 is a full re-verification of the route map against fresh Vaultwarden source and the extracted web-vault Docker tree, plus fixes to four issues found in a dual (independent + self) security audit:
- Trailing-slash allowlist bypass — fixed. In earlier releases the root branch matched the trailing slash of any path, so
/wp-login.php/,/.env/and/phpmyadmin/sailed straight past the path allowlist. The branch is now anchored. - Enforcement rules could disable themselves — fixed. The method / Content-Type / path deny rules were tagged
OWASP_CRS, so the auth-path false-positive strip (ctl:ruleRemoveByTag=OWASP_CRS) — or any host-levelOWASP_CRStag strip — silently switched the positive-security layer off on those paths. The tag was removed from the enforcement rules. - Deep-nested probe tightening. The mount/static-dir branch matched its token at any depth, so
/wp-content/images/shell.phpslipped through on theimagessegment. It now allows at most one optional basepath segment. - Case-insensitive Content-Type. The
application/jsoncheck now folds case, soContent-Type: Application/JSONno longer draws a false415.
The fresh scan also corrected the route map: root-level hashed JSON/XML assets are now allowed (a hashed web-vault bundle was being 404’d), the stale root-level admin-bundle branch was dropped (those files are served under /vw_static/, not the webroot), and the token form-field allowlist gained the send-access fields send_id and password_hash_b64. Every fix is proven by a Coraza runtime test that replays the exact bypass and confirms the deny fires.
Optional arg-name allowlist (experimental)
JSON bodies are never name-allowlisted, but the two non-JSON surfaces are stable and fully enumerable, so the plugin 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.
