bitwarden/server · error · BadRequestException

All existing trusted devices must be included in the rotatio

Error message

All existing trusted devices must be included in the rotation.

What it means

Thrown by DeviceRotationValidator during a user encryption-key rotation (e.g. master password change). The validator loads every device the user has marked as trusted (devices holding an encrypted copy of the user key for SSO/trust flows) and requires the rotation request to include every one of them, matched by DeviceId. If even one trusted device is absent from the submitted list, the whole rotation is rejected so that no device is left encrypted under the old (now-rotated) key.

Source

Thrown at src/Api/KeyManagement/Validators/DeviceRotationValidator.cs:40

        _deviceRepository = deviceRepository;
    }

    public async Task<IEnumerable<Device>> ValidateAsync(User user, IEnumerable<OtherDeviceKeysUpdateRequestModel> devices)
    {
        var result = new List<Device>();

        var existingTrustedDevices = (await _deviceRepository.GetManyByUserIdAsync(user.Id)).Where(d => d.IsTrusted()).ToList();
        if (existingTrustedDevices.Count == 0)
        {
            return result;
        }

        foreach (var existing in existingTrustedDevices)
        {
            var device = devices.FirstOrDefault(c => c.DeviceId == existing.Id);
            if (device == null)
            {
                throw new BadRequestException("All existing trusted devices must be included in the rotation.");
            }

            if (device.EncryptedUserKey == null || device.EncryptedPublicKey == null)
            {
                throw new BadRequestException("Rotated encryption keys must be provided for all devices that are trusted.");
            }

            result.Add(device.ToDevice(existing));
        }

        return result;
    }
}

View on GitHub (pinned to e93b962371)

Solutions

  1. Re-fetch the user's full device list immediately before building the rotation payload so no trusted device is omitted.
  2. Ensure every entry with IsTrusted()=true on the server is present in the request, keyed by its exact DeviceId GUID.
  3. If a device should no longer be trusted, un-trust/delete it through its own endpoint before rotating keys, rather than dropping it from the rotation list.
  4. Validate the submitted DeviceId set is a superset of the trusted-device set before sending the request.

Example fix

// before: client sends only devices it knew about
var devices = ["device-a", "device-b"];
rotate({ devices: devices.map(reencrypt) });

// after: fetch the authoritative trusted set first, include all
const trusted = await api.getMyDevices();
const devices = trusted.filter(d => d.isTrusted).map(d => d.id);
rotate({ devices: devices.map(reencrypt) });
Defensive patterns

Strategy: validation

Validate before calling

// Before rotating, ensure every trusted device is represented
const trusted = (await api.getMyDevices()).filter(d => d.isTrusted);
const submitted = new Set(rotationPayload.devices.map(d => d.deviceId));
const missing = trusted.filter(d => !submitted.has(d.id));
if (missing.length) {
  throw new Error(`Rotation is missing trusted devices: ${missing.map(d => d.id).join(', ')}`);
}

Type guard

function isCompleteDeviceRotation(existing: Device[], submitted: { deviceId: string }[]): boolean {
  const have = new Set(submitted.map(s => s.deviceId));
  return existing.filter(d => d.isTrusted).every(d => have.has(d.id));
}

Try / catch

try {
  await api.rotateKey(payload);
} catch (e) {
  if (e.status === 400 && /trusted devices must be included/i.test(e.message)) {
    await refreshDevices();
    payload.devices = trustedDevices.map(reencrypt);
    return api.rotateKey(payload);
  }
  throw e;
}

Prevention

When it happens

Trigger: PUT/POST to the user key-rotation endpoint with an `OtherDeviceKeysUpdateRequestModel` collection that omits one or more DeviceIds returned by the devices repository for the user. A new device was trusted since the client last enumerated the device list; the client sent a stale/partial list; or a DeviceId was malformed/transposed.

Common situations: A client app cached the device list at session start and a device was trusted in another session; the rotation request was hand-built and only included 'known' devices; concurrent rotations or a race where a device trust completed mid-rotation.

Related errors


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