High severity8.1NVD Advisory· Published May 11, 2026· Updated May 16, 2026
CVE-2026-43640
CVE-2026-43640
Description
Bitwarden Server prior to v2026.4.1 does not require master-password re-authentication when retrieving or rotating an organization's SCIM API key, allowing an authenticated user with SCIM management privileges to obtain the key using only a valid session.
Affected products
1Patches
1eb251d9bf807Removing not scim check from api-key and rotate-api-key (#7403)
2 files changed · +120 −15
src/Api/AdminConsole/Controllers/OrganizationsController.cs+9 −15 modified@@ -394,17 +394,14 @@ public async Task<ApiKeyResponseModel> ApiKey(string id, [FromBody] Organization throw new UnauthorizedAccessException(); } - if (model.Type != OrganizationApiKeyType.Scim - && !await _userService.VerifySecretAsync(user, model.Secret)) + if (!await _userService.VerifySecretAsync(user, model.Secret)) { await Task.Delay(2000); throw new BadRequestException("MasterPasswordHash", "Invalid password."); } - else - { - var response = new ApiKeyResponseModel(organizationApiKey); - return response; - } + + var response = new ApiKeyResponseModel(organizationApiKey); + return response; } [HttpGet("{id}/api-key-information/{type?}")] @@ -447,18 +444,15 @@ public async Task<ApiKeyResponseModel> RotateApiKey(string id, [FromBody] Organi throw new UnauthorizedAccessException(); } - if (model.Type != OrganizationApiKeyType.Scim - && !await _userService.VerifySecretAsync(user, model.Secret)) + if (!await _userService.VerifySecretAsync(user, model.Secret)) { await Task.Delay(2000); throw new BadRequestException("MasterPasswordHash", "Invalid password."); } - else - { - await _rotateOrganizationApiKeyCommand.RotateApiKeyAsync(organizationApiKey); - var response = new ApiKeyResponseModel(organizationApiKey); - return response; - } + + await _rotateOrganizationApiKeyCommand.RotateApiKeyAsync(organizationApiKey); + var response = new ApiKeyResponseModel(organizationApiKey); + return response; } private async Task<bool> HasApiKeyAccessAsync(Guid orgId, OrganizationApiKeyType? type)
test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs+111 −0 modified@@ -1,12 +1,14 @@ using System.Security.Claims; using Bit.Api.AdminConsole.Controllers; +using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.Auth.Models.Request.Accounts; using Bit.Api.Models.Request.Organizations; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Models.Business; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Policies; @@ -228,4 +230,113 @@ await sutProvider.GetDependency<IOrganizationService>() s.LimitItemDeletion == model.LimitItemDeletion && s.AllowAdminAccessToAllCollectionItems == model.AllowAdminAccessToAllCollectionItems)); } + + [Theory, BitAutoData] + public async Task ApiKey_ScimType_InvalidSecret_ThrowsBadRequest( + SutProvider<OrganizationsController> sutProvider, + Organization organization, + OrganizationApiKey organizationApiKey, + User user) + { + organization.PlanType = PlanType.EnterpriseAnnually; + var model = new OrganizationApiKeyRequestModel + { + Type = OrganizationApiKeyType.Scim, + MasterPasswordHash = "invalid-hash" + }; + + sutProvider.GetDependency<ICurrentContext>().ManageScim(organization.Id).Returns(true); + sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization); + sutProvider.GetDependency<IGetOrganizationApiKeyQuery>() + .GetOrganizationApiKeyAsync(organization.Id, OrganizationApiKeyType.Scim) + .Returns(organizationApiKey); + + var userService = sutProvider.GetDependency<IUserService>(); + userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user); + userService.VerifySecretAsync(user, model.Secret).Returns(false); + + await Assert.ThrowsAsync<BadRequestException>( + () => sutProvider.Sut.ApiKey(organization.Id.ToString(), model)); + } + + [Theory, BitAutoData] + public async Task ApiKey_ScimType_ValidSecret_ReturnsApiKey( + SutProvider<OrganizationsController> sutProvider, + Organization organization, + OrganizationApiKey organizationApiKey, + User user) + { + organization.PlanType = PlanType.EnterpriseAnnually; + var model = new OrganizationApiKeyRequestModel + { + Type = OrganizationApiKeyType.Scim, + MasterPasswordHash = "valid-hash" + }; + + sutProvider.GetDependency<ICurrentContext>().ManageScim(organization.Id).Returns(true); + sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization); + sutProvider.GetDependency<IGetOrganizationApiKeyQuery>() + .GetOrganizationApiKeyAsync(organization.Id, OrganizationApiKeyType.Scim) + .Returns(organizationApiKey); + var userService = sutProvider.GetDependency<IUserService>(); + userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user); + userService.VerifySecretAsync(user, model.Secret).Returns(true); + + var result = await sutProvider.Sut.ApiKey(organization.Id.ToString(), model); + + Assert.Equal(organizationApiKey.ApiKey, result.ApiKey); + } + + [Theory, BitAutoData] + public async Task RotateApiKey_ScimType_InvalidSecret_ThrowsBadRequest( + SutProvider<OrganizationsController> sutProvider, + Organization organization, + OrganizationApiKey organizationApiKey, + User user) + { + var model = new OrganizationApiKeyRequestModel + { + Type = OrganizationApiKeyType.Scim, + MasterPasswordHash = "invalid-hash" + }; + + sutProvider.GetDependency<ICurrentContext>().ManageScim(organization.Id).Returns(true); + sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization); + sutProvider.GetDependency<IGetOrganizationApiKeyQuery>() + .GetOrganizationApiKeyAsync(organization.Id, OrganizationApiKeyType.Scim) + .Returns(organizationApiKey); + var userService = sutProvider.GetDependency<IUserService>(); + userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user); + userService.VerifySecretAsync(user, model.Secret).Returns(false); + + await Assert.ThrowsAsync<BadRequestException>( + () => sutProvider.Sut.RotateApiKey(organization.Id.ToString(), model)); + } + + [Theory, BitAutoData] + public async Task RotateApiKey_ScimType_ValidSecret_ReturnsApiKey( + SutProvider<OrganizationsController> sutProvider, + Organization organization, + OrganizationApiKey organizationApiKey, + User user) + { + var model = new OrganizationApiKeyRequestModel + { + Type = OrganizationApiKeyType.Scim, + MasterPasswordHash = "valid-hash" + }; + + sutProvider.GetDependency<ICurrentContext>().ManageScim(organization.Id).Returns(true); + sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization); + sutProvider.GetDependency<IGetOrganizationApiKeyQuery>() + .GetOrganizationApiKeyAsync(organization.Id, OrganizationApiKeyType.Scim) + .Returns(organizationApiKey); + var userService = sutProvider.GetDependency<IUserService>(); + userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user); + userService.VerifySecretAsync(user, model.Secret).Returns(true); + + var result = await sutProvider.Sut.RotateApiKey(organization.Id.ToString(), model); + + Assert.Equal(organizationApiKey.ApiKey, result.ApiKey); + } }
Vulnerability mechanics
AI mechanics synthesis has not run for this CVE yet.
References
5- github.com/bitwarden/server/commit/eb251d9bf80724c87b187661783b9354d1784083nvdPatch
- github.com/bitwarden/server/pull/7403nvdIssue TrackingPatch
- sanjokkarki.com.np/blog/bitwarden-scim-key-bypassnvdExploitThird Party Advisory
- www.vulncheck.com/advisories/bitwarden-server-authentication-bypass-via-scim-api-keynvdThird Party Advisory
- github.com/bitwarden/server/releases/tag/v2026.4.1nvdRelease Notes
News mentions
0No linked articles in our index yet.