diff --git a/changelog.txt b/changelog.txt index 61aeca7..a6f7d91 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,18 @@ == Changelog == += 1.4.0 = + +_Release date: 2026-08-17_ + +**Changed** + +* Premium package URLs use short-lived download tokens (Core 1.9.0+ `POST /me/download-token`): the updater exchanges the account key each update cycle and embeds the 15-minute token instead of the API key, so the long-lived key no longer sits in the `update_plugins` transient or server access logs. Falls back to the API-key flow automatically on older Core. (Closes the 0.5.1 audit finding.) + +**Compatibility** + +* WordPress: 4.4 - 7.1 (scan-verified: wp-compat clean from 4.4) +* PHP: 8.0 - 8.5 (scan-verified: PHPCompatibility + manual feature audit) + = 1.3.1 = _Release date: 2026-08-17_ diff --git a/includes/class-robotstxt-manager-core-client.php b/includes/class-robotstxt-manager-core-client.php index 672c66f..d969da3 100644 --- a/includes/class-robotstxt-manager-core-client.php +++ b/includes/class-robotstxt-manager-core-client.php @@ -361,6 +361,45 @@ class Robotstxt_Manager_Core_Client { return $rows; } + /** + * Exchanges the account API key for a short-lived download token + * (Core 1.9.0+ `POST /me/download-token`). + * + * @param string $slug Plugin slug the token may download. + * + * @return string Token string, or '' when unavailable (older Core, no + * key, inactive subscription, or transport error — + * callers fall back to the API-key flow). + */ + public function exchange_download_token( string $slug ): string { + if ( ! $this->has_api_key() ) { + return ''; + } + + $response = wp_remote_post( + $this->store_url . '/wp-json/' . self::REST_NAMESPACE . '/me/download-token', + array( + 'headers' => array( + 'Authorization' => 'Bearer ' . $this->api_key, + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ), + 'body' => (string) wp_json_encode( array( 'slug' => $slug ) ), + 'timeout' => self::TIMEOUT, + ) + ); + + if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { + return ''; + } + + $data = json_decode( wp_remote_retrieve_body( $response ), true ); + + $token = is_array( $data ) ? ( $data['token'] ?? '' ) : ''; + + return is_string( $token ) ? $token : ''; + } + /** * Clears the cached subscriptions response (key change, manual refresh). * diff --git a/includes/class-robotstxt-manager-updater.php b/includes/class-robotstxt-manager-updater.php index 50cd2fe..ae2ba11 100644 --- a/includes/class-robotstxt-manager-updater.php +++ b/includes/class-robotstxt-manager-updater.php @@ -300,11 +300,20 @@ class Robotstxt_Manager_Updater { ); if ( 'premium' === $type ) { - $api_key_raw = get_option( 'robotstxt_manager_api_key', '' ); - $api_key = is_string( $api_key_raw ) ? Robotstxt_Manager_Encryption::decrypt( $api_key_raw ) : ''; + // Preferred: a short-lived download token (Core 1.9.0+) — keeps the + // long-lived API key out of the update transient and access logs. + $token = $client->exchange_download_token( $slug ); - if ( '' !== $api_key ) { - $args['api_key'] = $api_key; + if ( '' !== $token ) { + $args['token'] = $token; + } else { + // Fallback (older Core): the API key itself. + $api_key_raw = get_option( 'robotstxt_manager_api_key', '' ); + $api_key = is_string( $api_key_raw ) ? Robotstxt_Manager_Encryption::decrypt( $api_key_raw ) : ''; + + if ( '' !== $api_key ) { + $args['api_key'] = $api_key; + } } } diff --git a/readme.txt b/readme.txt index a87fd6b..f83cd64 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: javiercasares, robotstxt Tags: dashboard, catalog, updates, subscriptions, management Requires at least: 4.4 Tested up to: 7.1 -Stable tag: 1.3.1 +Stable tag: 1.4.0 Requires PHP: 8.0 -Version: 1.3.1 +Version: 1.4.0 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.txt @@ -92,6 +92,12 @@ Encrypted at rest using AES-256-CBC with a key derived from your site's WordPres == Changelog == += 1.4.0 = + +_Release date: 2026-08-17_ + +* Premium update URLs carry a short-lived download token (Core 1.9.0+) instead of the API key; automatic fallback on older Core. + = 1.3.1 = _Release date: 2026-08-17_ diff --git a/robotstxt-manager.php b/robotstxt-manager.php index d3e8a99..36e9340 100644 --- a/robotstxt-manager.php +++ b/robotstxt-manager.php @@ -3,7 +3,7 @@ * Plugin Name: Manager (by ROBOTSTXT) * Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-manager * Description: Client-side dashboard for the ROBOTSTXT plugin ecosystem. Lists the catalog from a remote Plugins Core install, resolves local install/update state, and installs, activates, and updates plugins directly from the store. - * Version: 1.3.1 + * Version: 1.4.0 * Requires at least: 4.4 * Requires PHP: 8.0 * Author: ROBOTSTXT @@ -21,7 +21,7 @@ if ( ! defined( 'ABSPATH' ) ) { } /** Plugin version. */ -define( 'ROBOTSTXT_MANAGER_VERSION', '1.3.1' ); +define( 'ROBOTSTXT_MANAGER_VERSION', '1.4.0' ); /** Absolute path to the plugin directory, with trailing slash. */ define( 'ROBOTSTXT_MANAGER_DIR', plugin_dir_path( __FILE__ ) );