Plugin Directory

Changeset 3763844


Ignore:
Timestamp:
10/25/2026 12:38:56 AM (16 hours ago)
Author:
ofirs1988
Message:

Update to version 1.2.2 from GitHub

Location:
gen-wave
Files:
18 edited
1 copied

Legend:

Unmodified
Added
Removed
  • gen-wave/tags/1.2.2/gen-wave.php

    r3724835 r3763844  
    33 * Plugin Name: Genwave - AI Agent
    44 * Description: Run the WordPress site you already have by describing what you need: pages, WooCommerce, plugins, SEO and error fixes. Genwave Agent shows you the plan, you approve it, and it does the work.
    5  * Version: 1.2.1
     5 * Version: 1.2.2
    66 * Author: Genwave.ai
    77 * Author URI: https://genwave.ai
    … …  
    2929}
    3030
    31 define( 'GEN_WAVE_VERSION', '1.2.1' );
     31define( 'GEN_WAVE_VERSION', '1.2.2' );
    3232
    3333define( 'GEN_WAVE__FILE__', __FILE__ );
  • gen-wave/tags/1.2.2/languages/gen-wave.pot

    r3724835 r3763844  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Genwave - AI Agent 1.2.0\n"
     5"Project-Id-Version: Genwave - AI Agent 1.2.1\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/gen-wave\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    … …  
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-10-02T15:04:44+02:00\n"
     12"POT-Creation-Date: 2026-10-25T02:28:21+02:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.12.0\n"
    … …  
    5353msgstr ""
    5454
    55 #: src/AjaxManager.php:983
     55#: src/AjaxManager.php:980
    5656#: src/Handlers/GenerationHandler.php:351
    5757#: src/Handlers/PollingHandler.php:41
    … …  
    6363
    6464#: src/Controllers/VerifyLoginController.php:26
    65 #: src/Core/AgentAuth.php:44
     65#: src/Core/AgentAuth.php:66
    6666msgid "Enter your license key first."
    6767msgstr ""
    6868
    69 #: src/Core/AgentAuth.php:64
     69#: src/Core/AgentAuth.php:90
    7070#: src/Core/PanelConnect.php:157
    7171msgid "Connection failed."
    7272msgstr ""
    7373
    74 #: src/Core/AgentAuth.php:81
     74#: src/Core/AgentAuth.php:107
    7575msgid "Your site is connected."
    7676msgstr ""
    7777
    78 #: src/Core/AgentAuth.php:107
     78#: src/Core/AgentAuth.php:135
    7979msgid "Your site has been disconnected."
    8080msgstr ""
    8181
    82 #: src/Core/AgentAuth.php:129
     82#: src/Core/AgentAuth.php:157
    8383msgid "Genwave needs to reconnect your site to keep working securely."
    8484msgstr ""
    8585
    86 #: src/Core/AgentAuth.php:130
     86#: src/Core/AgentAuth.php:158
    8787msgid "Reconnect now"
    8888msgstr ""
    8989
    90 #: src/Core/AiRestApi.php:104
    91 #: src/Core/AiRestApi.php:121
     90#: src/Core/AiRestApi.php:125
     91#: src/Core/AiRestApi.php:142
    9292msgid "You do not have permission to access this endpoint"
    9393msgstr ""
    9494
    95 #: src/Core/AiRestApi.php:231
     95#: src/Core/AiRestApi.php:252
    9696msgid "Response data saved successfully"
    9797msgstr ""
    9898
    99 #: src/Core/AiRestApi.php:233
     99#: src/Core/AiRestApi.php:254
    100100msgid "Invalid JSON in response"
    101101msgstr ""
    102102
    103 #: src/Core/AiRestApi.php:236
     103#: src/Core/AiRestApi.php:257
    104104msgid "Missing response key in data"
    105105msgstr ""
    106106
    107 #: src/Core/AiRestApi.php:240
     107#: src/Core/AiRestApi.php:261
    108108msgid "No data received"
    109109msgstr ""
    110110
    111 #: src/Core/AiRestApi.php:456
     111#: src/Core/AiRestApi.php:480
     112msgid "This site is not connecting right now. Connect it from its Genwave settings."
     113msgstr ""
     114
     115#: src/Core/AiRestApi.php:487
    112116msgid "Plugin is not configured with a license key"
    113117msgstr ""
    114118
    115 #: src/Core/AiRestApi.php:462
    116 #: src/Core/AiRestApi.php:482
     119#: src/Core/AiRestApi.php:493
    117120msgid "Genwave plugin is active"
    118121msgstr ""
    119122
    120 #: src/Core/AiRestApi.php:473
    121 msgid "Verification token is required"
     123#: src/Core/AiRestApi.php:505
     124msgid "A verification challenge is required"
    122125msgstr ""
    123126
    … …  
    307310msgstr ""
    308311
     312#: src/Handlers/ProductHandler.php:254
     313msgid "You do not have permission to read this post."
     314msgstr ""
     315
    309316#: src/MetaBox.php:33
    310317#: src/MetaBox.php:44
  • gen-wave/tags/1.2.2/readme.txt

    r3724835 r3763844  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 1.2.1
     7Stable tag: 1.2.2
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    155155
    156156== Changelog ==
     157
     158= 1.2.2 =
     159* Security hardening for the REST API and site verification.
    157160
    158161= 1.2.1 =
  • gen-wave/tags/1.2.2/src/AjaxManager.php

    r3714852 r3763844  
    5151        add_action('wp_ajax_genwave_get_all_products', [$this->productHandler, 'getAllProducts']);
    5252        add_action('wp_ajax_genwave_get_all_posts', [$this->productHandler, 'getAllPosts']);
     53        // Logged-in only (1.2.2): the handler checks edit_post for the post asked for.
    5354        add_action('wp_ajax_genwave_get_post_data', [$this->productHandler, 'handle_get_post_data']);
    54         add_action('wp_ajax_nopriv_genwave_get_post_data', [$this->productHandler, 'handle_get_post_data']);
    5555
    5656        add_action('wp_ajax_genwave_refresh_credits', [$this, 'handle_refresh_credits']);
    … …  
    6565        add_action('wp_ajax_genwave_install_plugin', [$this->pluginsHandler, 'handle_install_plugin']);
    6666
    67         // Polling handlers - delegated to PollingHandler
    68         add_action('wp_ajax_genwave_polling_results', [$this->pollingHandler, 'handle_ai_polling_results']);
    69         add_action('wp_ajax_nopriv_genwave_polling_results', [$this->pollingHandler, 'handle_ai_polling_results']);
    70         add_action('wp_ajax_genwave_poll_proxy', [$this->pollingHandler, 'handle_ai_poll_proxy']);
    71         add_action('wp_ajax_nopriv_genwave_poll_proxy', [$this->pollingHandler, 'handle_ai_poll_proxy']);
     67        // REMOVED (1.2.2): genwave_polling_results and genwave_poll_proxy (also on
     68        // nopriv). Only the unused streaming client called them.
    7269        // REMOVED (F11): the genwave_update_credit_balance / _token_balance actions
    7370        // and their handler are gone. Nothing called them (no JS caller anywhere),
  • gen-wave/tags/1.2.2/src/Core/AgentAuth.php

    r3642637 r3763844  
    2828    }
    2929
     30    /**
     31     * The verify-domain challenge is answered only while this site is talking to
     32     * the panel (connect, disconnect): a short window opened before the request
     33     * and closed after it. Five minutes covers a slow panel; it closes itself.
     34     */
     35    private const CHALLENGE_WINDOW = 'genwave_challenge_open';
     36
     37    public static function openChallenge(): void
     38    {
     39        set_transient(self::CHALLENGE_WINDOW, 1, 5 * MINUTE_IN_SECONDS);
     40    }
     41
     42    public static function closeChallenge(): void
     43    {
     44        delete_transient(self::CHALLENGE_WINDOW);
     45    }
     46
     47    public static function challengeOpen(): bool
     48    {
     49        return (bool) get_transient(self::CHALLENGE_WINDOW);
     50    }
     51
    3052    /** True once the site has completed the signed connect. */
    3153    public static function isConnected(): bool
    … …  
    4567        }
    4668
     69        // The panel calls back to verify-domain during this request: answer it now, and only now.
     70        self::openChallenge();
    4771        $response = wp_remote_post(self::panelUrl() . '/api/plugin/connect', [
    4872            'headers' => ['Content-Type' => 'application/json', 'Accept' => 'application/json'],
    … …  
    5680        ]);
    5781
     82        self::closeChallenge();
     83
    5884        if (is_wp_error($response)) {
    5985            return ['success' => false, 'message' => $response->get_error_message()];
    … …  
    93119
    94120        if (!empty($licenseKey)) {
     121            self::openChallenge();
    95122            wp_remote_post(self::panelUrl() . '/api/plugin/disconnect', [
    96123                'headers' => ['Content-Type' => 'application/json', 'Accept' => 'application/json'],
    … …  
    102129                'sslverify' => ! preg_match('#(localhost|127\.0\.0\.1|\.local)#', self::panelUrl()),
    103130            ]);
     131            self::closeChallenge();
    104132        }
    105133
  • gen-wave/tags/1.2.2/src/Core/AiRestApi.php

    r3714852 r3763844  
    5050     * runs its own permission_callback afterwards, so nothing is actually loosened.
    5151     *
     52     * Two things are never cleared (1.2.2):
     53     *  - core's own cookie check (`rest_cookie_invalid_nonce`): with a wrong nonce
     54     *    WordPress keeps the cookie user logged in and only this error stops the
     55     *    request, so clearing it turned any cookie request into a CSRF;
     56     *  - anything that is not our route. The route is the one WordPress parsed
     57     *    (`rest_route`), not a substring of the raw URI: a query string such as
     58     *    `/wp-json/wp/v2/users?x=/wp-json/gen-wave/v1/` used to count as ours.
     59     *
    5260     * @param mixed $result null (no auth attempted), true (allowed) or WP_Error/false (denied).
    5361     * @return mixed
    … …  
    6068        }
    6169
     70        // Core's cookie authentication said no: that answer stands.
     71        if (is_wp_error($result) && in_array('rest_cookie_invalid_nonce', $result->get_error_codes(), true)) {
     72            return $result;
     73        }
     74
     75        return $this->is_own_route($this->current_rest_route()) ? true : $result;
     76    }
     77
     78    /** The REST route WordPress is serving (e.g. "/gen-wave/v1/verify-domain"), or ''. */
     79    private function current_rest_route(): string
     80    {
     81        if (isset($GLOBALS['wp']) && is_object($GLOBALS['wp']) && !empty($GLOBALS['wp']->query_vars['rest_route'])) {
     82            return (string) $GLOBALS['wp']->query_vars['rest_route'];
     83        }
    6284        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only route match, no state change
    63         $uri = isset($_SERVER['REQUEST_URI']) ? wp_unslash($_SERVER['REQUEST_URI']) : '';
    64         if (!is_string($uri) || $uri === '') {
    65             return $result;
    66         }
    67 
    68         $prefix = rest_get_url_prefix(); // usually "wp-json"
    69         $ns = $this->namespace;          // "gen-wave/v1"
    70         $ours =
    71             strpos($uri, '/' . $prefix . '/' . $ns . '/') !== false ||   // pretty permalinks
    72             strpos($uri, 'rest_route=/' . $ns . '/') !== false ||        // plain permalinks
    73             strpos($uri, 'rest_route=' . rawurlencode('/' . $ns . '/')) !== false; // encoded
    74 
    75         return $ours ? true : $result;
     85        if (isset($_GET['rest_route']) && is_string($_GET['rest_route'])) {
     86            // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     87            return sanitize_text_field(wp_unslash($_GET['rest_route']));
     88        }
     89        return '';
     90    }
     91
     92    /** Whether a REST route is in this plugin's namespace (the route itself, never its query). */
     93    public function is_own_route(string $route): bool
     94    {
     95        $route = '/' . ltrim($route, '/');
     96        return strpos($route, '/' . $this->namespace . '/') === 0;
    7697    }
    7798
    … …  
    449470
    450471        // Secure challenge-response — proves domain control + key possession.
     472        // Answered only while this site is itself connecting or disconnecting
     473        // (AgentAuth::openChallenge, 1.2.2): otherwise anyone holding the license
     474        // key could have the panel "connect" this site for them and receive its
     475        // signing key.
    451476        if (!empty($challenge)) {
     477            if (!AgentAuth::challengeOpen()) {
     478                return new WP_REST_Response([
     479                    'success' => false,
     480                    'message' => __('This site is not connecting right now. Connect it from its Genwave settings.', 'gen-wave')
     481                ], 403);
     482            }
    452483            $licenseKey = Config::get('license_key');
    453484            if (empty($licenseKey)) {
    … …  
    467498        }
    468499
    469         // Legacy token echo (unchanged).
    470         if (empty($token)) {
    471             return new WP_REST_Response([
    472                 'success' => false,
    473                 'message' => __('Verification token is required', 'gen-wave')
    474             ], 400);
    475         }
    476 
    477         // NOTE (F10): the exact plugin version is intentionally NOT included — this
    478         // endpoint is public (permission_callback __return_true), and leaking the
    479         // precise version lets an unauthenticated caller fingerprint the site.
     500        // The legacy token echo (retired account.genwave.ai flow) is gone (1.2.2): it
     501        // repeated any token back, so anyone could "verify" any site with it.
     502        unset($token);
    480503        return new WP_REST_Response([
    481             'success' => true,
    482             'message' => __('Genwave plugin is active', 'gen-wave'),
    483             'token' => $token,
    484             'domain' => get_site_url(),
    485             'verified_at' => current_time('mysql')
    486         ], 200);
     504            'success' => false,
     505            'message' => __('A verification challenge is required', 'gen-wave')
     506        ], 400);
    487507    }
    488508}
  • gen-wave/tags/1.2.2/src/Handlers/GenerationHandler.php

    r3642637 r3763844  
    249249            if (defined('WP_DEBUG') && WP_DEBUG) {
    250250                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug mode only
    251                 error_log("Genwave: License Key: " . $license_key);
     251                error_log("Genwave: Has License Key: " . ($license_key ? 'YES' : 'NO'));
    252252                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug mode only
    253253                error_log("Genwave: Has Token: " . ($encrypted_token ? 'YES' : 'NO'));
  • gen-wave/tags/1.2.2/src/Handlers/ProductHandler.php

    r3642637 r3763844  
    250250            }
    251251
     252            // A nonce proves where the request came from, not who may read the post (1.2.2).
     253            if (!current_user_can('edit_post', $post_id)) {
     254                wp_send_json_error(__('You do not have permission to read this post.', 'gen-wave'), 403);
     255                return;
     256            }
     257
    252258            // Get the post
    253259            $post = get_post($post_id);
  • gen-wave/tags/1.2.2/vendor/composer/installed.php

    r3724835 r3763844  
    44        'pretty_version' => 'dev-master',
    55        'version' => 'dev-master',
    6         'reference' => '9f21dd8efa8e041d7d601e31c3813bd25a8ed4e6',
     6        'reference' => '8b75da2c8b8dc14d65d95387e3657e296bb2337f',
    77        'type' => 'wordpress-plugin',
    88        'install_path' => __DIR__ . '/../../',
    … …  
    1414            'pretty_version' => 'dev-master',
    1515            'version' => 'dev-master',
    16             'reference' => '9f21dd8efa8e041d7d601e31c3813bd25a8ed4e6',
     16            'reference' => '8b75da2c8b8dc14d65d95387e3657e296bb2337f',
    1717            'type' => 'wordpress-plugin',
    1818            'install_path' => __DIR__ . '/../../',
  • gen-wave/trunk/gen-wave.php

    r3724835 r3763844  
    33 * Plugin Name: Genwave - AI Agent
    44 * Description: Run the WordPress site you already have by describing what you need: pages, WooCommerce, plugins, SEO and error fixes. Genwave Agent shows you the plan, you approve it, and it does the work.
    5  * Version: 1.2.1
     5 * Version: 1.2.2
    66 * Author: Genwave.ai
    77 * Author URI: https://genwave.ai
    … …  
    2929}
    3030
    31 define( 'GEN_WAVE_VERSION', '1.2.1' );
     31define( 'GEN_WAVE_VERSION', '1.2.2' );
    3232
    3333define( 'GEN_WAVE__FILE__', __FILE__ );
  • gen-wave/trunk/languages/gen-wave.pot

    r3724835 r3763844  
    33msgid ""
    44msgstr ""
    5 "Project-Id-Version: Genwave - AI Agent 1.2.0\n"
     5"Project-Id-Version: Genwave - AI Agent 1.2.1\n"
    66"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/gen-wave\n"
    77"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
    … …  
    1010"Content-Type: text/plain; charset=UTF-8\n"
    1111"Content-Transfer-Encoding: 8bit\n"
    12 "POT-Creation-Date: 2026-10-02T15:04:44+02:00\n"
     12"POT-Creation-Date: 2026-10-25T02:28:21+02:00\n"
    1313"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
    1414"X-Generator: WP-CLI 2.12.0\n"
    … …  
    5353msgstr ""
    5454
    55 #: src/AjaxManager.php:983
     55#: src/AjaxManager.php:980
    5656#: src/Handlers/GenerationHandler.php:351
    5757#: src/Handlers/PollingHandler.php:41
    … …  
    6363
    6464#: src/Controllers/VerifyLoginController.php:26
    65 #: src/Core/AgentAuth.php:44
     65#: src/Core/AgentAuth.php:66
    6666msgid "Enter your license key first."
    6767msgstr ""
    6868
    69 #: src/Core/AgentAuth.php:64
     69#: src/Core/AgentAuth.php:90
    7070#: src/Core/PanelConnect.php:157
    7171msgid "Connection failed."
    7272msgstr ""
    7373
    74 #: src/Core/AgentAuth.php:81
     74#: src/Core/AgentAuth.php:107
    7575msgid "Your site is connected."
    7676msgstr ""
    7777
    78 #: src/Core/AgentAuth.php:107
     78#: src/Core/AgentAuth.php:135
    7979msgid "Your site has been disconnected."
    8080msgstr ""
    8181
    82 #: src/Core/AgentAuth.php:129
     82#: src/Core/AgentAuth.php:157
    8383msgid "Genwave needs to reconnect your site to keep working securely."
    8484msgstr ""
    8585
    86 #: src/Core/AgentAuth.php:130
     86#: src/Core/AgentAuth.php:158
    8787msgid "Reconnect now"
    8888msgstr ""
    8989
    90 #: src/Core/AiRestApi.php:104
    91 #: src/Core/AiRestApi.php:121
     90#: src/Core/AiRestApi.php:125
     91#: src/Core/AiRestApi.php:142
    9292msgid "You do not have permission to access this endpoint"
    9393msgstr ""
    9494
    95 #: src/Core/AiRestApi.php:231
     95#: src/Core/AiRestApi.php:252
    9696msgid "Response data saved successfully"
    9797msgstr ""
    9898
    99 #: src/Core/AiRestApi.php:233
     99#: src/Core/AiRestApi.php:254
    100100msgid "Invalid JSON in response"
    101101msgstr ""
    102102
    103 #: src/Core/AiRestApi.php:236
     103#: src/Core/AiRestApi.php:257
    104104msgid "Missing response key in data"
    105105msgstr ""
    106106
    107 #: src/Core/AiRestApi.php:240
     107#: src/Core/AiRestApi.php:261
    108108msgid "No data received"
    109109msgstr ""
    110110
    111 #: src/Core/AiRestApi.php:456
     111#: src/Core/AiRestApi.php:480
     112msgid "This site is not connecting right now. Connect it from its Genwave settings."
     113msgstr ""
     114
     115#: src/Core/AiRestApi.php:487
    112116msgid "Plugin is not configured with a license key"
    113117msgstr ""
    114118
    115 #: src/Core/AiRestApi.php:462
    116 #: src/Core/AiRestApi.php:482
     119#: src/Core/AiRestApi.php:493
    117120msgid "Genwave plugin is active"
    118121msgstr ""
    119122
    120 #: src/Core/AiRestApi.php:473
    121 msgid "Verification token is required"
     123#: src/Core/AiRestApi.php:505
     124msgid "A verification challenge is required"
    122125msgstr ""
    123126
    … …  
    307310msgstr ""
    308311
     312#: src/Handlers/ProductHandler.php:254
     313msgid "You do not have permission to read this post."
     314msgstr ""
     315
    309316#: src/MetaBox.php:33
    310317#: src/MetaBox.php:44
  • gen-wave/trunk/readme.txt

    r3724835 r3763844  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 1.2.1
     7Stable tag: 1.2.2
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    155155
    156156== Changelog ==
     157
     158= 1.2.2 =
     159* Security hardening for the REST API and site verification.
    157160
    158161= 1.2.1 =
  • gen-wave/trunk/src/AjaxManager.php

    r3714852 r3763844  
    5151        add_action('wp_ajax_genwave_get_all_products', [$this->productHandler, 'getAllProducts']);
    5252        add_action('wp_ajax_genwave_get_all_posts', [$this->productHandler, 'getAllPosts']);
     53        // Logged-in only (1.2.2): the handler checks edit_post for the post asked for.
    5354        add_action('wp_ajax_genwave_get_post_data', [$this->productHandler, 'handle_get_post_data']);
    54         add_action('wp_ajax_nopriv_genwave_get_post_data', [$this->productHandler, 'handle_get_post_data']);
    5555
    5656        add_action('wp_ajax_genwave_refresh_credits', [$this, 'handle_refresh_credits']);
    … …  
    6565        add_action('wp_ajax_genwave_install_plugin', [$this->pluginsHandler, 'handle_install_plugin']);
    6666
    67         // Polling handlers - delegated to PollingHandler
    68         add_action('wp_ajax_genwave_polling_results', [$this->pollingHandler, 'handle_ai_polling_results']);
    69         add_action('wp_ajax_nopriv_genwave_polling_results', [$this->pollingHandler, 'handle_ai_polling_results']);
    70         add_action('wp_ajax_genwave_poll_proxy', [$this->pollingHandler, 'handle_ai_poll_proxy']);
    71         add_action('wp_ajax_nopriv_genwave_poll_proxy', [$this->pollingHandler, 'handle_ai_poll_proxy']);
     67        // REMOVED (1.2.2): genwave_polling_results and genwave_poll_proxy (also on
     68        // nopriv). Only the unused streaming client called them.
    7269        // REMOVED (F11): the genwave_update_credit_balance / _token_balance actions
    7370        // and their handler are gone. Nothing called them (no JS caller anywhere),
  • gen-wave/trunk/src/Core/AgentAuth.php

    r3642637 r3763844  
    2828    }
    2929
     30    /**
     31     * The verify-domain challenge is answered only while this site is talking to
     32     * the panel (connect, disconnect): a short window opened before the request
     33     * and closed after it. Five minutes covers a slow panel; it closes itself.
     34     */
     35    private const CHALLENGE_WINDOW = 'genwave_challenge_open';
     36
     37    public static function openChallenge(): void
     38    {
     39        set_transient(self::CHALLENGE_WINDOW, 1, 5 * MINUTE_IN_SECONDS);
     40    }
     41
     42    public static function closeChallenge(): void
     43    {
     44        delete_transient(self::CHALLENGE_WINDOW);
     45    }
     46
     47    public static function challengeOpen(): bool
     48    {
     49        return (bool) get_transient(self::CHALLENGE_WINDOW);
     50    }
     51
    3052    /** True once the site has completed the signed connect. */
    3153    public static function isConnected(): bool
    … …  
    4567        }
    4668
     69        // The panel calls back to verify-domain during this request: answer it now, and only now.
     70        self::openChallenge();
    4771        $response = wp_remote_post(self::panelUrl() . '/api/plugin/connect', [
    4872            'headers' => ['Content-Type' => 'application/json', 'Accept' => 'application/json'],
    … …  
    5680        ]);
    5781
     82        self::closeChallenge();
     83
    5884        if (is_wp_error($response)) {
    5985            return ['success' => false, 'message' => $response->get_error_message()];
    … …  
    93119
    94120        if (!empty($licenseKey)) {
     121            self::openChallenge();
    95122            wp_remote_post(self::panelUrl() . '/api/plugin/disconnect', [
    96123                'headers' => ['Content-Type' => 'application/json', 'Accept' => 'application/json'],
    … …  
    102129                'sslverify' => ! preg_match('#(localhost|127\.0\.0\.1|\.local)#', self::panelUrl()),
    103130            ]);
     131            self::closeChallenge();
    104132        }
    105133
  • gen-wave/trunk/src/Core/AiRestApi.php

    r3714852 r3763844  
    5050     * runs its own permission_callback afterwards, so nothing is actually loosened.
    5151     *
     52     * Two things are never cleared (1.2.2):
     53     *  - core's own cookie check (`rest_cookie_invalid_nonce`): with a wrong nonce
     54     *    WordPress keeps the cookie user logged in and only this error stops the
     55     *    request, so clearing it turned any cookie request into a CSRF;
     56     *  - anything that is not our route. The route is the one WordPress parsed
     57     *    (`rest_route`), not a substring of the raw URI: a query string such as
     58     *    `/wp-json/wp/v2/users?x=/wp-json/gen-wave/v1/` used to count as ours.
     59     *
    5260     * @param mixed $result null (no auth attempted), true (allowed) or WP_Error/false (denied).
    5361     * @return mixed
    … …  
    6068        }
    6169
     70        // Core's cookie authentication said no: that answer stands.
     71        if (is_wp_error($result) && in_array('rest_cookie_invalid_nonce', $result->get_error_codes(), true)) {
     72            return $result;
     73        }
     74
     75        return $this->is_own_route($this->current_rest_route()) ? true : $result;
     76    }
     77
     78    /** The REST route WordPress is serving (e.g. "/gen-wave/v1/verify-domain"), or ''. */
     79    private function current_rest_route(): string
     80    {
     81        if (isset($GLOBALS['wp']) && is_object($GLOBALS['wp']) && !empty($GLOBALS['wp']->query_vars['rest_route'])) {
     82            return (string) $GLOBALS['wp']->query_vars['rest_route'];
     83        }
    6284        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only route match, no state change
    63         $uri = isset($_SERVER['REQUEST_URI']) ? wp_unslash($_SERVER['REQUEST_URI']) : '';
    64         if (!is_string($uri) || $uri === '') {
    65             return $result;
    66         }
    67 
    68         $prefix = rest_get_url_prefix(); // usually "wp-json"
    69         $ns = $this->namespace;          // "gen-wave/v1"
    70         $ours =
    71             strpos($uri, '/' . $prefix . '/' . $ns . '/') !== false ||   // pretty permalinks
    72             strpos($uri, 'rest_route=/' . $ns . '/') !== false ||        // plain permalinks
    73             strpos($uri, 'rest_route=' . rawurlencode('/' . $ns . '/')) !== false; // encoded
    74 
    75         return $ours ? true : $result;
     85        if (isset($_GET['rest_route']) && is_string($_GET['rest_route'])) {
     86            // phpcs:ignore WordPress.Security.NonceVerification.Recommended
     87            return sanitize_text_field(wp_unslash($_GET['rest_route']));
     88        }
     89        return '';
     90    }
     91
     92    /** Whether a REST route is in this plugin's namespace (the route itself, never its query). */
     93    public function is_own_route(string $route): bool
     94    {
     95        $route = '/' . ltrim($route, '/');
     96        return strpos($route, '/' . $this->namespace . '/') === 0;
    7697    }
    7798
    … …  
    449470
    450471        // Secure challenge-response — proves domain control + key possession.
     472        // Answered only while this site is itself connecting or disconnecting
     473        // (AgentAuth::openChallenge, 1.2.2): otherwise anyone holding the license
     474        // key could have the panel "connect" this site for them and receive its
     475        // signing key.
    451476        if (!empty($challenge)) {
     477            if (!AgentAuth::challengeOpen()) {
     478                return new WP_REST_Response([
     479                    'success' => false,
     480                    'message' => __('This site is not connecting right now. Connect it from its Genwave settings.', 'gen-wave')
     481                ], 403);
     482            }
    452483            $licenseKey = Config::get('license_key');
    453484            if (empty($licenseKey)) {
    … …  
    467498        }
    468499
    469         // Legacy token echo (unchanged).
    470         if (empty($token)) {
    471             return new WP_REST_Response([
    472                 'success' => false,
    473                 'message' => __('Verification token is required', 'gen-wave')
    474             ], 400);
    475         }
    476 
    477         // NOTE (F10): the exact plugin version is intentionally NOT included — this
    478         // endpoint is public (permission_callback __return_true), and leaking the
    479         // precise version lets an unauthenticated caller fingerprint the site.
     500        // The legacy token echo (retired account.genwave.ai flow) is gone (1.2.2): it
     501        // repeated any token back, so anyone could "verify" any site with it.
     502        unset($token);
    480503        return new WP_REST_Response([
    481             'success' => true,
    482             'message' => __('Genwave plugin is active', 'gen-wave'),
    483             'token' => $token,
    484             'domain' => get_site_url(),
    485             'verified_at' => current_time('mysql')
    486         ], 200);
     504            'success' => false,
     505            'message' => __('A verification challenge is required', 'gen-wave')
     506        ], 400);
    487507    }
    488508}
  • gen-wave/trunk/src/Handlers/GenerationHandler.php

    r3642637 r3763844  
    249249            if (defined('WP_DEBUG') && WP_DEBUG) {
    250250                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug mode only
    251                 error_log("Genwave: License Key: " . $license_key);
     251                error_log("Genwave: Has License Key: " . ($license_key ? 'YES' : 'NO'));
    252252                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug mode only
    253253                error_log("Genwave: Has Token: " . ($encrypted_token ? 'YES' : 'NO'));
  • gen-wave/trunk/src/Handlers/ProductHandler.php

    r3642637 r3763844  
    250250            }
    251251
     252            // A nonce proves where the request came from, not who may read the post (1.2.2).
     253            if (!current_user_can('edit_post', $post_id)) {
     254                wp_send_json_error(__('You do not have permission to read this post.', 'gen-wave'), 403);
     255                return;
     256            }
     257
    252258            // Get the post
    253259            $post = get_post($post_id);
  • gen-wave/trunk/vendor/composer/installed.php

    r3724835 r3763844  
    44        'pretty_version' => 'dev-master',
    55        'version' => 'dev-master',
    6         'reference' => '9f21dd8efa8e041d7d601e31c3813bd25a8ed4e6',
     6        'reference' => '8b75da2c8b8dc14d65d95387e3657e296bb2337f',
    77        'type' => 'wordpress-plugin',
    88        'install_path' => __DIR__ . '/../../',
    … …  
    1414            'pretty_version' => 'dev-master',
    1515            'version' => 'dev-master',
    16             'reference' => '9f21dd8efa8e041d7d601e31c3813bd25a8ed4e6',
     16            'reference' => '8b75da2c8b8dc14d65d95387e3657e296bb2337f',
    1717            'type' => 'wordpress-plugin',
    1818            'install_path' => __DIR__ . '/../../',
Note: See TracChangeset for help on using the changeset viewer.