[GH-ISSUE #7301] Upgrade Web Vault to 2026.6.1 (to fix a bug) #35623

Closed
opened 2026-07-13 20:28:08 -05:00 by GiteaMirror · 9 comments
Owner

Originally created by @martin-s-a on GitHub (Jun 4, 2026).
Original GitHub issue: https://github.com/dani-garcia/vaultwarden/issues/7301

Adding non-confirmed (Accepted) org member to a collection silently no-ops: PUT returns 200 OK but users_collections row is never written

Prerequisites

Vaultwarden Support String

Your environment (Generated via diagnostics page)

  • Vaultwarden version: v1.36.0
  • Web-vault version: v2026.4.1
  • OS/Arch: linux/x86_64
  • Running within a container: true (Base: Debian)
  • Database type: SQLite
  • Database version: 3.51.3
  • Uses config.json: false
  • Uses a reverse proxy: true
  • IP Header check: true (X-Real-IP)
  • Internet access: true
  • Internet access via a proxy: false
  • DNS Check: true
  • Browser/Server Time Check: true
  • Server/NTP Time Check: true
  • Domain Configuration Check: true
  • HTTPS Check: true
  • Websocket Check: true
  • HTTP Response Checks: false

Config & Details (Generated via diagnostics page)

Show Config & Details

Failed HTTP Checks:

API calls:
Header: 'x-frame-options' does not contain 'SAMEORIGIN'
Header: 'x-xss-protection' does not contain '0'
2FA Connector calls:
Header: 'x-xss-protection' does not contain '0'
Header: 'x-frame-options' is present while it should not

Config:

{
  "_duo_akey": null,
  "_enable_duo": true,
  "_enable_email_2fa": false,
  "_enable_smtp": true,
  "_enable_yubico": true,
  "_icon_service_csp": "",
  "_icon_service_url": "",
  "_ip_header_enabled": true,
  "_max_note_size": 10000,
  "_smtp_img_src": "***:",
  "admin_ratelimit_max_burst": 3,
  "admin_ratelimit_seconds": 300,
  "admin_session_lifetime": 20,
  "admin_token": "***",
  "allowed_connect_src": "",
  "allowed_iframe_ancestors": "",
  "attachments_folder": "data/attachments",
  "auth_request_purge_schedule": "30 * * * * *",
  "authenticator_disable_time_drift": false,
  "data_folder": "data",
  "database_conn_init": "",
  "database_idle_timeout": 600,
  "database_max_conns": 10,
  "database_min_conns": 2,
  "database_timeout": 30,
  "database_url": "***************",
  "db_connection_retries": 15,
  "disable_2fa_remember": false,
  "disable_admin_token": false,
  "disable_icon_download": false,
  "dns_prefer_ipv6": false,
  "domain": "*****://**********************",
  "domain_origin": "*****://**********************",
  "domain_path": "",
  "domain_set": true,
  "duo_context_purge_schedule": "30 * * * * *",
  "duo_host": null,
  "duo_ikey": null,
  "duo_skey": null,
  "duo_use_iframe": false,
  "email_2fa_auto_fallback": false,
  "email_2fa_enforce_on_verified_invite": false,
  "email_attempts_limit": 3,
  "email_change_allowed": true,
  "email_expiration_time": 600,
  "email_token_size": 6,
  "emergency_access_allowed": true,
  "emergency_notification_reminder_schedule": "0 3 * * * *",
  "emergency_request_timeout_schedule": "0 7 * * * *",
  "enable_db_wal": true,
  "enable_websocket": true,
  "enforce_single_org_with_reset_pw_policy": false,
  "event_cleanup_schedule": "0 10 0 * * *",
  "events_days_retain": null,
  "experimental_client_feature_flags": "",
  "extended_logging": true,
  "helo_name": null,
  "hibp_api_key": null,
  "http_request_block_non_global_ips": true,
  "http_request_block_regex": null,
  "icon_blacklist_non_global_ips": true,
  "icon_blacklist_regex": null,
  "icon_cache_folder": "data/icon_cache",
  "icon_cache_negttl": 259200,
  "icon_cache_ttl": 2592000,
  "icon_download_timeout": 10,
  "icon_redirect_code": 302,
  "icon_service": "internal",
  "incomplete_2fa_schedule": "30 * * * * *",
  "incomplete_2fa_time_limit": 3,
  "increase_note_size_limit": false,
  "invitation_expiration_hours": 120,
  "invitation_org_name": "Vaultwarden",
  "invitations_allowed": true,
  "ip_header": "X-Real-IP",
  "job_poll_interval_ms": 30000,
  "log_file": null,
  "log_level": "info",
  "log_timestamp_format": "%Y-%m-%d %H:%M:%S.%3f",
  "login_ratelimit_max_burst": 10,
  "login_ratelimit_seconds": 60,
  "org_attachment_limit": null,
  "org_creation_users": "",
  "org_events_enabled": false,
  "org_groups_enabled": false,
  "password_hints_allowed": true,
  "password_iterations": 600000,
  "purge_incomplete_sso_auth": "0 20 0 * * *",
  "push_enabled": false,
  "push_identity_uri": "https://identity.bitwarden.com",
  "push_installation_id": "***",
  "push_installation_key": "***",
  "push_relay_uri": "https://push.bitwarden.com",
  "reload_templates": false,
  "require_device_email": false,
  "rsa_key_filename": "data/rsa_key",
  "send_purge_schedule": "0 5 * * * *",
  "sendmail_command": null,
  "sends_allowed": true,
  "sends_folder": "data/sends",
  "show_password_hint": false,
  "signups_allowed": false,
  "signups_domains_whitelist": "",
  "signups_verify": false,
  "signups_verify_resend_limit": 6,
  "signups_verify_resend_time": 3600,
  "smtp_accept_invalid_certs": false,
  "smtp_accept_invalid_hostnames": false,
  "smtp_auth_mechanism": null,
  "smtp_debug": false,
  "smtp_embed_images": true,
  "smtp_explicit_tls": null,
  "smtp_from": "",
  "smtp_from_name": "***********",
  "smtp_host": null,
  "smtp_password": null,
  "smtp_port": 587,
  "smtp_security": "starttls",
  "smtp_ssl": null,
  "smtp_timeout": 15,
  "smtp_username": null,
  "sso_allow_unknown_email_verification": false,
  "sso_audience_trusted": null,
  "sso_auth_only_not_session": false,
  "sso_authority": "",
  "sso_authorize_extra_params": "",
  "sso_callback_path": "*****://***************************************************",
  "sso_client_cache_expiration": 0,
  "sso_client_id": "",
  "sso_client_secret": "***",
  "sso_debug_tokens": false,
  "sso_enabled": false,
  "sso_master_password_policy": null,
  "sso_only": false,
  "sso_pkce": true,
  "sso_scopes": "email profile",
  "sso_signups_match_email": true,
  "templates_folder": "data/templates",
  "tmp_folder": "data/tmp",
  "trash_auto_delete_days": null,
  "trash_purge_schedule": "0 5 0 * * *",
  "use_sendmail": false,
  "use_syslog": false,
  "user_attachment_limit": null,
  "user_send_limit": null,
  "web_vault_enabled": true,
  "web_vault_folder": "web-vault/",
  "yubico_client_id": null,
  "yubico_secret_key": null,
  "yubico_server": null
}

Vaultwarden Build Version

v1.36.0

Deployment method

Official Container Image

Custom deployment method

Compose stack behind Traefik, SQLite backend (./vw-data:/data), no custom build flags.

Reverse Proxy

Traefik v3.7.1

Host/Server Operating System

Linux

Operating System Version

Debian 12 (Bookworm)

Clients

Web Vault

Client Version

Web Vault 2026.4.1 (bundled with Vaultwarden v1.36.0)

Steps To Reproduce

  1. As Owner, invite a new user to the organization. The user accepts the invitation by clicking the email link, but the Owner has not yet confirmed them (membership status 1 = Accepted).
  2. As Owner, open an existing collection → Edit access
  3. Set permission to Manage collection and select the Accepted user from the "Select members" dropdown. The user appears in the list with the chosen permission alongside other Confirmed users.
  4. Click Save. The toast notification reports Collection <name> edited successfully.
  5. Close the dialog and re-open Edit access on the same collection.

Expected Result

Either:

  • The change persists and the Accepted user remains in the access list (and gains the assigned permission once they are Confirmed), or
  • The save call fails with an explicit error explaining that the target user must be Confirmed before they can be granted collection access.

In any case the UI feedback should match the actual server-side state.

Actual Result

  • The save call returns 200 OK and the success toast is shown.
  • On re-open of Edit access, the Accepted user is no longer present in the access list.
  • No record is ever written to the users_collections table for that user/collection pair.
  • There is no warning or error surfaced to the operator, who reasonably assumes the change was applied.

Direct inspection of the SQLite DB confirms: after the apparently-successful save the users_collections table contains zero rows for the target user, even though the membership row exists in users_organizations (with status = 1).

Logs

[INFO] PUT  /api/organizations/<org_id>/collections/<col_id>
[INFO] (put_organization_collection_update) PUT /api/organizations/<org_id>/collections/<col_id> => 200 OK
[INFO] GET  /api/organizations/<org_id>/collections/details
[INFO] (get_org_collections_details) GET /api/organizations/<org_id>/collections/details => 200 OK

(No WARN/ERROR entries are emitted on the save.)

Screenshots or Videos

No response

Additional Context

DB state right after the apparently-successful save (SQLite, schema of v1.36.0):

sqlite> SELECT u.email, uo.atype, uo.status
        FROM users_organizations uo JOIN users u ON u.uuid = uo.user_uuid;
owner@example.com    | 0 | 2   -- Owner, Confirmed
pending@example.com  | 0 | 1   -- target user, Accepted (not yet Confirmed)

sqlite> SELECT * FROM users_collections;
-- 0 rows

In all fields above, I have anonimized the data (emails, userames, etc) for privacy reasons.

Originally created by @martin-s-a on GitHub (Jun 4, 2026). Original GitHub issue: https://github.com/dani-garcia/vaultwarden/issues/7301 # Adding non-confirmed (Accepted) org member to a collection silently no-ops: PUT returns 200 OK but users_collections row is never written ### Prerequisites - [x] I have searched the existing **Closed _AND_ Open** [Issues](https://github.com/dani-garcia/vaultwarden/issues?q=is%3Aissue%20) **_AND_** [Discussions](https://github.com/dani-garcia/vaultwarden/discussions?discussions_q=) - [x] I have searched and read the [documentation](https://github.com/dani-garcia/vaultwarden/wiki/) ### Vaultwarden Support String ### Your environment (Generated via diagnostics page) * Vaultwarden version: v1.36.0 * Web-vault version: v2026.4.1 * OS/Arch: linux/x86_64 * Running within a container: true (Base: Debian) * Database type: SQLite * Database version: 3.51.3 * Uses config.json: false * Uses a reverse proxy: true * IP Header check: true (X-Real-IP) * Internet access: true * Internet access via a proxy: false * DNS Check: true * Browser/Server Time Check: true * Server/NTP Time Check: true * Domain Configuration Check: true * HTTPS Check: true * Websocket Check: true * HTTP Response Checks: false ### Config & Details (Generated via diagnostics page) <details><summary>Show Config & Details</summary> **Failed HTTP Checks:** ```yaml API calls: Header: 'x-frame-options' does not contain 'SAMEORIGIN' Header: 'x-xss-protection' does not contain '0' 2FA Connector calls: Header: 'x-xss-protection' does not contain '0' Header: 'x-frame-options' is present while it should not ``` **Config:** ```json { "_duo_akey": null, "_enable_duo": true, "_enable_email_2fa": false, "_enable_smtp": true, "_enable_yubico": true, "_icon_service_csp": "", "_icon_service_url": "", "_ip_header_enabled": true, "_max_note_size": 10000, "_smtp_img_src": "***:", "admin_ratelimit_max_burst": 3, "admin_ratelimit_seconds": 300, "admin_session_lifetime": 20, "admin_token": "***", "allowed_connect_src": "", "allowed_iframe_ancestors": "", "attachments_folder": "data/attachments", "auth_request_purge_schedule": "30 * * * * *", "authenticator_disable_time_drift": false, "data_folder": "data", "database_conn_init": "", "database_idle_timeout": 600, "database_max_conns": 10, "database_min_conns": 2, "database_timeout": 30, "database_url": "***************", "db_connection_retries": 15, "disable_2fa_remember": false, "disable_admin_token": false, "disable_icon_download": false, "dns_prefer_ipv6": false, "domain": "*****://**********************", "domain_origin": "*****://**********************", "domain_path": "", "domain_set": true, "duo_context_purge_schedule": "30 * * * * *", "duo_host": null, "duo_ikey": null, "duo_skey": null, "duo_use_iframe": false, "email_2fa_auto_fallback": false, "email_2fa_enforce_on_verified_invite": false, "email_attempts_limit": 3, "email_change_allowed": true, "email_expiration_time": 600, "email_token_size": 6, "emergency_access_allowed": true, "emergency_notification_reminder_schedule": "0 3 * * * *", "emergency_request_timeout_schedule": "0 7 * * * *", "enable_db_wal": true, "enable_websocket": true, "enforce_single_org_with_reset_pw_policy": false, "event_cleanup_schedule": "0 10 0 * * *", "events_days_retain": null, "experimental_client_feature_flags": "", "extended_logging": true, "helo_name": null, "hibp_api_key": null, "http_request_block_non_global_ips": true, "http_request_block_regex": null, "icon_blacklist_non_global_ips": true, "icon_blacklist_regex": null, "icon_cache_folder": "data/icon_cache", "icon_cache_negttl": 259200, "icon_cache_ttl": 2592000, "icon_download_timeout": 10, "icon_redirect_code": 302, "icon_service": "internal", "incomplete_2fa_schedule": "30 * * * * *", "incomplete_2fa_time_limit": 3, "increase_note_size_limit": false, "invitation_expiration_hours": 120, "invitation_org_name": "Vaultwarden", "invitations_allowed": true, "ip_header": "X-Real-IP", "job_poll_interval_ms": 30000, "log_file": null, "log_level": "info", "log_timestamp_format": "%Y-%m-%d %H:%M:%S.%3f", "login_ratelimit_max_burst": 10, "login_ratelimit_seconds": 60, "org_attachment_limit": null, "org_creation_users": "", "org_events_enabled": false, "org_groups_enabled": false, "password_hints_allowed": true, "password_iterations": 600000, "purge_incomplete_sso_auth": "0 20 0 * * *", "push_enabled": false, "push_identity_uri": "https://identity.bitwarden.com", "push_installation_id": "***", "push_installation_key": "***", "push_relay_uri": "https://push.bitwarden.com", "reload_templates": false, "require_device_email": false, "rsa_key_filename": "data/rsa_key", "send_purge_schedule": "0 5 * * * *", "sendmail_command": null, "sends_allowed": true, "sends_folder": "data/sends", "show_password_hint": false, "signups_allowed": false, "signups_domains_whitelist": "", "signups_verify": false, "signups_verify_resend_limit": 6, "signups_verify_resend_time": 3600, "smtp_accept_invalid_certs": false, "smtp_accept_invalid_hostnames": false, "smtp_auth_mechanism": null, "smtp_debug": false, "smtp_embed_images": true, "smtp_explicit_tls": null, "smtp_from": "", "smtp_from_name": "***********", "smtp_host": null, "smtp_password": null, "smtp_port": 587, "smtp_security": "starttls", "smtp_ssl": null, "smtp_timeout": 15, "smtp_username": null, "sso_allow_unknown_email_verification": false, "sso_audience_trusted": null, "sso_auth_only_not_session": false, "sso_authority": "", "sso_authorize_extra_params": "", "sso_callback_path": "*****://***************************************************", "sso_client_cache_expiration": 0, "sso_client_id": "", "sso_client_secret": "***", "sso_debug_tokens": false, "sso_enabled": false, "sso_master_password_policy": null, "sso_only": false, "sso_pkce": true, "sso_scopes": "email profile", "sso_signups_match_email": true, "templates_folder": "data/templates", "tmp_folder": "data/tmp", "trash_auto_delete_days": null, "trash_purge_schedule": "0 5 0 * * *", "use_sendmail": false, "use_syslog": false, "user_attachment_limit": null, "user_send_limit": null, "web_vault_enabled": true, "web_vault_folder": "web-vault/", "yubico_client_id": null, "yubico_secret_key": null, "yubico_server": null } ``` </details> ### Vaultwarden Build Version v1.36.0 ### Deployment method Official Container Image ### Custom deployment method Compose stack behind Traefik, SQLite backend (`./vw-data:/data`), no custom build flags. ### Reverse Proxy Traefik v3.7.1 ### Host/Server Operating System Linux ### Operating System Version Debian 12 (Bookworm) ### Clients Web Vault ### Client Version Web Vault 2026.4.1 (bundled with Vaultwarden v1.36.0) ### Steps To Reproduce 1. As Owner, invite a new user to the organization. The user accepts the invitation by clicking the email link, but the Owner has **not** yet confirmed them (membership status `1 = Accepted`). 2. As Owner, open an existing collection → **Edit access** 3. Set permission to **Manage collection** and select the Accepted user from the "Select members" dropdown. The user appears in the list with the chosen permission alongside other Confirmed users. 4. Click **Save**. The toast notification reports `Collection <name> edited` successfully. 5. Close the dialog and re-open **Edit access** on the same collection. ### Expected Result Either: - The change persists and the Accepted user remains in the access list (and gains the assigned permission once they are Confirmed), **or** - The save call fails with an explicit error explaining that the target user must be Confirmed before they can be granted collection access. In any case the UI feedback should match the actual server-side state. ### Actual Result - The save call returns `200 OK` and the success toast is shown. - On re-open of **Edit access**, the Accepted user is no longer present in the access list. - No record is ever written to the `users_collections` table for that user/collection pair. - There is no warning or error surfaced to the operator, who reasonably assumes the change was applied. Direct inspection of the SQLite DB confirms: after the apparently-successful save the `users_collections` table contains zero rows for the target user, even though the membership row exists in `users_organizations` (with `status = 1`). ### Logs ```text [INFO] PUT /api/organizations/<org_id>/collections/<col_id> [INFO] (put_organization_collection_update) PUT /api/organizations/<org_id>/collections/<col_id> => 200 OK [INFO] GET /api/organizations/<org_id>/collections/details [INFO] (get_org_collections_details) GET /api/organizations/<org_id>/collections/details => 200 OK (No WARN/ERROR entries are emitted on the save.) ``` ### Screenshots or Videos _No response_ ### Additional Context DB state right after the apparently-successful save (SQLite, schema of v1.36.0): ``` sqlite> SELECT u.email, uo.atype, uo.status FROM users_organizations uo JOIN users u ON u.uuid = uo.user_uuid; owner@example.com | 0 | 2 -- Owner, Confirmed pending@example.com | 0 | 1 -- target user, Accepted (not yet Confirmed) sqlite> SELECT * FROM users_collections; -- 0 rows ``` In all fields above, I have anonimized the data (emails, userames, etc) for privacy reasons.
GiteaMirror added the bug label 2026-07-13 20:28:08 -05:00
Author
Owner

@maximilize commented on GitHub (Jun 22, 2026):

The server has no status gate on this path — if the Accepted member reached the handler, the row would be written. The assignment loop in post_organization_collection_update (src/api/core/organizations.rs:689-700) resolves each entry via Membership::find_by_uuid_and_org (no status filter — src/db/models/organization.rs:829-838) and calls CollectionUser::save, an unconditional upsert (src/db/models/collection.rs:674-735). The only skip is if member.access_all { continue; } (organizations.rs:694).

The re-open doesn't hide it either: get_org_collections_details lists members via a join with no status filter (src/db/models/collection.rs:647-672). So "0 rows" means the row is genuinely never created — most likely the bundled web-vault builds the access PUT from Confirmed members only and drops the Accepted user before sending, so the server gets a valid 200 with that member simply absent from data.users (then delete_all_by_collection + re-insert leaves nothing for them).

If maintainers want the server to be the backstop, the minimal change is to reject non-confirmed members explicitly in the loop (organizations.rs:689-700, and the bulk path at 609-621) instead of relying on the client — turning the silent 200 into the explicit error your "Expected Result" asks for.

Could you capture the raw PUT body (devtools → Network → PUT /api/organizations/<org>/collections/<col>) and confirm whether the Accepted user's membership id is in the users array? That pins it to client vs. server.

<!-- gh-comment-id:4766418145 --> @maximilize commented on GitHub (Jun 22, 2026): The server has **no status gate** on this path — if the Accepted member reached the handler, the row *would* be written. The assignment loop in `post_organization_collection_update` (`src/api/core/organizations.rs:689-700`) resolves each entry via `Membership::find_by_uuid_and_org` (no status filter — `src/db/models/organization.rs:829-838`) and calls `CollectionUser::save`, an unconditional upsert (`src/db/models/collection.rs:674-735`). The only skip is `if member.access_all { continue; }` (organizations.rs:694). The re-open doesn't hide it either: `get_org_collections_details` lists members via a join with no status filter (`src/db/models/collection.rs:647-672`). So "0 rows" means the row is genuinely **never created** — most likely the bundled web-vault builds the access PUT from *Confirmed* members only and drops the Accepted user before sending, so the server gets a valid 200 with that member simply absent from `data.users` (then `delete_all_by_collection` + re-insert leaves nothing for them). If maintainers want the server to be the backstop, the minimal change is to reject non-confirmed members explicitly in the loop (organizations.rs:689-700, and the bulk path at 609-621) instead of relying on the client — turning the silent 200 into the explicit error your "Expected Result" asks for. Could you capture the raw PUT body (devtools → Network → `PUT /api/organizations/<org>/collections/<col>`) and confirm whether the Accepted user's membership `id` is in the `users` array? That pins it to client vs. server.
Author
Owner

@martin-s-a commented on GitHub (Jun 22, 2026):

Could you capture the raw PUT body (devtools → Network → PUT /api/organizations/<org>/collections/<col>) and confirm whether the Accepted user's membership id is in the users array? That pins it to client vs. server.

To replicate it, I followed this process:

  • Start with a vault with already two confirmed admin users onboarded, and two collections shared between both of them.
  • Invite a new user (as role admin too)
  • Create new account with the invited user (but then not confirm such user from the inviter's account)
  • From the original admin, provide access to the new user to a collection, with acces type "Manage collection" (full access, just like the other two admins), and click "Save"
    The originated PUT request payload was:
{
  "groups": [],
  "users": [
    {
      "id": "cbec232c-f4f8-4e3d-975c-0aa24440d48a",
      "readOnly": false,
      "hidePasswords": false,
      "manage": true
    },
    {
      "id": "6e8a5a8f-949f-48c1-abc9-63258c2a4bde",
      "readOnly": false,
      "hidePasswords": false,
      "manage": true
    },
    {
      "id": "226e8363-e9e2-474e-bd31-eb89ee712e44",
      "readOnly": false,
      "hidePasswords": false,
      "manage": true
    }
  ],
  "externalId": null,
  "name": "2.uQ3TvwR67v+jGNBS/eES7w==|Ucf+uwf3WOaCd93CjltiAGnOEHdxPEdGf1Foj4NrdNM=|eguhZ2aCh1ZSGw5UlvI9IEMH4d/jqXTYF3EZ+HjRZ/c="
}

and the response was a 200 OK with the body:

{
    "externalId": null,
    "hidePasswords": false,
    "id": "6755fc59-187d-4988-a5e2-14a73fcbd042",
    "manage": true,
    "name": "2.uQ3TvwR67v+jGNBS/eES7w==|Ucf+uwf3WOaCd93CjltiAGnOEHdxPEdGf1Foj4NrdNM=|eguhZ2aCh1ZSGw5UlvI9IEMH4d/jqXTYF3EZ+HjRZ/c=",
    "object": "collectionDetails",
    "organizationId": "294adbf6-50ff-468f-96ab-d83dce732d78",
    "readOnly": false
}

The I went to the DB, and inspected the table users_collections (for collection with uuid 6755fc59-187d-4988-a5e2-14a73fcbd042) and got:

membership_id status access_all
226e8363-e9e2-474e-bd31-eb89ee712e44 1 1
6e8a5a8f-949f-48c1-abc9-63258c2a4bde 2 1
cbec232c-f4f8-4e3d-975c-0aa24440d48a 2 1

Where I believe that 1Accepted & 2Confimed.
Of course, when opening the access control menu for the collection again, the new user was not there, only the two previous admins.

Looking back at this, maybe the key is the role being applied to the user, as I understand that admins must always get access_all: true.
No users_collections row exists for any of the three users — including the two already-Confirmed admins. This is consistent with the if member.access_all { continue; } skip in organizations.rs:694 applying to all of them regardless of status.

So the "silent swallow" we observed originally was not status-specific: it happens for all org admins (Accepted or Confirmed) because access_all = true makes the insert a no-op by design. The web vault shows a success toast either way, which is the actual UX issue — the admin had no way to know the operation was a no-op. And then, when I click on Edit Access again, there must be some filter that only includes confirmed admins. Also, as the new user has not been confirmed, won't have access to the collection.

TLDR: Looks like a client-side issue indeed.

<!-- gh-comment-id:4772829482 --> @martin-s-a commented on GitHub (Jun 22, 2026): > Could you capture the raw PUT body (devtools → Network → `PUT /api/organizations/<org>/collections/<col>`) and confirm whether the Accepted user's membership `id` is in the `users` array? That pins it to client vs. server. To replicate it, I followed this process: - Start with a vault with already two confirmed admin users onboarded, and two collections shared between both of them. - Invite a new user (as role admin too) - Create new account with the invited user (but then not confirm such user from the inviter's account) - From the original admin, provide access to the new user to a collection, with acces type "Manage collection" (full access, just like the other two admins), and click "Save" The originated `PUT` request payload was: ```json { "groups": [], "users": [ { "id": "cbec232c-f4f8-4e3d-975c-0aa24440d48a", "readOnly": false, "hidePasswords": false, "manage": true }, { "id": "6e8a5a8f-949f-48c1-abc9-63258c2a4bde", "readOnly": false, "hidePasswords": false, "manage": true }, { "id": "226e8363-e9e2-474e-bd31-eb89ee712e44", "readOnly": false, "hidePasswords": false, "manage": true } ], "externalId": null, "name": "2.uQ3TvwR67v+jGNBS/eES7w==|Ucf+uwf3WOaCd93CjltiAGnOEHdxPEdGf1Foj4NrdNM=|eguhZ2aCh1ZSGw5UlvI9IEMH4d/jqXTYF3EZ+HjRZ/c=" } ``` and the response was a `200 OK` with the body: ```json { "externalId": null, "hidePasswords": false, "id": "6755fc59-187d-4988-a5e2-14a73fcbd042", "manage": true, "name": "2.uQ3TvwR67v+jGNBS/eES7w==|Ucf+uwf3WOaCd93CjltiAGnOEHdxPEdGf1Foj4NrdNM=|eguhZ2aCh1ZSGw5UlvI9IEMH4d/jqXTYF3EZ+HjRZ/c=", "object": "collectionDetails", "organizationId": "294adbf6-50ff-468f-96ab-d83dce732d78", "readOnly": false } ``` The I went to the DB, and inspected the table `users_collections` (for collection with uuid `6755fc59-187d-4988-a5e2-14a73fcbd042`) and got: | membership_id | status | access_all | | :---------------- | :------: | ----: | | 226e8363-e9e2-474e-bd31-eb89ee712e44 | 1 | 1 | | 6e8a5a8f-949f-48c1-abc9-63258c2a4bde | 2 | 1 | | cbec232c-f4f8-4e3d-975c-0aa24440d48a | 2 | 1 | Where I believe that `1` &#8594; `Accepted` & `2` &#8594; `Confimed`. Of course, when opening the access control menu for the collection again, the new user was not there, only the two previous admins. Looking back at this, maybe the key is the role being applied to the user, as I understand that admins must always get `access_all: true`. No `users_collections` row exists for any of the three users — including the two already-Confirmed admins. This is consistent with the `if member.access_all { continue; }` skip in `organizations.rs:694` applying to all of them regardless of status. So the "silent swallow" we observed originally was not status-specific: it happens for all org admins (Accepted or Confirmed) because `access_all = true` makes the insert a no-op by design. The web vault shows a success toast either way, which is the actual UX issue — the admin had no way to know the operation was a no-op. And then, when I click on Edit Access again, there must be some filter that only includes confirmed admins. Also, as the new user has not been confirmed, won't have access to the collection. TLDR: Looks like a client-side issue indeed.
Author
Owner

@maximilize commented on GitHub (Jun 22, 2026):

Agreed it's client-side — and I think this can be closed as a web-vault (not server) issue. Two concrete points:

1. The skip is required, not a bug. All three users are admins, so they carry access_all = true (organizations.rs:1059 / :1546), and the loop intentionally skips them (if member.access_all { continue; }, organizations.rs:694) because access_all already grants every collection — a per-collection row would be redundant. Note your PUT also included the two existing confirmed admins: the web-vault sends the full admin list on every collection-access save. So the server must silently skip access_all members — turning this into an explicit error (as the original "Expected Result" asked) would break every normal save on any collection shared with admins.

2. The only real defect is the success toast in the bundled web-vault (Bitwarden client v2026.4.1): it lets you "assign" a collection to an access_all admin and reports success even though it's a structural no-op, then filters them back out on reload. vaultwarden vendors that client unchanged from upstream bitwarden/clients, so this almost certainly reproduces identically on bitwarden.com — it isn't fixable in this repo. (I searched bitwarden/clients and couldn't find an existing issue for this exact symptom.)

Suggest closing as an upstream web-vault issue (or relabeling); if the toast should be fixed, it belongs at bitwarden/clients. Thanks for the clean repro — the DB dump made it unambiguous.

<!-- gh-comment-id:4773131814 --> @maximilize commented on GitHub (Jun 22, 2026): Agreed it's client-side — and I think this can be closed as a web-vault (not server) issue. Two concrete points: **1. The skip is required, not a bug.** All three users are admins, so they carry `access_all = true` (`organizations.rs:1059` / `:1546`), and the loop intentionally skips them (`if member.access_all { continue; }`, `organizations.rs:694`) because `access_all` already grants every collection — a per-collection row would be redundant. Note your PUT also included the two *existing* confirmed admins: the web-vault sends the full admin list on every collection-access save. So the server **must** silently skip access_all members — turning this into an explicit error (as the original "Expected Result" asked) would break every normal save on any collection shared with admins. **2. The only real defect is the success toast** in the bundled web-vault (Bitwarden client `v2026.4.1`): it lets you "assign" a collection to an access_all admin and reports success even though it's a structural no-op, then filters them back out on reload. vaultwarden vendors that client unchanged from upstream `bitwarden/clients`, so this almost certainly reproduces identically on bitwarden.com — it isn't fixable in this repo. (I searched `bitwarden/clients` and couldn't find an existing issue for this exact symptom.) Suggest closing as an upstream web-vault issue (or relabeling); if the toast should be fixed, it belongs at `bitwarden/clients`. Thanks for the clean repro — the DB dump made it unambiguous.
Author
Owner

@martin-s-a commented on GitHub (Jun 23, 2026):

Closing as this issue does not belong here — opened issue in bitwarden/clients repository.

Thanks @maximilize for your help with the investigation.

<!-- gh-comment-id:4777183586 --> @martin-s-a commented on GitHub (Jun 23, 2026): Closing as this issue does not belong here — [opened issue](https://github.com/bitwarden/clients/issues/21435) in bitwarden/clients repository. Thanks @maximilize for your help with the investigation.
Author
Owner

@martin-s-a commented on GitHub (Jun 24, 2026):

I am reopening because, after investigating the issue within the latest 2026.6.1 client using bitwarden.com as a backend, the issue is not present anymore.

So, the solution to the issue here would be to upgrade web vault to the latest version.

<!-- gh-comment-id:4787381827 --> @martin-s-a commented on GitHub (Jun 24, 2026): I am reopening because, after investigating the issue within the latest `2026.6.1` client using bitwarden.com as a backend, the issue is not present anymore. So, the solution to the issue here would be to upgrade web vault to the latest version.
Author
Owner

@stefan0xC commented on GitHub (Jun 25, 2026):

There are a couple of PRs open that would need to be merged before we can update the web-vault. Also I'm unsure if such a bug is automatically addressed by updating the web-vault just because it's not an issue in Bitwarden. If this is a bug in Vaultwarden then updating the web-vault won't fix this because it still might be a bug in our backend. Have not tried to replicate it but it could well be that we are not returning the collections associated with unconfirmed users.

<!-- gh-comment-id:4801906401 --> @stefan0xC commented on GitHub (Jun 25, 2026): There are a couple of PRs open that would need to be merged before we can update the web-vault. Also I'm unsure if such a bug is automatically addressed by updating the web-vault just because it's not an issue in Bitwarden. If this is a bug in Vaultwarden then updating the web-vault won't fix this because it still might be a bug in our backend. Have not tried to replicate it but it could well be that we are not returning the collections associated with unconfirmed users.
Author
Owner

@martin-s-a commented on GitHub (Jun 25, 2026):

... it still might be a bug in our backend. Have not tried to replicate it but it could well be that we are not returning the collections associated with unconfirmed users.

I just replicated it and when clicking "Edit access" on a collection, two separate GET requests are made:
https://myvaultdomain.com/api/organizations/294adbf6-50ff-468f-96ab-d83dce732d78/users/mini-details
which returns this json object:

{
    "continuationToken": null,
    "data": [
        {
            "email": "admin1@mydomain.com",
            "id": "6e8a5a8f-949f-48c1-abc9-63258c2a4bde",
            "name": "Admin 1",
            "object": "organizationUserUserMiniDetails",
            "status": 2,
            "type": 0,
            "userId": "5ed821eb-1728-4bb5-8721-d9d37c7868ea"
        },
        {
            "email": "admin2@mydomain.com",
            "id": "cbec232c-f4f8-4e3d-975c-0aa24440d48a",
            "name": "Admin 2",
            "object": "organizationUserUserMiniDetails",
            "status": 2,
            "type": 0,
            "userId": "9e1afe79-3eac-4b9e-9136-da5779d11fe0"
        },
        {
            "email": "newadmin@mydomain.com",
            "id": "76f95979-19c5-4e56-a16c-804df883f181",
            "name": "New unconfirmed admin",
            "object": "organizationUserUserMiniDetails",
            "status": 1,
            "type": 1,
            "userId": "62d5213d-32fb-4e26-8642-8b552a35eb58"
        }
    ],
    "object": "list"
}

and also https://myvaultdomain.com/api/organizations/294adbf6-50ff-468f-96ab-d83dce732d78/collections/details, which returns:

{
    "continuationToken": null,
    "data": [
        {
            "assigned": true,
            "externalId": null,
            "groups": [],
            "hidePasswords": false,
            "id": "5790582d-ca80-4987-8593-d67103d6e83f",
            "manage": true,
            "name": "2.0kaWURRr1yToBnF8D5B6wg==|bjReqAAu29ytSq0HiQkmLXlp3XiiST2gQvDnN491RAc=|9i6HwG0MPu0hqESoWrW43+PQigObdBKIV1g2m/YPy+0=",
            "object": "collectionAccessDetails",
            "organizationId": "294adbf6-50ff-468f-96ab-d83dce732d78",
            "readOnly": false,
            "unmanaged": false,
            "users": [
                {
                    "hidePasswords": false,
                    "id": "6e8a5a8f-949f-48c1-abc9-63258c2a4bde",
                    "manage": true,
                    "readOnly": false
                },
                {
                    "hidePasswords": false,
                    "id": "cbec232c-f4f8-4e3d-975c-0aa24440d48a",
                    "manage": true,
                    "readOnly": false
                }
            ]
        },
        {
            "assigned": true,
            "externalId": null,
            "groups": [],
            "hidePasswords": false,
            "id": "6755fc59-187d-4988-a5e2-14a73fcbd042",
            "manage": true,
            "name": "2.uQ3TvwR67v+jGNBS/eES7w==|Ucf+uwf3WOaCd93CjltiAGnOEHdxPEdGf1Foj4NrdNM=|eguhZ2aCh1ZSGw5UlvI9IEMH4d/jqXTYF3EZ+HjRZ/c=",
            "object": "collectionAccessDetails",
            "organizationId": "294adbf6-50ff-468f-96ab-d83dce732d78",
            "readOnly": false,
            "unmanaged": false,
            "users": [
                {
                    "hidePasswords": false,
                    "id": "6e8a5a8f-949f-48c1-abc9-63258c2a4bde",
                    "manage": true,
                    "readOnly": false
                },
                {
                    "hidePasswords": false,
                    "id": "cbec232c-f4f8-4e3d-975c-0aa24440d48a",
                    "manage": true,
                    "readOnly": false
                }
            ]
        }
    ],
    "object": "list"
}

Looking at this, specially at the mini-details call, I'd say all users in the organization are being sent, so I still think it might be a Web Vault issue.

<!-- gh-comment-id:4802146252 --> @martin-s-a commented on GitHub (Jun 25, 2026): > ... it still might be a bug in our backend. Have not tried to replicate it but it could well be that we are not returning the collections associated with unconfirmed users. I just replicated it and when clicking "Edit access" on a collection, two separate GET requests are made: `https://myvaultdomain.com/api/organizations/294adbf6-50ff-468f-96ab-d83dce732d78/users/mini-details` which returns this json object: ```json { "continuationToken": null, "data": [ { "email": "admin1@mydomain.com", "id": "6e8a5a8f-949f-48c1-abc9-63258c2a4bde", "name": "Admin 1", "object": "organizationUserUserMiniDetails", "status": 2, "type": 0, "userId": "5ed821eb-1728-4bb5-8721-d9d37c7868ea" }, { "email": "admin2@mydomain.com", "id": "cbec232c-f4f8-4e3d-975c-0aa24440d48a", "name": "Admin 2", "object": "organizationUserUserMiniDetails", "status": 2, "type": 0, "userId": "9e1afe79-3eac-4b9e-9136-da5779d11fe0" }, { "email": "newadmin@mydomain.com", "id": "76f95979-19c5-4e56-a16c-804df883f181", "name": "New unconfirmed admin", "object": "organizationUserUserMiniDetails", "status": 1, "type": 1, "userId": "62d5213d-32fb-4e26-8642-8b552a35eb58" } ], "object": "list" } ``` and also `https://myvaultdomain.com/api/organizations/294adbf6-50ff-468f-96ab-d83dce732d78/collections/details`, which returns: ```json { "continuationToken": null, "data": [ { "assigned": true, "externalId": null, "groups": [], "hidePasswords": false, "id": "5790582d-ca80-4987-8593-d67103d6e83f", "manage": true, "name": "2.0kaWURRr1yToBnF8D5B6wg==|bjReqAAu29ytSq0HiQkmLXlp3XiiST2gQvDnN491RAc=|9i6HwG0MPu0hqESoWrW43+PQigObdBKIV1g2m/YPy+0=", "object": "collectionAccessDetails", "organizationId": "294adbf6-50ff-468f-96ab-d83dce732d78", "readOnly": false, "unmanaged": false, "users": [ { "hidePasswords": false, "id": "6e8a5a8f-949f-48c1-abc9-63258c2a4bde", "manage": true, "readOnly": false }, { "hidePasswords": false, "id": "cbec232c-f4f8-4e3d-975c-0aa24440d48a", "manage": true, "readOnly": false } ] }, { "assigned": true, "externalId": null, "groups": [], "hidePasswords": false, "id": "6755fc59-187d-4988-a5e2-14a73fcbd042", "manage": true, "name": "2.uQ3TvwR67v+jGNBS/eES7w==|Ucf+uwf3WOaCd93CjltiAGnOEHdxPEdGf1Foj4NrdNM=|eguhZ2aCh1ZSGw5UlvI9IEMH4d/jqXTYF3EZ+HjRZ/c=", "object": "collectionAccessDetails", "organizationId": "294adbf6-50ff-468f-96ab-d83dce732d78", "readOnly": false, "unmanaged": false, "users": [ { "hidePasswords": false, "id": "6e8a5a8f-949f-48c1-abc9-63258c2a4bde", "manage": true, "readOnly": false }, { "hidePasswords": false, "id": "cbec232c-f4f8-4e3d-975c-0aa24440d48a", "manage": true, "readOnly": false } ] } ], "object": "list" } ``` Looking at this, specially at the `mini-details` call, I'd say all users in the organization are being sent, so I still think it might be a Web Vault issue.
Author
Owner

@maximilize commented on GitHub (Jun 25, 2026):

That mini-details vs collections/details discrepancy is consistent backend behaviour, not a dropped assignment — it comes from how each endpoint builds its user list:

  • get_org_collections_details (src/api/core/organizations.rs:404) composes each collection's users[] from explicit CollectionUser rows plus Membership::find_confirmed_and_manage_all_by_org, which filters status == Confirmed (and atype Owner/Admin, or manage-all Manager). That's why your two confirmed Owners show up as manage:true on every collection.
  • The new admin is manage-all (type Admin) and unconfirmed, so it lands in neither set: manage-all members never get an explicit CollectionUser row (the assign path skips access_all at :694), and the Confirmed filter drops it from manage_all_members.
  • mini-details (organizations.rs:1464) lists all members regardless of status — hence it's there but not in collections/details.

So nothing is being silently lost server-side: an unconfirmed member has no org key and no collection access yet, so it's deliberately not surfaced as a collection member until confirmed. Surfacing pending-confirmation admins would be a design change, not a bug fix — which matches your read that this is a web-vault presentation choice, and the bump won't (and needn't) change this path.

<!-- gh-comment-id:4802211722 --> @maximilize commented on GitHub (Jun 25, 2026): That `mini-details` vs `collections/details` discrepancy is consistent backend behaviour, not a dropped assignment — it comes from how each endpoint builds its user list: - `get_org_collections_details` (`src/api/core/organizations.rs:404`) composes each collection's `users[]` from explicit `CollectionUser` rows **plus** `Membership::find_confirmed_and_manage_all_by_org`, which filters `status == Confirmed` (and atype Owner/Admin, or manage-all Manager). That's why your two confirmed Owners show up as `manage:true` on every collection. - The new admin is manage-all (type Admin) **and** unconfirmed, so it lands in neither set: manage-all members never get an explicit `CollectionUser` row (the assign path skips `access_all` at `:694`), and the `Confirmed` filter drops it from `manage_all_members`. - `mini-details` (`organizations.rs:1464`) lists *all* members regardless of status — hence it's there but not in `collections/details`. So nothing is being silently lost server-side: an unconfirmed member has no org key and no collection access yet, so it's deliberately not surfaced as a collection member until confirmed. Surfacing pending-confirmation admins would be a design change, not a bug fix — which matches your read that this is a web-vault presentation choice, and the bump won't (and needn't) change this path.
Author
Owner

@BlackDex commented on GitHub (Jul 1, 2026):

I'm going to close this as this hardly is a bug going to be fixed by updating the web-vault as @stefan0xC already mentioned.
There are several steps still to be taken to before I'm sure we can merge some PR's and update the web-vault anyway.
Since I'm going away for a few day's it will take a bit longer for this to be happening.

<!-- gh-comment-id:4855333957 --> @BlackDex commented on GitHub (Jul 1, 2026): I'm going to close this as this hardly is a bug going to be fixed by updating the web-vault as @stefan0xC already mentioned. There are several steps still to be taken to before I'm sure we can merge some PR's and update the web-vault anyway. Since I'm going away for a few day's it will take a bit longer for this to be happening.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/vaultwarden#35623