* fix(attachments): enforce quota at v2 metadata reservation
POST /ciphers/<id>/attachment/v2 persisted client-declared fileSize without
checking USER_ATTACHMENT_LIMIT or ORG_ATTACHMENT_LIMIT, allowing a writer to
exhaust org/user quota via metadata-only reservations before uploading bytes.
Extract shared quota arithmetic into attachment_quota_remaining and use it
from post_attachment_v2 (size_adjust=0) and save_attachment so both paths
stay consistent. Add unit tests covering limit, size_adjust, and the v2
over-quota rejection case.
* Remove unneeded tests and simplify
---------
Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
Upgrade Rocket to v0.5.2 which has updated all the crate dependencies.
Also adjusted some code which I changed with Rkt and during that found
some small bugs and fixed those too.
Adjusted the ETag Caching a bit.
Also updated other crates and GHA.
Signed-off-by: BlackDex <black.dex@gmail.com>
CollectionGroup::to_json_details_for_group derived manage from
!read_only && !hide_passwords, so the group permission "Can edit items"
was reported as "Can manage collection" by collections/details, while
groups/<id>/details reported the stored value. Saving the collection
access dialog in the web vault then persisted manage=true.
Fixes#7794
Co-authored-by: Alexander Widak <deltatree.widak@gmail.com>
Since #7666 the sync profile's `organizationsNew` also lists Accepted
(not yet confirmed) memberships. Their `akey` is still empty, so the
response contained `"key": ""`, whereas upstream sends `null` until the
member is confirmed.
Bitwarden Android 2026.9 prefers `organizationsNew` and only skips
organizations whose key is null. The empty string is handed to the SDK,
which rejects it ("Invalid asymmetric type, got type 0 with 1 parts"),
so the whole `initializeOrgCrypto` call fails: none of the user's
organization keys are loaded and every organization item and collection
fails to decrypt on that device.
Send null for an empty key, matching upstream and the existing handling
of the user key in `GET /api/accounts/keys`.
Claude-Session: https://claude.ai/code/session_011UHYQWTz2PkXSCfucaRMZB
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix several scheduled job issues
Several jobs could cause a panic, or stop without running through all
items because of this panic. This could cause missed items until that
panic somehow resolved it self, and probably would never work fully
again.
This could cause emergency access for example to never work again.
Besides this, emergency access was also able to be panic if the
wait-time was out-of-bounds and date/time calculation might fail.
This is also fixed now by using `checked` functions and continue upon
those issues so all awaiting items will be handled.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Fix several possible panics and overflows
Adjust several integer types to be a lower or unsigned type.
Most types were too large, only because the function which used this
value needed that specific type. But, this allowed too large values
which either caused panics or might cause a DoS.
This does however might possible cause configurations which have larger
values, which are probably way too large anyway to be not loaded or use
their defaults. During the config loading, either via env or json, it
will show error logs regarding these items.
Also fixed an issue with protected actions via OTP, it increased the
attempt before it validated if the attempt limit was reached. This could
cause an issue when set to `1`, that way it was never allowed, and `3`
times would actually be `2`.
Also added several config validation checks which otherwise would have
prevent Vaultwarden to start, or login, so these will probably not cause
issues, else users would already have issues.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Show config errors on diagnostics page
With the previous commits it is now possible to add a warning if their
rare issues with loading of the config, either environment variables
which we were not able to parse correctly, or if loading the
`config.json` failed instead of just showing it wasn't used, while it
does exists, which might be confusing.
Signed-off-by: BlackDex <black.dex@gmail.com>
---------
Signed-off-by: BlackDex <black.dex@gmail.com>
Reuse `EMAIL_EXPIRATION_TIME` and `EMAIL_ATTEMPTS_LIMIT`, like the email
2FA codes, instead of hardcoded values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sending the code only for allowed emails made those responses take as
long as the SMTP round trip, which tells allowed and unknown emails
apart. Mail the code in the background so both answer the same way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mobile apps and the CLI don't know the Item Send type, and an unknown
Send type makes the whole Android sync fail. Other clients support it
since 2026.10.0. Leave Item Sends out of sync, list and get for those
clients, and only advertise the item sharing flag to clients that
support it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clients only decrypt (and rotate the keys of) Item Sends through the
SDK, gated by `pm-30110-sdk-sends-api`. Without it they silently drop
Item Sends and recipients can't open them, so support that flag and
only enable Item Sends when both flags are set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Support the Item Send type used by the Bitwarden clients (2026.10+) to
share a single vault item through a temporary link.
- New `Item` Send type; the item is encrypted client side into one
opaque blob, stored with its item id in clear
- Item Sends require email verification
- Only items the user owns, or organization items they can edit and see
the password of, can be shared; deleted, archived and SSH key items
can't. Access is checked on creation and when the item changes
- Accesses are counted like Text Sends, so one-time links work
- Opt-in through the `pm-34203-temporary-item-sharing` client feature
flag; while disabled, Item Sends can't be created and existing ones
are hidden from owners and recipients
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Allow Sends to be restricted to a list of email addresses. Recipients
request a one-time code by email through the `send_access` grant and
exchange it for an access token, matching the upstream flow.
- Store `auth_type` and `emails` on sends, with an additive migration
- One-time codes: single use, constant-time compared, invalidated after
5 wrong attempts, 60s resend cooldown, 5 minute validity
- Unlisted emails get the same response as listed ones and no email is
sent, so recipients can't be enumerated
- Access is gated on the stored auth type and fails closed when a Send
doesn't hold the emails or password its auth type requires
- Shared create/update handling of authType; it is inferred when
missing, like upstream, for clients that don't send it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This PR makes it easy for users to build a Debian container images which
is able to run on a riscv64 architecture using a Debian Container, or if
needed, you can extract the binary from this container, but the user
needs to be sure the libraries are available and supported if using
Docker is not an option.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Add revision_date to org policies
Org policy JSON responses never included a revisionDate field, unlike
every other synced entity. The official Bitwarden server always sets
it, and current official clients (recently migrated to the WASM SDK's
typed PolicyView) now assume it is always a valid date string. Its
absence deserializes to undefined client-side, which then throws
(RangeError: Invalid time value) when the client formats it, breaking
the whole policy sync pipeline and silently emptying the vault view in
the browser and desktop apps.
Adds a revision_date column (mirroring the existing pattern used by
Send), stamped on creation and bumped on every save.
* Fix migration correctness for org policy revision_date
Caught by review: SQLite rejects non-constant defaults in ALTER TABLE
ADD COLUMN, so the sqlite migration would fail outright on every
existing installation (vaultwarden's default backend) rather than the
postgresql setup this was developed against.
- sqlite/mysql: add the column with a constant placeholder default,
then backfill via UPDATE, since neither allows a volatile default
in ALTER TABLE ADD COLUMN (mysql does allow it, but a constant
keeps the three backends' migrations symmetric).
- mysql: use DATETIME instead of TIMESTAMP, matching this repo's
existing revision_date columns, and backfill with UTC_TIMESTAMP()
instead of a session-timezone-dependent CURRENT_TIMESTAMP.
- postgresql: backfill with `now() AT TIME ZONE 'utc'` instead of a
DEFAULT now(), since assigning timestamptz now() into this naive
TIMESTAMP column would otherwise cast through the server's TimeZone
GUC and store local wall-clock instead of UTC on non-UTC servers.
- mysql/postgresql: add a real down.sql (DROP COLUMN) instead of
leaving it empty.
* Move org policy revision_date migration after the latest one
---------
Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
* Allow email-only /api/two-factor/send-email-login for mobile clients
Mobile clients (iOS) may call /api/two-factor/send-email-login with only the user's email and without a MasterPasswordHash or an AuthRequest. Permit email-only requests so the server will send the email 2FA token in that flow.\n\nModified: src/api/core/two_factor/email.rs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(email-2fa): use device-identifier fallback when no password hash submitted
iOS clients call /api/two-factor/send-email-login with an email and
DeviceIdentifier but without masterPasswordHash or authRequestId after
receiving a 2FA-required response from the token endpoint. The previous
empty else-block allowed any caller to trigger a 2FA email for any
account knowing only the email address.
Replace the empty block with a device-identifier-based fallback:
- Look up the most-recently-active device via find_by_device_for_email2fa.
- Verify the device's associated user email matches the submitted email.
- Log (debug) when the fallback path is exercised for operator visibility.
- Reject with the original error when no device identifier is provided or
when the device maps to a different account.
Fixes#7568
* Check the pending 2FA login for this user and device
Look up the pending 2FA login for the user and device instead of the
latest one on the device, answer a failed check with the same error and
IP/username log detail as a wrong password, and escape the device id in
the logs.
---------
Co-authored-by: Arunabha-Mukhopadhyay <dkarunabha2006@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* 2FA using userVerificationToken
* Fix 2FA Yubikeys
* TwoFactor.find_by_user_and_type take enum parameter not i32
* log_user_event take enum parameter not i32
* Fix 2fa webauthn
* Prevent 2FA claims reuse
* 2fa webauthn return keys when deleting
* 2fa reset twofactor_remember
* Reject 2FA verification tokens issued for another provider
The Email and Duo claims only have optional fields, so a userVerificationToken minted for another
provider deserialized into them and was accepted. Each provider's claims now reject unknown fields,
with a test that every token only parses as its own provider.
Also fix the email 2FA login in the Playwright user setup, which used an undefined `mailBuffer`.
---------
Co-authored-by: Timshel <timshel@users.noreply.github.com>
Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
* Support new account recovery password payload
* Address account recovery review feedback
* Check the account recovery password before sending the recovery email
---------
Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>
Both notification hubs send a Ping every 15 seconds but ignore the
Pong, so the loop only ends on a Close frame, EOF or a socket error.
When a client disappears without the TCP connection being closed, for
example behind a reverse proxy or CDN that keeps the upstream
connection open, none of these ever happens. The connection, its file
descriptor and its map entry are then kept forever, until the process
runs out of file descriptors and stops accepting connections and
opening the database.
Keep track of when something was last received from the client, Pongs
included, and close the connection when nothing was received for 30
seconds. This is the default ClientTimeoutInterval of ASP.NET Core
SignalR, which the official server uses.
Fixes#7805
Co-authored-by: Cassian433 <262026629+Cassian433@users.noreply.github.com>
Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com>