bitwarden/server · error · BadRequestException
All existing emergency access keys must be included in the r
Error message
All existing emergency access keys must be included in the rotation.
What it means
Thrown by EmergencyAccessRotationValidator during key rotation. It loads every emergency-access grant the user has made (as grantor) that is already confirmed (KeyEncrypted != null), and requires the rotation request to include each one matched by Id. Omitting a confirmed emergency-access grant would leave it encrypted under the dead key, so the entire rotation is rejected.
Source
Thrown at src/Api/KeyManagement/Validators/EmergencyAccessRotationValidator.cs:41
public async Task<IEnumerable<EmergencyAccess>> ValidateAsync(User user,
IEnumerable<EmergencyAccessWithIdRequestModel> emergencyAccessKeys)
{
var result = new List<EmergencyAccess>();
var existing = await _emergencyAccessRepository.GetManyDetailsByGrantorIdAsync(user.Id);
if (existing == null || existing.Count == 0)
{
return result;
}
// Exclude any emergency access that has not been confirmed yet.
existing = existing.Where(ea => ea.KeyEncrypted != null).ToList();
foreach (var ea in existing)
{
var emergencyAccess = emergencyAccessKeys.FirstOrDefault(c => c.Id == ea.Id);
if (emergencyAccess == null)
{
throw new BadRequestException("All existing emergency access keys must be included in the rotation.");
}
if (emergencyAccess.KeyEncrypted == null)
{
throw new BadRequestException("Emergency access keys cannot be set to null during rotation.");
}
result.Add(emergencyAccess.ToEmergencyAccess(ea));
}
return result;
}
}
View on GitHub (pinned to e93b962371)
Solutions
- Fetch all confirmed emergency-access grants for the user immediately before constructing the rotation payload.
- Include every grant with KeyEncrypted set, keyed by its exact Id.
- Revoke any grant you do not want to rotate before starting the key rotation.
- Verify the submitted Id set is a superset of the server's confirmed-grant set before sending.
Example fix
// before
const ea = locallyKnownGrants.map(g => ({ id: g.id, keyEncrypted: reencrypt(g.key) }));
// after
const all = await api.getEmergencyAccessGrants();
const ea = all.filter(g => g.keyEncrypted).map(g => ({ id: g.id, keyEncrypted: reencrypt(g.key) })); Defensive patterns
Strategy: validation
Validate before calling
const confirmed = (await api.getEmergencyAccessGrants()).filter(g => g.keyEncrypted);
const submitted = new Set(payload.emergencyAccessKeys.map(g => g.id));
const missing = confirmed.filter(g => !submitted.has(g.id));
if (missing.length) {
throw new Error(`Rotation is missing emergency access grants: ${missing.map(g => g.id).join(', ')}`);
} Type guard
function isCompleteEmergencyAccessRotation(existing: { id: string; keyEncrypted: string | null }[], submitted: { id: string }[]): boolean {
const have = new Set(submitted.map(s => s.id));
return existing.filter(e => e.keyEncrypted != null).every(e => have.has(e.id));
} Try / catch
try {
await api.rotateKey(payload);
} catch (e) {
if (e.status === 400 && /emergency access keys must be included/i.test(e.message)) {
await refreshEmergencyAccess();
payload.emergencyAccessKeys = confirmed.map(g => ({ id: g.id, keyEncrypted: reencrypt(g.keyEncrypted) }));
return api.rotateKey(payload);
}
throw e;
} Prevention
- Re-fetch confirmed emergency-access grants before building the rotation payload.
- Revoke grants you do not want to rotate before starting the rotation.
- Assert the submitted id set is a superset of the confirmed-grant set.
When it happens
Trigger: Key-rotation request whose emergency-access array omits an EmergencyAccess.Id that exists for the grantor and already has a KeyEncrypted set. A grant was confirmed in another session after the client cached the list; or the client only rotated grants it 'remembers'.
Common situations: User confirmed a new emergency-access contact on another device and then rotated keys on this one; client built the list from local state rather than the server; a grant Id was dropped during serialization.
Related errors
- All existing trusted devices must be included in the rotatio
- Emergency access keys cannot be set to null during rotation.
- All existing folders must be included in the rotation.
- All existing reset password keys must be included in the rot
- All existing sends must be included in the rotation.
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/1ef39d107e14b31f.
Report an issue: GitHub.