From 62376f72405731d80a33df13901b566d5c85273d Mon Sep 17 00:00:00 2001 From: hbhalodia Date: Thu, 13 Aug 2026 12:15:59 +0530 Subject: [PATCH 1/2] Fix remove API check for connectors on every settings save via /wp/v2/settings --- src/wp-includes/connectors.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/connectors.php b/src/wp-includes/connectors.php index 7433203ab7fec..bf079e1727abd 100644 --- a/src/wp-includes/connectors.php +++ b/src/wp-includes/connectors.php @@ -705,6 +705,7 @@ function _wp_connectors_rest_settings_dispatch( WP_REST_Response $response, WP_R } $is_update = 'POST' === $request->get_method() || 'PUT' === $request->get_method(); + $submitted = $is_update ? $request->get_params() : array(); foreach ( wp_get_connectors() as $connector_id => $connector_data ) { $auth = $connector_data['authentication']; @@ -731,9 +732,14 @@ function _wp_connectors_rest_settings_dispatch( WP_REST_Response $response, WP_R $value = $data[ $setting_name ]; - // On update, validate AI provider keys before masking. - // Non-AI connectors accept keys as-is; the service plugin handles its own validation. - if ( $is_update && is_string( $value ) && '' !== $value && 'ai_provider' === $connector_data['type'] ) { + // On update, validate AI provider keys before masking, but only when the + // key was actually submitted in this request. Non-AI connectors accept keys + // as-is; the service plugin handles its own validation. + if ( $is_update + && array_key_exists( $setting_name, $submitted ) + && is_string( $value ) && '' !== $value + && 'ai_provider' === $connector_data['type'] + ) { if ( true !== _wp_connectors_is_ai_api_key_valid( $value, $connector_id ) ) { update_option( $setting_name, '' ); $data[ $setting_name ] = ''; From 78545362d6880e8507902e99876d9333b1235f51 Mon Sep 17 00:00:00 2001 From: hbhalodia Date: Thu, 13 Aug 2026 12:59:20 +0530 Subject: [PATCH 2/2] Add tests for the changes added --- .../wpConnectorsRestSettingsDispatch.php | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/phpunit/tests/connectors/wpConnectorsRestSettingsDispatch.php b/tests/phpunit/tests/connectors/wpConnectorsRestSettingsDispatch.php index 7e0aa7ce6b2f1..ba9476815272e 100644 --- a/tests/phpunit/tests/connectors/wpConnectorsRestSettingsDispatch.php +++ b/tests/phpunit/tests/connectors/wpConnectorsRestSettingsDispatch.php @@ -1,4 +1,7 @@ assertSame( str_repeat( "\u{2022}", 16 ), $data[ self::CREDENTIALS_SETTING_NAME ]['password'] ); $this->assertNotSame( $application_password, $data[ self::CREDENTIALS_SETTING_NAME ]['password'] ); } + + /** + * Ensures a stored AI provider API key is not re-validated, and therefore not + * reset, when it is not part of the current settings update. + * + * @ticket 65867 + */ + public function test_does_not_validate_or_reset_unsubmitted_ai_key(): void { + $stored_key = 'sk-stored-valid-key'; + update_option( self::AI_KEY_SETTING_NAME, $stored_key ); + + self::set_mock_provider_configured( false ); + + // An update that does not submit the AI key (e.g. saving another setting). + $request = new WP_REST_Request( 'POST', '/wp/v2/settings' ); + $request->set_param( 'title', 'New Site Title' ); + + // The settings endpoint response always contains every registered setting. + $response = new WP_REST_Response( array( self::AI_KEY_SETTING_NAME => $stored_key ) ); + + $result = _wp_connectors_rest_settings_dispatch( $response, rest_get_server(), $request ); + $data = $result->get_data(); + + $this->assertSame( + $stored_key, + get_option( self::AI_KEY_SETTING_NAME ), + 'An AI provider key that was not submitted should not be reset.' + ); + $this->assertSame( + _wp_connectors_mask_api_key( $stored_key ), + $data[ self::AI_KEY_SETTING_NAME ], + 'The stored AI provider key should still be masked in the response.' + ); + + self::set_mock_provider_configured( true ); + delete_option( self::AI_KEY_SETTING_NAME ); + } }