bitwarden/server · error · BadRequestException
All existing emergency access keys must be included in the…
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.
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 folders must be included in the rotation.
- All existing reset password keys must be included in the…
- All existing sends must be included in the rotation.
- All existing trusted devices must be included in the…
- All existing webauthn prf keys must be included in the…
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/1ef39d107e14b31f.
Report an issue: GitHub.
Appendix: 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)