bitwarden/server · error · BadRequestException

The model state is invalid.

Error message

The model state is invalid.

What it means

In POST /accounts/kdf, when _changeKdfCommand.ChangeKdfAsync returns a failed result, each IdentityError is added to ModelState, a 2-second delay is applied, and BadRequestException(ModelState) is thrown → HTTP 400. The change-KDF command validates the master password and the re-encrypted authentication/unlock data; any failure surfaces here.

Source

Thrown at src/Api/Auth/Controllers/AccountsController.cs:371

        var user = await _userService.GetUserByPrincipalAsync(User);
        if (user == null)
        {
            throw new UnauthorizedAccessException();
        }

        var result = await _changeKdfCommand.ChangeKdfAsync(user, model.MasterPasswordHash, model.AuthenticationData.ToData(), model.UnlockData.ToData());
        if (result.Succeeded)
        {
            return;
        }

        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }

        await Task.Delay(2000);
        throw new BadRequestException(ModelState);
    }

    [HttpPost("security-stamp")]
    public async Task PostSecurityStamp([FromBody] SecretVerificationRequestModel model)
    {
        var user = await _userService.GetUserByPrincipalAsync(User);
        if (user == null)
        {
            throw new UnauthorizedAccessException();
        }

        var result = await _userService.RefreshSecurityStampAsync(user, model.Secret);
        if (result.Succeeded)
        {
            return;
        }

        foreach (var error in result.Errors)

View on GitHub (pinned to e93b962371)

Solutions

  1. Inspect ValidationErrors for the specific failure (password vs. re-encryption vs. parameter range).
  2. Re-derive MasterPasswordHash from the correct, current master password and KDF settings.
  3. Rebuild AuthenticationData/UnlockData by re-encrypting the user key with the NEW KDF-derived key on a fresh, complete vault sync.
  4. Validate the requested KDF parameters against the server's allowed ranges before submitting.

Example fix

// before
model.AuthenticationData = ReencryptWith(oldKdf);
// after
model.MasterPasswordHash = DeriveHash(password, newKdf);
model.AuthenticationData = ReencryptUserKey(userKey, password, newKdf);
model.UnlockData = ReencryptWith(newKdf);
Defensive patterns

Strategy: validation

Validate before calling

// Validate KDF target params and re-encrypt with a complete vault before posting
if (!KdfParamsValid(newKdf)) errors.Add("KDF params out of range");
if (!vaultFullySynced) errors.Add("Sync vault before KDF change");

Try / catch

try { await client.PostKdfAsync(model); }
catch (BadRequestException ex) { /* read ex.ModelState: password vs re-encryption vs params */ }

Prevention

When it happens

Trigger: Wrong master password hash supplied with the KDF change; AuthenticationData or UnlockData that fail to decrypt/re-encrypt under the new KDF; invalid target KDF parameters (e.g. Argon2id with out-of-range memory/parallelism).

Common situations: User mistyped the master password during KDF migration; client built the re-encrypted payload with stale vault keys; Argon2id memory/iterations below server minimums; vault has ciphers the client failed to include in the re-encryption payload.

Related errors


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