A Bitwarden-compatible password vault API, written in Rust, compiled to WebAssembly, and deployed on Cloudflare Workers.
It implements the Bitwarden REST API, so the official clients (web, browser extension, desktop, mobile, CLI) connect without any patches. Vault data is encrypted on the client; the server only ever sees ciphertext.
Vaultwarden depends on Rocket, Tokio, and OpenSSL, none of which run on Workers. This is a port that targets D1, R2, KV, and Durable Objects instead, so there's no VM or container to manage.
- Register, login via the OAuth token endpoint, RS256 access/refresh JWTs, device tracking
- Full vault sync (ciphers, folders, collections, organizations)
- Cipher CRUD, including soft-delete and trash
- Encrypted folders
- Organizations with collection-based access, groups, and policies
- TOTP two-factor with recovery codes
- Sends: encrypted text and file sharing, with passwords, expirations, and access limits
- Real-time push over WebSockets via a Durable Object speaking SignalR's binary MessagePack protocol
- Cron that purges expired sends and ciphers trashed for 30+ days
| Layer | Tech |
|---|---|
| Language | Rust (2024 edition), wasm32-unknown-unknown target |
| Runtime | Cloudflare Workers |
| Database | Cloudflare D1 |
| File storage | Cloudflare R2 |
| Cache | Cloudflare KV |
| WebSockets | Cloudflare Durable Objects |
| Crypto | Web Crypto API (PBKDF2, RSA, HMAC, TOTP). No native crypto crates; they don't build for wasm. |
| Tests | Vitest + Miniflare |
src/
lib.rs # router, entry point, fetch handler
config.rs # request context, env/secret access
error.rs # error type -> HTTP response
auth/ # JWT (RS256), claims, auth guard
crypto/ # PBKDF2, RSA, HMAC, TOTP via SubtleCrypto
db/ # D1 models and queries
middleware/ # CORS, security headers
api/ # route handlers
models/ # API request/response types
notifications.rs # SignalR framing + Durable Object handlers
jobs.rs # cron handlers
migrations/ # D1 schema
integration-tests/ # Vitest + Miniflare tests
- Rust with the wasm target:
rustup target add wasm32-unknown-unknown - Node.js (for Wrangler and the tests)
- A Cloudflare account with D1, R2, KV, and Durable Objects available
Clone and set up:
git clone https://github.com/connyay/orangevault.git
cd orangevaultFill in your Cloudflare resource IDs in wrangler.toml:
[[d1_databases]]
database_id = "<your-d1-database-id>"
[[kv_namespaces]]
id = "<your-kv-namespace-id>"Create the D1 database and apply migrations:
npx wrangler d1 create orangevault
npx wrangler d1 migrations apply orangevaultCreate the R2 bucket:
npx wrangler r2 bucket create orangevault-filescargo install -q worker-build@^0.8 && worker-build --releaseOutput lands in build/.
npx wrangler devIntegration tests run against a local Miniflare worker:
cd integration-tests
npm install
npm testOther options:
npm run test:watch # watch mode
npm run test:no-build # skip the worker rebuildOrangeVault can serve the patched Bitwarden web client from dani-garcia/bw_web_builds as a Workers Assets binding. The bundle isn't checked in; fetch it before you build:
scripts/fetch-web-vault.sh # latest release
scripts/fetch-web-vault.sh v2026.2.0 # pinned versionThat populates ./web-vault/, which wrangler.toml wires up as the ASSETS binding. SPA fallback is on, so Angular routes fall through to index.html while API paths (/api/*, /identity/*, /notifications/*, etc.) go to the worker. The worker also serves /app-id.json dynamically so the WebAuthn origin matches DOMAIN instead of whatever placeholder was baked into the bundle.
If you don't want the web vault, leave ./web-vault/ empty or drop the [assets] block from wrangler.toml.
npx wrangler deploySet in wrangler.toml under [vars] or via wrangler secret put:
| Variable | Description | Default |
|---|---|---|
DOMAIN |
Base URL of your instance | https://vault.example.com |
SIGNUPS_ALLOWED |
Allow new user registration | true |
WEB_VAULT_ENABLED |
Controls the dynamic /app-id.json handler. Set false to turn off the FIDO2 trusted-apps endpoint. Static asset serving is controlled separately, by whether ./web-vault/ is populated. |
true |
RSA signing keys are generated on first use and stored in KV.
| Binding | Service | Purpose |
|---|---|---|
DB |
D1 | All vault data |
FILES |
R2 | Send file uploads |
CACHE |
KV | RSA keys and general cache |
USER_NOTIFIER |
Durable Object | Per-user WebSockets for sync push |
| Schedule | Task |
|---|---|
| Every 6 hours | Delete expired sends and their R2 files |
| Daily | Delete ciphers trashed 30+ days ago |
AGPL-3.0, matching vaultwarden, which this is a port of. See LICENSE.