bitwarden/server · error · BadRequestException

Reset Password keys cannot be set to null during rotation.

Error message

Reset Password keys cannot be set to null during rotation.

What it means

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.)

Source

Thrown at src/Api/KeyManagement/Validators/OrganizationUserRotationValidator.cs:51

            return result;
        }

        // Exclude any account recovery that do not have a key.
        existing = existing.Where(o => OrganizationUser.IsValidResetPasswordKey(o.ResetPasswordKey)).ToList();

        foreach (var ou in existing)
        {
            var organizationUser = resetPasswordKeys.FirstOrDefault(a => a.OrganizationId == ou.OrganizationId);
            if (organizationUser == null)
            {
                throw new BadRequestException("All existing reset password keys must be included in the rotation.");
            }

            // Should be migrated to: if (!OrganizationUser.IsValidResetPasswordKey(organizationUser.ResetPasswordKey))
            // after https://bitwarden.atlassian.net/browse/PM-31001 is resolved
            if (organizationUser.ResetPasswordKey == null)
            {
                throw new BadRequestException("Reset Password keys cannot be set to null during rotation.");
            }

            ou.ResetPasswordKey = organizationUser.ResetPasswordKey;
            result.Add(ou);
        }

        return result;
    }
}

View on GitHub (pinned to e93b962371)

Solutions

  1. Re-encrypt the ResetPasswordKey with the new user key for every included org membership.
  2. To stop account recovery for an org, leave the org or have recovery disabled administratively before rotating.
  3. Assert every included entry has a non-null ResetPasswordKey before submitting.

Example fix

// before: trying to clear recovery
{ organizationId: o.id, resetPasswordKey: null }

// after: re-encrypt, or handle removal out of band
{ organizationId: o.id, resetPasswordKey: reencrypt(o.resetPasswordKey) }
Defensive patterns

Strategy: validation

Validate before calling

const nulled = payload.resetPasswordKeys.filter(k => k.resetPasswordKey == null);
if (nulled.length) {
  throw new Error(`Reset password keys cannot be null for orgs: ${nulled.map(k => k.organizationId).join(', ')}`);
}

Type guard

function hasResetPasswordKey(k: { resetPasswordKey?: string | null }): boolean {
  return k.resetPasswordKey != null && k.resetPasswordKey.length > 0;
}

Try / catch

try {
  await api.rotateKey(payload);
} catch (e) {
  if (e.status === 400 && /cannot be set to null/i.test(e.message)) {
    payload.resetPasswordKeys = payload.resetPasswordKeys.map(k => ({ ...k, resetPasswordKey: reencrypt(k.resetPasswordKey ?? oldKeyFor(k.organizationId)) }));
    return api.rotateKey(payload);
  }
  throw e;
}

Prevention

When it happens

Trigger: The reset-password-keys array includes the OrganizationId but the ResetPasswordKey field is null or was omitted during request construction.

Common situations: 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.

Related errors


AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13). Data as JSON: /api/errors/06e5874310b7442e. Report an issue: GitHub.