bitwarden/server · error · BadRequestException
All existing ciphers must be included in the rotation.
Error message
All existing ciphers must be included in the rotation.
What it means
Thrown by CipherRotationValidator when the set of ciphers submitted for a key rotation does not include every existing personal (non-organization) cipher the user currently has. The validator iterates over existing user ciphers and requires a matching submitted cipher by Id for each one. BadRequestException returns HTTP 400.
Source
Thrown at src/Api/KeyManagement/Validators/CipherRotationValidator.cs:39
var existingCiphers = await _cipherRepository.GetManyByUserIdAsync(user.Id);
if (existingCiphers == null)
{
return result;
}
var existingUserCiphers = existingCiphers.Where(c => c.OrganizationId == null);
if (existingUserCiphers.Count() == 0)
{
return result;
}
foreach (var existing in existingUserCiphers)
{
var cipher = ciphers.FirstOrDefault(c => c.Id == existing.Id);
if (cipher == null)
{
throw new BadRequestException("All existing ciphers must be included in the rotation.");
}
result.Add(cipher.ToCipher(existing));
}
return result;
}
}
View on GitHub (pinned to e93b962371)
Solutions
- Re-fetch the full cipher list immediately before submitting the rotation to minimize the race window.
- Ensure the client sends the complete, untruncated cipher list including all personal ciphers.
- If a cipher was added concurrently, abort the rotation, re-fetch, and retry.
- Consider implementing an optimistic-concurrency check (e.g., a revision/version stamp) to detect stale cipher lists.
Example fix
// before await api.RotateAllCipherKeysAsync(staleCipherList); // throws if incomplete // after — refresh right before rotation var currentCiphers = await api.ListCiphersAsync(); await api.RotateAllCipherKeysAsync(currentCiphers);
Defensive patterns
Strategy: validation
Validate before calling
// Refresh cipher list immediately before rotation
var currentCiphers = await _cipherRepository.GetManyByUserIdAsync(userId);
var personalCiphers = currentCiphers.Where(c => c.OrganizationId == null);
var submittedIds = submittedCiphers.Select(c => c.Id).ToHashSet();
if (!personalCiphers.All(c => submittedIds.Contains(c.Id)))
return BadRequest("Cipher list is stale — some personal ciphers are missing. Refresh and retry.");
await _cipherRotationService.RotateAsync(submittedCiphers); Type guard
public static bool AllPersonalCiphersIncluded(IEnumerable<Cipher> existing, IEnumerable<Cipher> submitted) =>
existing.Where(c => c.OrganizationId == null).All(e => submitted.Any(s => s.Id == e.Id)); Try / catch
try
{
await _cipherService.RotateAllKeysAsync(submittedCiphers);
}
catch (BadRequestException ex) when (ex.Message.Contains("All existing ciphers"))
{
// Re-fetch and retry once
var fresh = await _cipherRepository.GetManyByUserIdAsync(userId);
await _cipherService.RotateAllKeysAsync(fresh.Where(c => c.OrganizationId == null));
} Prevention
- Re-fetch the complete cipher list immediately before submitting a key rotation.
- Prevent concurrent cipher creation during the rotation window using a client-side lock.
- Validate that the submitted cipher count matches the fetched personal cipher count before sending.
When it happens
Trigger: POST to the key-rotation endpoint (e.g., /ciphers/rotate-all) with a cipher list that is missing one or more of the user's existing personal ciphers. This is a safety check — rotating keys requires re-encrypting every cipher with the new key; omitting any would leave ciphers undecryptable.
Common situations: Client fetches the cipher list, user creates a new cipher in another session/tab, then the rotation is submitted with the now-stale list; network error caused one cipher to be dropped from the payload; client-side bug truncating the list; concurrent modification between fetch and rotate.
Related errors
- KeyConnectorKeyWrappedUserKey and AccountKeys must be suppli
- All existing trusted devices must be included in the rotatio
- All existing emergency access keys must be included in the r
- All existing folders must be included in the rotation.
- All existing reset password keys must be included in the rot
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/c62d41824b66a348.
Report an issue: GitHub.