# How the Vaultwarden Send Feature Works: A Technical Guide to Secure File Sharing

> Explore the technical details of Vaultwarden Send for secure file sharing. Learn about its PBKDF2 hashing, JWT tokens, and expiration policies to protect your data.

- Repository: [Daniel García/vaultwarden](https://github.com/dani-garcia/vaultwarden)
- Tags: deep-dive
- Published: 2026-03-07

---

**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`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/sends.rs) and the data model defined in [`src/db/models/send.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/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`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/sends.rs)), which instantiates a `Send` struct and persists it via `Send::save` in [`src/db/models/send.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/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:

1. **Request upload URL**: The client calls `/api/sends/file/v2`, handled by `post_send_file_v2` (lines 30-45), which validates quotas and returns a temporary UUID.
2. **Binary upload**: The client POSTs to `/api/sends/<send_id>/file/<file_id>`, processed by `post_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`](https://github.com/dani-garcia/vaultwarden/blob/main/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::purge` cleanup

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`](https://github.com/dani-garcia/vaultwarden/blob/main/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

```bash
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)

```bash

# 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 "data=@report.pdf"

```

### Accessing a Password-Protected Send

```bash
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.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/api/core/sends.rs) endpoints that validate against [`src/db/models/send.rs`](https://github.com/dani-garcia/vaultwarden/blob/main/src/db/models/send.rs) policies.
- 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_url` and validated by `download_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::delete` and `operator.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`](https://github.com/dani-garcia/vaultwarden/blob/main/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`](https://github.com/dani-garcia/vaultwarden/blob/main/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`](https://github.com/dani-garcia/vaultwarden/blob/main/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>`.