🔗 Vaultwarden: github.com/dani-garcia/vaultwarden
📖 Read the write-up: Self-Hosted Vaultwarden — Docker Setup, Clients & Full Guide
A drop-in OWASP CRS 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.
Why this differs from a form-app plugin. Vaultwarden is a JSON API: the web vault, browser extensions, mobile, desktop and the Bitwarden CLI all POST
application/jsonbodies whose argument names are JSON keys that vary per endpoint and per client version. A vimbadmin-styleARGS_NAMESallowlist would therefore false-block real clients. This plugin deliberately ships no arg-name allowlist — its positive-security layer is a PATH allowlist only. Bodies are end-to-end encrypted (EncString2.<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.
It does two things:
-
False-positive exclusions (
vaultwarden-before.conf) — surgical, host-scoped exclusions so legitimate inputs (the Argon2 admintoken, the OAuth password-grant hashes on/identity/connect/token, the EncString cipher/account/send blobs under/api, user-supplied icon domains) don't trip CRS. -
Positive security / path allowlist (
vaultwarden-after.conf, opt-in) — allow Vaultwarden's real routes, deny everything else. Any path outside Vaultwarden's mount points (/api,/identity,/admin,/events,/icons,/notifications,/attachments, the built-in static routes and the web-vault static tree) is denied. This stops the usual/.env//wp-login.phpscanner noise before it reaches the backend, regardless of payload. No arg-name allowlist (see above).
The route map is derived from Vaultwarden's source
(src/main.rs mount points + src/api/web.rs static routes) and the
bundled web-vault static tree extracted from vaultwarden/server:latest —
not guessed. It was last re-verified against source commit 169aa5e.
The positive-security layer also: enforces an HTTP-method
allowlist (only GET/POST/PUT/DELETE/HEAD/OPTIONS — TRACE, CONNECT,
PATCH, junk verbs are denied, 9530220); requires application/json on
/api writes (except multipart /api/sends/file* and cipher attachment
uploads, 9530225); anchors static-file extensions 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 instead of a silent 404.
- CRS Version 4.0 or newer
- ModSecurity compatible Web Application Firewall
Copy the three files into your CRS plugins/ directory:
plugins/vaultwarden-config.conf
plugins/vaultwarden-before.conf
plugins/vaultwarden-after.conf
CRS loads plugins/*-config.conf, then *-before.conf (before the rules),
then *-after.conf (after the rules) automatically.
Edit vaultwarden-config.conf:
| Variable | Default | Meaning |
|---|---|---|
tx.vaultwarden-plugin_enabled |
0 |
Master on/off. OFF by default — the plugin weakens CRS on the Vaultwarden routes, so it must be enabled per vhost, not globally. Set to 1 only in the Vaultwarden server/location block (see Roll-out). |
tx.vaultwarden-plugin_positive_security |
follows _enabled |
Turn the path-allowlist layer on/off. Defaults to the enable flag; set to 0 explicitly in the enable block to run exclusions without the allowlist. |
tx.vaultwarden-plugin_argname_allowlist |
0 |
Experimental, independent opt-in for the arg-name allowlists (9530240 token form fields, 9530245 GET query names). Does not follow _enabled; enable only after a DetectionOnly burn-in. |
Scoping is done entirely by the per-vhost enable flag — there is no Host gate. Enable the plugin only on the Vaultwarden vhost, e.g. (Angie / nginx + libmodsecurity3):
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 inside the matching
<Location> / <VirtualHost> block.
- Install, then enable the plugin in the Vaultwarden vhost only
(
setvar:tx.vaultwarden-plugin_enabled=1). The exclusions are safe immediately and never touch other vhosts on the same CRS engine. - Run CRS in DetectionOnly and watch the audit log for
9530230hits — those are paths missing from the route allowlist. If you front Vaultwarden with extra routes (a reverse-proxy health check, a custom connector), add them to the inline allowlist regex on rule9530230invaultwarden-after.conf. - Flip CRS back to blocking mode.
Rule ID range: 9,530,000 – 9,530,999 (block base 9,530,000; free in the CRS plugin registry, pending formal assignment).
Every push/PR runs six GitHub Actions workflows (all but Lint get a badge above):
| Workflow | What it does |
|---|---|
| Lint | Rule-ID-range (9530000–9530999) / duplicate-ID / @pmFromFile / test-reference checks, then the official coreruleset/crs-plugin-test-action lint. |
| Integration Tests | Plugin-structure gates (no host gate, opt-in allowlist, no ARGS_NAMES allowlist, conditional config defaults, ver: on every rule). |
| Apache/v2 | Runs the go-ftw regression suite on real Apache httpd + mod_security2 (apache2ctl -t gates parse). |
| NGINX/v3 | Same suite on Angie + libmodsecurity3 3.0.14 — a production mirror (angie -t gates parse). |
| NGINX/Coraza | Loads every plugin file into coraza.NewWAF() then replays runtime transactions — Coraza fails hard at config load where ModSecurity warns, and load-OK ≠ fires (PRs only). |
| WAF Corpus | Replays real scanner/probe paths to prove the path/method allowlists deny them (regression guard for the trailing-slash + deep-nest bypass classes). |
The three-engine harness lives under tests/integration/;
go-ftw cases under tests/regression/ and
tests/security/.
Set tx.vaultwarden-plugin_enabled to 0 (the default), or remove the plugin
files from the plugins/ directory entirely.
The CRS plugin is the WAF half. For the edge half — per-endpoint rate
limiting (/admin, /identity/connect/token, register, send-download),
a native path allowlist that 404s unknown routes, a method allowlist, security
headers and body/timeout caps — see contrib/angie/vault.conf
and contrib/README.md.
Rate limiting in particular cannot be done in the plugin: libmodsecurity3 (v3) has no persistent per-IP collections, so it belongs at the edge (or in fail2ban). Run the plugin and the contrib vhost together for belt-and-braces.
Open a new issue or pull request. For issues, include:
- CRS Version
- ModSecurity/Coraza Version
- modsec audit logs
- what caused the false positive
- Vaultwarden: https://github.com/dani-garcia/vaultwarden
- Write-up / deployment guide: Self-Hosted Vaultwarden on deb.myguard.nl
- ViMbAdmin CRS plugin (same author, form-app variant with arg-name allowlist): https://github.com/myguard-labs/vimbadmin-crs-plugin
