{"record":{"id":"06e5874310b7442e","repo":"bitwarden/server","slug":"reset-password-keys-cannot-be-set-to-null-during-r","errorCode":null,"errorMessage":"Reset Password keys cannot be set to null during rotation.","messagePattern":"Reset Password keys cannot be set to null during rotation\\.","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Api/KeyManagement/Validators/OrganizationUserRotationValidator.cs","lineNumber":51,"sourceCode":"            return result;\n        }\n\n        // Exclude any account recovery that do not have a key.\n        existing = existing.Where(o => OrganizationUser.IsValidResetPasswordKey(o.ResetPasswordKey)).ToList();\n\n        foreach (var ou in existing)\n        {\n            var organizationUser = resetPasswordKeys.FirstOrDefault(a => a.OrganizationId == ou.OrganizationId);\n            if (organizationUser == null)\n            {\n                throw new BadRequestException(\"All existing reset password keys must be included in the rotation.\");\n            }\n\n            // Should be migrated to: if (!OrganizationUser.IsValidResetPasswordKey(organizationUser.ResetPasswordKey))\n            // after https://bitwarden.atlassian.net/browse/PM-31001 is resolved\n            if (organizationUser.ResetPasswordKey == null)\n            {\n                throw new BadRequestException(\"Reset Password keys cannot be set to null during rotation.\");\n            }\n\n            ou.ResetPasswordKey = organizationUser.ResetPasswordKey;\n            result.Add(ou);\n        }\n\n        return result;\n    }\n}\n","sourceCodeStart":33,"sourceCodeEnd":61,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/KeyManagement/Validators/OrganizationUserRotationValidator.cs#L33-L61","documentation":"Thrown by OrganizationUserRotationValidator when a rotation entry matched an org membership with a valid ResetPasswordKey (matched by OrganizationId) but the submitted ResetPasswordKey is null. Reset keys cannot be cleared via the rotation endpoint; doing so would break account recovery, so it is rejected. (A tracked TODO notes this should use IsValidResetPasswordKey once PM-31001 lands.)","triggerScenarios":"The reset-password-keys array includes the OrganizationId but the ResetPasswordKey field is null or was omitted during request construction.","commonSituations":"Client attempted to disable account recovery by nulling the key instead of leaving the org or having an admin disable recovery; serialization dropped the field; re-encryption of that key failed silently.","solutions":["Re-encrypt the ResetPasswordKey with the new user key for every included org membership.","To stop account recovery for an org, leave the org or have recovery disabled administratively before rotating.","Assert every included entry has a non-null ResetPasswordKey before submitting."],"exampleFix":"// before: trying to clear recovery\n{ organizationId: o.id, resetPasswordKey: null }\n\n// after: re-encrypt, or handle removal out of band\n{ organizationId: o.id, resetPasswordKey: reencrypt(o.resetPasswordKey) }","handlingStrategy":"validation","validationCode":"const nulled = payload.resetPasswordKeys.filter(k => k.resetPasswordKey == null);\nif (nulled.length) {\n  throw new Error(`Reset password keys cannot be null for orgs: ${nulled.map(k => k.organizationId).join(', ')}`);\n}","typeGuard":"function hasResetPasswordKey(k: { resetPasswordKey?: string | null }): boolean {\n  return k.resetPasswordKey != null && k.resetPasswordKey.length > 0;\n}","tryCatchPattern":"try {\n  await api.rotateKey(payload);\n} catch (e) {\n  if (e.status === 400 && /cannot be set to null/i.test(e.message)) {\n    payload.resetPasswordKeys = payload.resetPasswordKeys.map(k => ({ ...k, resetPasswordKey: reencrypt(k.resetPasswordKey ?? oldKeyFor(k.organizationId)) }));\n    return api.rotateKey(payload);\n  }\n  throw e;\n}","preventionTips":["Never null a reset key to disable recovery; leave the org or have an admin disable it.","Assert every included entry has a non-null ResetPasswordKey before submitting.","Confirm re-encryption succeeded for each org membership."],"tags":["key-rotation","organization","account-recovery","null-check","encryption","bad-request"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}