Skip to content

myguard-labs/vaultwarden-crs-plugin

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🔗 Vaultwarden: github.com/dani-garcia/vaultwarden

📖 Read the write-up: Self-Hosted Vaultwarden — Docker Setup, Clients & Full Guide

Vaultwarden (Bitwarden) OWASP CRS Plugin

Integration Tests Apache/v2 NGINX/v3 NGINX/Coraza WAF Corpus

Defense-in-depth: the vaultwarden-crs-plugin adds a PATH allowlist and false-positive exclusions at the WAF edge before requests reach the end-to-end encrypted Vaultwarden Rust backend and a hardened Docker container

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/json bodies whose argument names are JSON keys that vary per endpoint and per client version. A vimbadmin-style ARGS_NAMES allowlist 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 (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.

It does two things:

  1. False-positive exclusions (vaultwarden-before.conf) — surgical, host-scoped exclusions so legitimate inputs (the Argon2 admin token, 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.

  2. 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.php scanner 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/OPTIONSTRACE, 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.

Requirements

  • CRS Version 4.0 or newer
  • ModSecurity compatible Web Application Firewall

Install

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.

Configure

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.

Roll-out

  1. 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.
  2. Run CRS in DetectionOnly and watch the audit log for 9530230 hits — 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 rule 9530230 in vaultwarden-after.conf.
  3. 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).

Continuous integration

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/.

Disabling the plugin

Set tx.vaultwarden-plugin_enabled to 0 (the default), or remove the plugin files from the plugins/ directory entirely.

Edge hardening (rate limiting, native path/method allowlist)

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.

Reporting false positives

Open a new issue or pull request. For issues, include:

  • CRS Version
  • ModSecurity/Coraza Version
  • modsec audit logs
  • what caused the false positive

See also

About

OWASP CRS plugin for Vaultwarden (Bitwarden): FP exclusions + opt-in path allowlist. JSON-API aware (no arg-name allowlist).

Topics

Resources

Stars

Watchers

Forks

Releases

Sponsor this project

Used by

Contributors

Languages