Skip to content

Unconfirmed Owner Can Purge Entire Organization Vault

High
dani-garcia published GHSA-937x-3j8m-7w7p Apr 25, 2026

Package

cargo vaultwarden (Rust)

Affected versions

<=1.35.4

Patched versions

1.35.5

Description

Summary

An invited organization owner can purge the entire organization vault after reaching Accepted state, before any existing owner confirms them.

This is a server-side authorization bypass in the org-invite confirmation flow:

  1. accepting an invite moves the membership from Invited to Accepted
  2. confirmation is a separate step that later upgrades Accepted to Confirmed
  3. most privileged org routes correctly require Confirmed
  4. POST /api/ciphers/purge?organizationId=<org_id> does not; it only checks member.atype == Owner

As a result, a not-yet-confirmed owner can hard-delete every cipher and attachment in the organization.

Impact

This is a destructive authorization bypass on organization-wide ownership privileges.

A user who is intentionally still pending confirmation can:

  • purge the entire organization vault
  • hard-delete all organization ciphers
  • delete all attachments linked to those ciphers
  • cause immediate organization-wide data loss before the invite is confirmed

This breaks the security boundary that the Accepted -> Confirmed step is supposed to enforce.

Exploit Scenario

Assume:

  • attacker controls an email address invited as an Owner into Org A
  • attacker is not yet confirmed by an existing owner
  • attacker has a normal authenticated session on their own account

Steps:

  1. Attacker accepts the org invite.
    Result: membership becomes Accepted, not Confirmed.
  2. Before any owner confirms them, attacker calls:
POST /api/ciphers/purge?organizationId=<org_id>
Authorization: Bearer <attacker_access_token>
Content-Type: application/json

{
  "masterPasswordHash": "<attacker password hash>",
  "otp": null
}

Expected behavior:

  • request should be rejected because org-wide destructive owner actions should require a Confirmed owner

Actual behavior:

  • server looks up the membership
  • server only checks member.atype == Owner
  • server deletes every cipher in the organization
  • each cipher delete also removes attachments, collection links, favorites, and related records

Root Cause

Invite acceptance is intentionally separate from confirmation:

  • accept_org_invite() transitions Invited -> Accepted
  • _confirm_invite() transitions Accepted -> Confirmed

Privileged org guards reflect this design:

  • OwnerHeaders only accepts is_confirmed_and_owner()

But the org purge endpoint bypasses those guards entirely:

  • it uses plain Headers
  • it loads the membership with Membership::find_by_user_and_org()
  • it authorizes solely on member.atype == MembershipType::Owner
  • it never checks member.status == Confirmed

That means a pending owner receives full destructive owner power earlier than intended.

Code References

  • Invite acceptance sets Accepted:
    • src/api/core/mod.rs:263-279
  • New-account setup also auto-runs invite acceptance:
    • src/api/core/accounts.rs:375-387
  • Confirmation is a separate Accepted -> Confirmed step:
    • src/api/core/organizations.rs:1384-1397
  • Owner-only guards require confirmed status:
    • src/auth.rs:682-689
    • src/auth.rs:934-947
  • Purge endpoint checks only owner type:
    • src/api/core/ciphers.rs:1645-1665
  • Organization purge hard-deletes all ciphers:
    • src/db/models/cipher.rs:470-489

Why This Is Security-Relevant

This is not just an edge-case workflow mismatch.

The codebase clearly distinguishes:

  • Accepted: invite acknowledged, still pending trust/confirmation
  • Confirmed: fully activated org role

Using Headers plus member.atype == Owner collapses that distinction for one of the most destructive org-wide operations in the product.

If confirmation is meant to be a real trust gate, this endpoint bypasses it.

Fix Recommendation

Make org purge use the same confirmed-role model as the rest of privileged org APIs.

Concrete options:

  • change the route to require OwnerHeaders
  • or explicitly reject when member.status != MembershipStatus::Confirmed as i32
  • audit other org-wide mutation endpoints that use Membership::find_by_user_and_org() plus direct type checks instead of the confirmed-role request guards

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H

CVE ID

CVE-2026-43913

Weaknesses

Incorrect Authorization

The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. Learn more on MITRE.

Credits