passbolt/passbolt_api · error · App\Error\Exception\CustomValidationException
Could not validate password data.
Error message
Could not validate password data.
What it means
Thrown by AccountRecoveryOrganizationPolicySetService::assertPasswordsCount when updating the organization account-recovery policy: the number of account_recovery_private_key_passwords entries sent in the request does not equal the number of password records already stored for the organization's recovery private key. The service wraps the mismatch in a CustomValidationException so the client receives a field-level error on 'account_recovery_private_key_passwords' with an invalidPasswordCount message naming expected vs actual counts.
Solutions
- Count the existing rows first (SELECT COUNT(*) FROM account_recovery_private_key_passwords) and send exactly that many password entries
- Fix the client payload so every stored private key has exactly one corresponding password entry (one per share recipient)
- If the stored rows are stale from a failed rotation, regenerate the organization recovery key and restart the policy setup flow
- Read the invalidPasswordCount message in the error details — it states the expected and actual numbers; align the payload to it
Example fix
// before
PUT /account-recovery/organization-policies
{"policy": "mandatory", "account_recovery_private_key_passwords": [/* only 1 entry */]}
// after
PUT /account-recovery/organization-policies
{"policy": "mandatory", "account_recovery_private_key_passwords": [/* one entry per stored key share, count matches DB */]} Defensive patterns
Strategy: validation
Validate before calling
// Before submitting the policy update:
$expected = $this->AccountRecoveryPrivateKeyPasswords->find()->all()->count();
if (count($payload['account_recovery_private_key_passwords'] ?? []) !== $expected) {
throw new \InvalidArgumentException("Send exactly {$expected} password entries");
} Type guard
// Guard payload shape before the API call
$ok = is_array($payload['account_recovery_private_key_passwords'] ?? null)
&& count($payload['account_recovery_private_key_passwords']) === $expectedCount; Try / catch
try {
$service->updatePolicy($uac, $data);
} catch (\App\Error\Exception\CustomValidationException $e) {
$details = $e->getErrors()['account_recovery_private_key_passwords'] ?? [];
if (isset($details['invalidPasswordCount'])) {
// parse expected/actual from $details['invalidPasswordCount'] and rebuild payload
}
} Prevention
- Always fetch the current organization policy and key first, then build a one-password-per-key-share payload
- After any key rotation, discard cached client payloads and rebuild from server state
- Parse the invalidPasswordCount detail message — it gives exact expected and actual counts
- Test policy updates against a staging instance before production
When it happens
Trigger: POST/PUT to the account-recovery organization policy settings endpoint with an 'account_recovery_private_key_passwords' array whose length differs from count(AccountRecoveryPrivateKeyPasswords) — e.g. omitting the passwords array entirely, sending only a subset of shares, or sending duplicates while rotating the policy.
Common situations: Client SDK or script not including the private-key passwords block during policy setup/rotation; stale client state after the organization key was re-generated so the client sends old counts; partially failed previous submission left an unexpected number of rows in account_recovery_private_key_passwords.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Could not save the account recovery private key.
- Could not save the account recovery setting.
- Could not validate key revocation.
- Could not validate password data.
- Could not validate policy data.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/e4af553dcd5ccee0.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryOrganizationPolicies/AccountRecoveryOrganizationPolicySetService.php:277
* Ensure the correct number of passwords are provided by the end user data
*
* Should be done in the same transaction than the save for data integrity purpose
* This assume the account_recovery_private_key_passwords is therefore validated
* It uses the original data for array operation speed versus working with entities
*
* @CustomValidationException if some of the private key passwords are missing
* @return void
*/
private function assertPasswordsCount(): void
{
$passwordsData = $this->getData('account_recovery_private_key_passwords');
// Check there is the correct number of passwords
$actual = count($passwordsData);
$expected = $this->AccountRecoveryPrivateKeyPasswords->find()->all()->count();
if ($actual !== $expected) {
$msg = __('An invalid number of passwords sent. Expected {0} and got {1}.', $expected, $actual);
throw new CustomValidationException(__('Could not validate password data.'), [
'account_recovery_private_key_passwords' => [
'invalidPasswordCount' => $msg,
],
]);
}
// Check there is the correct private key id for the passwords
$missing = $this->AccountRecoveryPrivateKeys->find()
->select('id')
->where(['id NOT IN' => Hash::extract($passwordsData, '{n}.private_key_id')])
->all();
if (count($missing)) {
throw new CustomValidationException(__('Could not validate password data.'), [
'account_recovery_private_key_passwords' => [
'missingPasswordForPrivateKeyIds' => Hash::extract($missing->toArray(), '{n}.id'),
],
]);
}View on GitHub (pinned to 31c1bbc10f)