How the Vaultwarden Send Feature Works: A Technical Guide to Secure File Sharing
Vaultwarden Send is a secure, ephemeral file and text sharing system that uses PBKDF2 password hashing, signed JWT download tokens, and configurable expiration policies to protect shared content.
The Vaultwarden Send feature implements Bitwarden’s protocol for creating time-limited, optionally password-protected shares directly within your self-hosted Vaultwarden instance. Unlike traditional file sharing, Send operates through a stateful API that enforces strict access controls, storage quotas, and cryptographic verification before releasing any content. Understanding how Vaultwarden Send works requires examining the HTTP endpoint flow in src/api/core/sends.rs and the data model defined in src/db/models/send.rs.
Send Lifecycle: Create, Upload, and Access
Vaultwarden processes every Send through three distinct stages, each handled by specific handler functions in the Rust codebase.
Stage 1: Metadata Creation
When a user initiates a Send, the client sends a POST request to /api/sends. The post_send handler invokes create_send (lines 29-66 in src/api/core/sends.rs), which instantiates a Send struct and persists it via Send::save in src/db/models/send.rs. This record stores the encrypted payload, expiration dates, access limits, and optional password hash—not the plaintext content.
Stage 2: File Upload Handling
For file attachments (type = File), Vaultwarden supports a two-step upload flow:
- Request upload URL: The client calls
/api/sends/file/v2, handled bypost_send_file_v2(lines 30-45), which validates quotas and returns a temporary UUID. - Binary upload: The client POSTs to
/api/sends/<send_id>/file/<file_id>, processed bypost_send_file_v2_data(lines 71-80). This function validates the 525 MiB size limit (SIZE_525_MB) and writes the data via the opendal storage abstraction to local filesystem or S3.
Stage 3: Secure Retrieval
Recipients access Sends using a Base64-URL encoded access_id. The post_access function (lines 56-84) validates expiration dates, access counts, and disabled flags. For files, post_access_file (lines 107-132) generates a download credential—a JWT for local storage or a 5-minute presigned URL for S3 backends.
Security Considerations for Vaultwarden Send
The Vaultwarden Send feature implements defense-in-depth security through cryptographic hashing, policy enforcement, and time-bound access tokens.
Password Protection Implementation
Send passwords are never stored in plaintext. The Send::set_password method (lines 81-95 in src/db/models/send.rs) derives a hash using PBKDF2-HMAC-SHA256 with 100,000 iterations and a random 64-byte salt. Verification occurs through verify_password_hash using constant-time comparison to prevent timing attacks.
Sender Privacy Controls
The hide_email flag enables anonymous sharing. When set, the Send::creator_identifier serialization (lines 106-119) omits the creatorIdentifier field from API responses, preventing recipient access to the sender’s Vaultwarden email address.
Organization Policy Enforcement
Before processing any Send operation, Vaultwarden checks enterprise policies via enforce_disable_send_policy and enforce_disable_hide_email_policy (lines 99-108). These functions respect both global CONFIG.sends_allowed settings and organization-level DisableSend or DisableHideEmail policies, ensuring administrative control over data exfiltration vectors.
Access Limitations and Expiration
Each Send enforces hard limits configured at creation:
- Max access count: Hard stop after N retrievals
- Expiration date: Time-based invalidation checked in
post_access(lines 60-78) - Deletion date: Final cutoff triggering
Send::purgecleanup
These constraints prevent indefinite exposure of sensitive files.
Storage Quotas and File Validation
Vaultwarden enforces dual limits: a per-user quota (CONFIG.user_send_limit) and a per-file cap of 525 MiB. Size validation occurs in both legacy and v2 upload endpoints (lines 48-62 and 21-36 in sends.rs), rejecting oversized payloads before storage allocation.
Tamper-Proof Download Mechanisms
For local file storage, Vaultwarden generates signed JWTs containing send_id and file_id claims via download_url (lines 68-78). The download_send handler (lines 81-86) validates this token before streaming bytes, ensuring requestors cannot forge download links. Remote storage uses opendal's presign_read with 5-minute expiration windows, minimizing window of exposure for direct object storage URLs.
Code Examples
Creating a Text Send
curl -X POST https://vaultwarden.example/api/sends \
-H "Authorization: Bearer <access-token>" \
-H "Content-Type: application/json" \
-d '{
"type": 0,
"name": "Secure Note",
"text": { "text": "Encrypted content here" },
"key": "<client-encryption-key>",
"deletionDate": "2025-12-31T23:59:59Z",
"hideEmail": true
}'
Uploading a File Send (V2 API)
# Step 1: Create metadata
curl -X POST https://vaultwarden.example/api/sends \
-H "Authorization: Bearer <token>" \
-d '{"type":1,"name":"report.pdf","file":{"fileName":"report.pdf"}}'
# Step 2: Request upload URL
curl -X POST https://vaultwarden.example/api/sends/file/v2 \
-H "Authorization: Bearer <token>" \
-d '{"type":1,"fileLength":1048576,"file":{"fileName":"report.pdf"}}'
# Step 3: Upload binary data
curl -X POST https://vaultwarden.example/api/sends/<send_id>/file/<file_id> \
-H "Authorization: Bearer <token>" \
-F "[email protected]"
Accessing a Password-Protected Send
curl -X POST https://vaultwarden.example/api/sends/access/<access_id> \
-H "Content-Type: application/json" \
-d '{"password":"user-supplied-password"}'
Summary
- Vaultwarden Send creates ephemeral shares through
src/api/core/sends.rsendpoints that validate againstsrc/db/models/send.rspolicies. - Passwords use PBKDF2-HMAC-SHA256 with 100,000 iterations and 64-byte salts, stored as hashes only.
- File uploads enforce a 525 MiB limit and respect per-user quotas configured in
CONFIG.user_send_limit. - Downloads use short-lived JWTs (local storage) or 5-minute presigned URLs (S3) generated by
download_urland validated bydownload_send. - Policy enforcement functions check organization settings before allowing Send creation or access.
- Purge routines automatically delete expired Sends and their associated storage files via
Send::deleteandoperator.remove_all.
Frequently Asked Questions
How does Vaultwarden protect Send passwords?
Vaultwarden hashes Send passwords using PBKDF2-HMAC-SHA256 with 100,000 iterations and a cryptographically random 64-byte salt. The plaintext password is never stored; only the derived hash persists in the database. Verification uses constant-time comparison functions to prevent timing-based attacks, as implemented in src/db/models/send.rs lines 81-95.
What is the maximum file size for Vaultwarden Send?
The maximum individual file size is 525 MiB (defined as SIZE_525_MB in the source). Additionally, administrators can configure per-user storage quotas via CONFIG.user_send_limit. The upload endpoints in src/api/core/sends.rs validate these limits before accepting binary data through the opendal storage abstraction.
Can organization admins disable the Send feature entirely?
Yes. Vaultwarden checks organization policies through enforce_disable_send_policy and enforce_disable_hide_email_policy (lines 99-108 in sends.rs). Admins can set the DisableSend policy to prevent creation, or DisableHideEmail to force sender identification. The system also respects a global CONFIG.sends_allowed flag that disables Send functionality server-wide.
How long do file download links remain valid?
For locally stored files, Vaultwarden generates signed JWTs that are valid for a single verification in download_send. For remote storage backends like S3, the system generates presigned URLs through opendal’s presign_read method that expire after 5 minutes. Recipients must retrieve files within this window or request a new access token via POST /api/sends/<send_id>/access/file/<file_id>.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →