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

  1. Count the existing rows first (SELECT COUNT(*) FROM account_recovery_private_key_passwords) and send exactly that many password entries
  2. Fix the client payload so every stored private key has exactly one corresponding password entry (one per share recipient)
  3. If the stored rows are stale from a failed rotation, regenerate the organization recovery key and restart the policy setup flow
  4. 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

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


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)