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
Web Vault 2026.4.1 (bundled with Vaultwarden v1.36.0)
Steps To Reproduce
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).
As Owner, open an existing collection → Edit access
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.
Click Save. The toast notification reports Collection <name> edited successfully.
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):
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
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.
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:
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 → Accepted & 2 → 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.
<!-- 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` → `Accepted` & `2` → `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.
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.
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.
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.
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.
... 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:
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.
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 plusMembership::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.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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)
Config & Details (Generated via diagnostics page)
Show Config & Details
Failed HTTP Checks:
Config:
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 = Accepted).Collection <name> editedsuccessfully.Expected Result
Either:
In any case the UI feedback should match the actual server-side state.
Actual Result
200 OKand the success toast is shown.users_collectionstable for that user/collection pair.Direct inspection of the SQLite DB confirms: after the apparently-successful save the
users_collectionstable contains zero rows for the target user, even though the membership row exists inusers_organizations(withstatus = 1).Logs
Screenshots or Videos
No response
Additional Context
DB state right after the apparently-successful save (SQLite, schema of v1.36.0):
In all fields above, I have anonimized the data (emails, userames, etc) for privacy reasons.
@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 viaMembership::find_by_uuid_and_org(no status filter —src/db/models/organization.rs:829-838) and callsCollectionUser::save, an unconditional upsert (src/db/models/collection.rs:674-735). The only skip isif member.access_all { continue; }(organizations.rs:694).The re-open doesn't hide it either:
get_org_collections_detailslists 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 fromdata.users(thendelete_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 membershipidis in theusersarray? That pins it to client vs. server.@martin-s-a commented on GitHub (Jun 22, 2026):
To replicate it, I followed this process:
The originated
PUTrequest payload was:and the response was a
200 OKwith the body:The I went to the DB, and inspected the table
users_collections(for collection with uuid6755fc59-187d-4988-a5e2-14a73fcbd042) and got:Where I believe that
1→Accepted&2→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_collectionsrow exists for any of the three users — including the two already-Confirmed admins. This is consistent with theif member.access_all { continue; }skip inorganizations.rs:694applying 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 = truemakes 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.
@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) becauseaccess_allalready 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 upstreambitwarden/clients, so this almost certainly reproduces identically on bitwarden.com — it isn't fixable in this repo. (I searchedbitwarden/clientsand 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.@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.
@martin-s-a commented on GitHub (Jun 24, 2026):
I am reopening because, after investigating the issue within the latest
2026.6.1client 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.
@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.
@martin-s-a commented on GitHub (Jun 25, 2026):
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-detailswhich returns this json object:
and also
https://myvaultdomain.com/api/organizations/294adbf6-50ff-468f-96ab-d83dce732d78/collections/details, which returns:Looking at this, specially at the
mini-detailscall, I'd say all users in the organization are being sent, so I still think it might be a Web Vault issue.@maximilize commented on GitHub (Jun 25, 2026):
That
mini-detailsvscollections/detailsdiscrepancy 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'susers[]from explicitCollectionUserrows plusMembership::find_confirmed_and_manage_all_by_org, which filtersstatus == Confirmed(and atype Owner/Admin, or manage-all Manager). That's why your two confirmed Owners show up asmanage:trueon every collection.CollectionUserrow (the assign path skipsaccess_allat:694), and theConfirmedfilter drops it frommanage_all_members.mini-details(organizations.rs:1464) lists all members regardless of status — hence it's there but not incollections/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.
@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.