post_bulk_access_collections authorized each requested collection via the
legacy Collection::is_manageable_by_user helper, which also accepts a member's
membership/group access_all. For a Custom member this diverged from the
single-collection edit endpoint (ManagerHeaders -> collection_edit_access),
which requires a real per-collection Manage grant and never treats a Custom
member's access_all as one. A flagless Custom member placed in an access_all
group could therefore rewrite collection user/group assignments in bulk while
the single-collection edit endpoint denied the exact same change.
Add auth::can_edit_collection - the collection_edit_access + can_manage_collection
pair the ManagerHeaders guard already uses - and call it per collection in the
bulk-access endpoint. Legacy Managers, Admins/Owners and Edit-any-collection
members are unaffected; only a Custom member's access_all shortcut is removed,
so bulk-access now enforces exactly what the single-collection edit endpoint does.
A Custom member holding only `edit_any_collection` (whose `access_all` mirror
makes every collection "manageable") could grant a per-collection `manage` row
to a group it belongs to -- via post_bulk_access_collections,
post_organization_collection_update, add_update_group or edit_member -- and then
delete that collection through CollectionDeleteHeaders'
`has_explicit_collection_manage_access` path, despite holding no
`delete_any_collection` permission. This defeated the intended edit != delete
separation (confirmed end-to-end: the delete returned 200 before this change).
Gate every caller-controlled collection-assignment `manage` write behind a new
`caller_may_grant_collection_manage()` check that mirrors the delete
authorization exactly: a caller may confer `manage` (which carries delete
authority) on a collection only if it could delete that collection itself --
Admin/Owner or `delete_any_collection` always; an exact legacy Manager via its
per-collection manage helper; any other Custom member only with a real persisted
`users_collections.manage` / `collections_groups.manage` grant.
`edit_any_collection`'s `access_all` mirror deliberately does not count.
The change is strictly subtractive: it can only ever downgrade a requested
`manage` to false, never grant it, so it opens no new access and leaves
Admins/Owners and genuinely delete-capable members unaffected. The create,
organization-import and invite paths are intentionally left alone (new-collection
creators must manage their own collection; non-owner invites can only create
plain Users, which cannot delete).
Adds a unit test for the collection-independent part of the gate.
Follow-up hardening on the custom-role work, found during a static review:
- Admin panel type change (apply_membership_type_change): when converting a
Custom member to the legacy Manager role, only preserve `access_all` if the
member actually held the full "manage all collections" grant (all three
collection flags). Previously an Edit-any-collection-only Custom member
(whose access_all is just the Edit mirror) became a Manager with a broad
access_all grant, silently escalating Edit-only into Create + Edit + Delete.
Mirrors the collection-permissions down-migration. Unit test updated.
- Bulk collection access (post_bulk_access_collections): drop the blanket
has_full_access() requirement and rely on the existing per-collection
is_manageable_by_user check (as the single-collection endpoint and the
pre-existing behavior do). Pure manage_users / manage_groups / manage_policies
Custom members hold no per-collection manage grant and are still rejected,
while a Manager/Custom member who manages some collections regains the ability
to bulk-edit exactly those collections.
- Group details (GET /organizations/<org>/groups/<id>/details): align the guard
with the list endpoint (/groups/details) to ManageUsersOrGroups, so a
manage_users member is not denied the single-group view of the same data it
can already read in bulk.
Addresses the confirmed High/Medium findings of the granular-collection-
permissions security audit:
- H-1: bind direct (non-sync) cipher access to a confirmed membership in the
cipher's organization, and harden the user/group collection access-flag
queries to require org consistency plus confirmed status. Revoked/invited
members can no longer reach ciphers via stale assignment rows.
- H-2: validate group ids against the org before mutating group membership in
edit_member, and reject cross-org group<->membership links in GroupUser::save
and Group::is_in_full_access_group.
- H-3: fully pre-validate collections/groups/users in bulk-access and collection
create before any mutation, and reject cross-org collection<->group links in
CollectionGroup::save.
- M-1: require Manage Users for the full member list and Manage Users/Groups for
group details (new ManageUsersOrGroupsHeaders guard).
- M-2: validate the whole bulk-access request before the destructive
delete/replace so an invalid element can't leave partial state behind.
- M-3: bounds-validate import collection relationships before writing and
propagate cipher-save errors instead of discarding them.
A Custom member with the manage_users permission reaches edit_member via
ManageUsersHeaders. Every mutated field there (access_all, the manage_* flags,
collection and group assignments) is gated behind an Admin/collection-management
check -- except the role type itself, which was written unconditionally aside
from the existing Admin/Owner-elevation guard.
Because collection "manage" rights are role-derived (`atype >= Manager` grants
manage on any collection the member can write), a manage_users caller with no
collection access could promote a plain User to Manager/Custom to grant them
collection administration (rename/delete/re-share), or demote to revoke it --
a separation-of-duties break between the user-management and data planes.
Add a may_change_member_type() guard so callers below Admin may only submit an
unchanged role (keeping the regular edit dialog working), and cover it with a
regression unit test.
* Misc updates and fixes
- Updated Rust to v1.96.1
- Updated all the crates
- Updated GitHub Actions
- Updated the web-vault to v2026.6.2
- Updated Alpine to v3.24
- Fixed several clippy lints
- The `send` UUID wrappers didn't need the special namespace anymore since an updated crate, so removed this extra mod.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update MSRV to v1.94.1
Signed-off-by: BlackDex <black.dex@gmail.com>
---------
Signed-off-by: BlackDex <black.dex@gmail.com>
A Custom member with only the manage_groups permission could revoke other
members' collection access via two endpoints that were missing the
collection-access check enforced elsewhere (put_group_members, edit_member):
- POST /organizations/<org>/groups/<group>/delete-user/<member>
(post_delete_group_member) removed a member from any group, including
collection-bearing ones.
- DELETE /organizations/<org>/groups/<group> and its bulk variant
(delete_group_impl / bulk_delete_groups) deleted collection-bearing groups
outright, revoking access for all their members.
Neither path can grant access, so confidentiality was never at risk, but both
let a manage_groups-only user tamper with other members' collection access,
contradicting the permission's invariant. Both now require Admin/Owner or full
collection access before touching a group that confers collection access (via
access_all or assigned collections).
send_invite computed access_all from the invite permissions but never
wrote the manage_users / manage_groups / manage_policies flags onto the
new membership, so a Custom member invited with a management permission
checked was created without it (the web-vault sends everything in a
single invite POST, with no follow-up edit). Mirror the handling in
edit_member and persist the flags at invite time.
Only Owners can invite Custom members, so the caller is always
authorized to grant these; the flags are gated on the Custom type and
forced false for every other type.
A Custom member holding manage_users + manage_groups (but without collection
access) could add a member -- including themselves via edit_member, or an
attacker-controlled invitee via send_invite -- to an access_all or
collection-bearing group, and thereby indirectly gain read access to those
collections' cipher contents. This bypassed the collection-access boundary
already enforced in put_group_members and add_update_group.
Gate both member-centric group-assignment paths on collection-management
rights via a shared `may_change_group_membership` predicate: callers who
cannot manage collections may only add/remove membership of groups that
confer no collection access, and collection-bearing memberships are left
untouched (neither granted nor revoked). Admins/Owners and full-access
callers keep the previous full-replace behaviour.
Add a regression unit test for the predicate.
Tighten two custom-role read paths that were broader than intended:
- get_group, get_group_details and get_group_members only required
ManagerHeadersLoose, exposing group metadata, collection mappings and
membership to any confirmed Manager/Custom member. Require
ManageGroupsHeaders (Admin/Owner or manage_groups) instead.
- list_policies returned the full policy configuration to any manage_*
member. Keep the endpoint reachable so the Admin Console still loads,
but return an empty list to callers without manage_policies.
Security fixes and hardening following a review of the custom-role
permissions feature:
- Gate the manage_* flags on the Custom membership type via new
Membership::has_manage_* helpers. Previously a stale flag (e.g. left
over after changing a member's type through the admin panel, which
does not go through edit_member) would keep granting management
permissions to a member of any type.
- Clear the manage_* flags in admin::update_membership_type when a
member is changed away from the Custom type.
- edit_member: reject any *change* to the manage_* flags by non-admin
callers. This keeps the previous protection against granting flags,
and additionally prevents a manage_users member from silently
stripping flags an Admin/Owner granted to fellow Custom members.
Unchanged flags still pass, so the regular edit dialog keeps working.
- list_policies: restrict the full policy list to Admins/Owners and
Custom members holding at least one management permission. The
previous loosening to ManagerHeadersLoose also exposed all policies
(including their configuration data) to plain Managers.
- Collection list metadata (get_org_collections{,_details}) is now only
readable with manage_users or manage_groups; manage_policies does not
need the collection list.
- find_confirmed_and_manage_all_by_org: include Custom members with
access_all, matching the pre-existing behaviour for Managers.
- Fix the partial_cmp_MembershipType unit test (Custom is no longer
converted to Manager) and document that Manager and Custom share an
access level in Ord while remaining distinct roles under PartialEq.
- Replace the remaining stale "HACK" comments and the raw "4" type
comparison in send_invite; drop unrelated .gitignore entries.
A Custom member with only manage_users could set the "manage all
collections" child permissions (createNewCollections / editAnyCollection /
deleteAnyCollection) on any member, including themselves, via
POST /organizations/<org_id>/users/<member_id>. edit_member wrote
member_to_edit.access_all unconditionally, so the resulting access_all=true
granted full access to every collection's contents - defeating the
"manage users without collection access" guarantee.
Gate the access_all change on caller_can_manage_collections (Admins/Owners
or full-access members), mirroring how put_group preserves a group's
access_all for callers without collection rights. For everyone else the
member's existing access_all is left untouched. The collection- and
group-assignment paths were already gated; this closes the remaining
direct path.
* Update to Rust 2024 Edition
Updated to the Rust 2024 Edition and added and fixed several lint checks.
This is a large change which, because of the extra lints, added some possible fixes for issues.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Reorder and merge imports
Signed-off-by: BlackDex <black.dex@gmail.com>
* Remove "db_run!" macro calls where possible
Signed-off-by: BlackDex <black.dex@gmail.com>
---------
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update crates and gha
Updated all the crates
Updated GitHub Actions
Signed-off-by: BlackDex <black.dex@gmail.com>
* Fix restoring revoked user
A new endpoint is used to restore a revoked user.
This commit fixes that.
Fixes#7224
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update datatables
Signed-off-by: BlackDex <black.dex@gmail.com>
---------
Signed-off-by: BlackDex <black.dex@gmail.com>
* Ensure SSO token is only usable on the same client
This commit adds an extra check via cookies to ensure the same browser/client is used to request and provide the SSO token.
Previously it would be able to provide a custom link which attackers could use to steal data.
While an attacker would still need the Master Password to be able to decrypt or execute specific actions, they were able to fetch encrypted data.
Solved with some help of Claude Code.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Check email-verified on SSO login/create
This commit prevents possible account takeover via SSO which doesn't check/validate or provide validated status of the email.
It was checked at other locations, but was skipped here.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Prevent data disclosure via SSO endpoints
This commit prevents some data disclosure and user enumeration by only returning the fake SSO identifier.
Since we do not check the identifier anywhere useful, returning the fake one is just fine.
During an invite to an org, that link contains the correct UUID and will be used for the master password requirements.
For anything else, server admins should set the `SSO_MASTER_PASSWORD_POLICY` env variable.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Adjust admin layout to fix issues when SSO is enabled
Signed-off-by: BlackDex <black.dex@gmail.com>
---------
Signed-off-by: BlackDex <black.dex@gmail.com>
Quote from the lint description:
"More flexibility, better memory optimization, and more idiomatic Rust code.
&Option<T> in a function signature breaks encapsulation because the caller must own T and move it into an Option to call with it. When returned, the owner must internally store it as Option<T> in order to return it. At a lower level, &Option<T> points to memory with the presence bit flag plus the T value, whereas Option<&T> is usually optimized to a single pointer, so it may be more optimal."
- Updated web-vault to v2026.3.1
Added a new endpoint needed for the admin console to work
- Updated all crates including webpki CVE fixes - Closes#7115
- Updated GHA
Signed-off-by: BlackDex <black.dex@gmail.com>
managers without the access_all flag should not be able to create
collections. the manage all collections permission actually consists of
three separate custom permissions that have not been implemented yet for
more fine-grain access control.
When a security-stamp gets reset/rotated we should also rotate all device refresh-tokens to invalidate them.
Else clients are still able to use old refresh tokens.
Signed-off-by: BlackDex <black.dex@gmail.com>
Fix an issue where it was possible for users who were not eligible to access all org ciphers to be able to download and extract the encrypted contents.
Only Managers with full access and Admins and Owners should be able to access this endpoint.
This change will block and prevent access for other users.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Optimizations and build speedup
With this commit I have changed several components to be more efficient.
This can be less llvm-lines generated or less `clone()` calls.
### Config
- Re-ordered the `make_config` macro to be more efficient
- Created a custom Deserializer for `ConfigBuilder` less code and more efficient
- Use struct's for the `prepare_json` function instead of generating a custom JSON object.
This generates less code and is more efficient.
- Updated the `get_support_string` function to handle the masking differently.
This generates less code and also was able to remove some sub-macro-calls
### Error
- Added an extra new call to prevent duplicate Strings in generated macro code.
This generated less llvm-lines and seems to be more efficient.
- Created a custom Serializer for `ApiError` and `CompactApiError`
This makes that struct smaller in size, so better for memory, but also less llvm-lines.
### General
- Removed `once_lock` and replace it all with Rust's std LazyLock
- Added and fixed some Clippy lints which reduced `clone()` calls for example.
- Updated build profiles for more efficiency
Also added a new profile specifically for CI, which should decrease the build check
- Updated several GitHub Workflows for better security and use the new `ci` build profile
- Updated to Rust v1.90.0 which uses a new linker `rust-lld` which should help in faster building
- Updated the Cargo.toml for all crates to better use the `workspace` variables
- Added a `typos` Workflow and Pre-Commit, which should help in detecting spell error's.
Also fixed a few found by it.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Fix release profile
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update typos and remove mimalloc check from pre-commit checks
Signed-off-by: BlackDex <black.dex@gmail.com>
* Misc fixes and updated typos
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update crates and workflows
Signed-off-by: BlackDex <black.dex@gmail.com>
* Fix formating and pre-commit
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update to Rust v1.91 and update crates
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update web-vault to v2025.10.1 and xx to v1.8.0
Signed-off-by: BlackDex <black.dex@gmail.com>
---------
Signed-off-by: BlackDex <black.dex@gmail.com>
* Use Diesels MultiConnections Derive
With this PR we remove almost all custom macro's to create the multiple database type code. This is now handled by Diesel it self.
This removed the need of the following functions/macro's:
- `db_object!`
- `::to_db`
- `.from_db()`
It is also possible to just use one schema instead of multiple per type.
Also done:
- Refactored the SQLite backup function
- Some formatting of queries so every call is one a separate line, this looks a bit better
- Declare `conn` as mut inside each `db_run!` instead of having to declare it as `mut` in functions or calls
- Added an `ACTIVE_DB_TYPE` static which holds the currently active database type
- Removed `diesel_logger` crate and use Diesel's `set_default_instrumentation()`
If you want debug queries you can now simply change the log level of `vaultwarden::db::query_logger`
- Use PostgreSQL v17 in the Alpine images to match the Debian Trixie version
- Optimized the Workflows since `diesel_logger` isn't needed anymore
And on the extra plus-side, this lowers the compile-time and binary size too.
Signed-off-by: BlackDex <black.dex@gmail.com>
* Adjust query_logger and some other small items
Signed-off-by: BlackDex <black.dex@gmail.com>
* Remove macro, replaced with an function
Signed-off-by: BlackDex <black.dex@gmail.com>
* Implement custom connection manager
Signed-off-by: BlackDex <black.dex@gmail.com>
* Updated some crates to keep up2date
Signed-off-by: BlackDex <black.dex@gmail.com>
* Small adjustment
Signed-off-by: BlackDex <black.dex@gmail.com>
* crate updates
Signed-off-by: BlackDex <black.dex@gmail.com>
* Update crates
Signed-off-by: BlackDex <black.dex@gmail.com>
---------
Signed-off-by: BlackDex <black.dex@gmail.com>
* Change OIDC dummy identifier
* Update src/sso.rs
Co-authored-by: Helmut K. C. Tessarek <tessarek@evermeet.cx>
* Use Org uuid as identifier
---------
Co-authored-by: Helmut K. C. Tessarek <tessarek@evermeet.cx>
Co-authored-by: Mathijs van Veluw <black.dex@gmail.com>
* Add SSO functionality using OpenID Connect
Co-authored-by: Pablo Ovelleiro Corral <mail@pablo.tools>
Co-authored-by: Stuart Heap <sheap13@gmail.com>
Co-authored-by: Alex Moore <skiepp@my-dockerfarm.cloud>
Co-authored-by: Brian Munro <brian.alexander.munro@gmail.com>
Co-authored-by: Jacques B. <timshel@github.com>
* Improvements and error handling
* Stop rolling device token
* Add playwright tests
* Activate PKCE by default
* Ensure result order when searching for sso_user
* add SSO_ALLOW_UNKNOWN_EMAIL_VERIFICATION
* Toggle SSO button in scss
* Base64 encode state before sending it to providers
* Prevent disabled User from SSO login
* Review fixes
* Remove unused UserOrganization.invited_by_email
* Split SsoUser::find_by_identifier_or_email
* api::Accounts::verify_password add the policy even if it's ignored
* Disable signups if SSO_ONLY is activated
* Add verifiedDate to organizations::get_org_domain_sso_details
* Review fixes
* Remove OrganizationId guard from get_master_password_policy
* Add wrapper type OIDCCode OIDCState OIDCIdentifier
* Membership::confirm_user_invitations fix and tests
* Allow set-password only if account is unitialized
* Review fixes
* Prevent accepting another user invitation
* Log password change event on SSO account creation
* Unify master password policy resolution
* Upgrade openidconnect to 4.0.0
* Revert "Remove unused UserOrganization.invited_by_email"
This reverts commit 548e19995e141314af98a10d170ea7371f02fab4.
* Process org enrollment in accounts::post_set_password
* Improve tests
* Pass the claim invited_by_email in case it was not in db
* Add Slack configuration hints
* Fix playwright tests
* Skip broken tests
* Add sso identifier in admin user panel
* Remove duplicate expiration check, add a log
* Augment mobile refresh_token validity
* Rauthy configuration hints
* Fix playwright tests
* Playwright upgrade and conf improvement
* Playwright tests improvements
* 2FA email and device creation change
* Fix and improve Playwright tests
* Minor improvements
* Fix enforceOnLogin org policies
* Run playwright sso tests against correct db
* PKCE should now work with Zitadel
* Playwright upgrade maildev to use MailBuffer.expect
* Upgrades playwright tests deps
* Check email_verified in id_token and user_info
* Add sso verified endpoint for v2025.6.0
* Fix playwright tests
* Create a separate sso_client
* Upgrade openidconnect to 4.0.1
* Server settings for login fields toggle
* Use only css for login fields
* Fix playwright test
* Review fix
* More review fix
* Perform same checks when setting kdf
---------
Co-authored-by: Felix Eckhofer <felix@eckhofer.com>
Co-authored-by: Pablo Ovelleiro Corral <mail@pablo.tools>
Co-authored-by: Stuart Heap <sheap13@gmail.com>
Co-authored-by: Alex Moore <skiepp@my-dockerfarm.cloud>
Co-authored-by: Brian Munro <brian.alexander.munro@gmail.com>
Co-authored-by: Jacques B. <timshel@github.com>
Co-authored-by: Timshel <timshel@480s>
Updated crates and made adjustments where needed.
Also removed a struct which wasn't used and the nightly compiler complained about it.
Used pinact to update GitHub Actions.
Validated GitHub Actions with zizmor.
Signed-off-by: BlackDex <black.dex@gmail.com>