{"record":{"id":"94d887b7f2e1888b","repo":"passbolt/passbolt_api","slug":"invalid-request-new-key-is-required-for-key-rotation","errorCode":null,"errorMessage":"Invalid request. New key is required for key rotation.","messagePattern":"Invalid request\\. New key is required for key rotation\\.","errorType":"http","errorClass":"Cake\\Http\\Exception\\BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryOrganizationPolicies/AccountRecoveryOrganizationPolicySetService.php","lineNumber":60,"sourceCode":"        // assert policy is provided as it should in any case\n        $newPolicy = $this->buildAndValidatePolicyEntityFromData($uac);\n\n        // Check request composition to understand user goal\n        $isPolicyChange = $this->isPolicyChange();\n        $isNewKeyProvided = $this->isPublicKeyProvided();\n        $isRevokedKeyProvided = $this->isRevokedKeyProvided();\n        $isPrivateKeyPasswordsProvided = $this->isPrivateKeyPasswordsProvided();\n\n        // if policy has not changed and (new key not provided or revoked key not provided)\n        if (!$isPolicyChange && !$isNewKeyProvided && !$isRevokedKeyProvided) {\n            throw new BadRequestException(__('Invalid request. No policy change.'));\n        }\n        if (!$isPolicyChange && $isNewKeyProvided && !$isRevokedKeyProvided) {\n            throw new BadRequestException(__('Invalid request. Revoked key is required for key rotation.'));\n        }\n        /** @psalm-suppress RedundantCondition */\n        if (!$isPolicyChange && !$isNewKeyProvided && $isRevokedKeyProvided) {\n            throw new BadRequestException(__('Invalid request. New key is required for key rotation.'));\n        }\n\n        // if disabled => enabled\n        if ($this->isEnabling()) {\n            // if public key is not provided\n            if (!$isNewKeyProvided) {\n                throw new BadRequestException(__('Invalid request. An organization recovery public key is required.'));\n            }\n            // if key revocation or passwords provided\n            if ($isRevokedKeyProvided || $isPrivateKeyPasswordsProvided) {\n                throw new BadRequestException(__('Invalid request. Revoked key or passwords are not required.'));\n            }\n\n            return $this->enablePolicy($uac, $newPolicy);\n        }\n\n        // if enabled => disabled\n        if ($this->isDisabling()) {","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryOrganizationPolicies/AccountRecoveryOrganizationPolicySetService.php#L42-L78","documentation":"Composition guard in the account recovery organization policy set service: the request neither changes the policy nor provides the new server key required for key rotation, so the requested operation is undetermined/invalid for key rotation and is rejected.","triggerScenarios":"PUT with unchanged policy and only the revoked key payload — client intends to revoke but forgot to attach the new organization public key.","commonSituations":"Confusion between 'revoke' and 'rotate' semantics; UI bug dropping the new-key field; admin thinks revoking alone is valid while the policy stays enabled.","solutions":["Provide both the new public key and the revoked old key in the same request","If the goal is to disable recovery entirely, set the policy to 'disabled' instead of sending a revoked key","If no change is intended, omit both keys"],"exampleFix":"// before\nawait api.put('/account-recovery/organization-settings.json', { policy: 'opt-in', revoked_key: oldKey });\n// after\nawait api.put('/account-recovery/organization-settings.json', { policy: 'opt-in', new_key: newPublicKey, revoked_key: oldKey });","handlingStrategy":"validation","validationCode":"if (revokedKey && !newKey && currentPolicy === desiredPolicy) throw new Error('rotation requires a new key');","typeGuard":null,"tryCatchPattern":"try { await updateSettings(payload); } catch (e) { if (e.message.includes('New key is required')) promptForNewKey(); else throw e; }","preventionTips":["Never send a revoked key alone","If disabling, send policy 'disabled' instead","Clarify revoke-vs-rotate semantics in the UI"],"tags":["account-recovery","key-rotation","missing-field","bad-request"],"backgroundTag":"missing-required-argument","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}