WP Manifestindependent plugin directory
manifest / ecommerce / wllp-point-based-level

WPLoyalty - Level Options WP Registry grade B. Medium-severity findings · 2 findingsWP RegistryMedium-severity findings · 2 findingsOpen the reportB

The add-on helps you customize levels or tiers based on specific options.

★ 0stars
708release downloads
≈190active sites
stablelatest · 11mo ago

Install

Download wllp-point-based-level.zip From the stable release on GitHub, Nov 26, 2025

Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/wployalty/wllp-point-based-level/releases/download/stable/wllp-point-based-level.zip
Updates itself. It ships its own updater (Plugin Update Checker), so new versions appear under Dashboard → Updates.

Releases

4 releases. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
stable latest
Nov 26, 2025 11mo ago
wllp-point-based-level.zip
298
Nov 26, 2025 11mo ago
wllp-point-based-level-v1.0.2.zip
210
Apr 17, 2025 1 year ago
wllp-point-based-level-v1.0.1.zip
191
Mar 24, 2025 1 year ago
wllp-point-based-level-v1.0.0.zip
9

Active-site estimate ≈190 comes from the median of recent superseded releases. Method.

B grade

Security

WPLoyalty - Level Options 1.0.2 · audited by WP Registry

Medium-severity findings.

2 medium
Audited release
1.0.2
Findings
2
Worst severity
medium
Content hash
2404ff03411522279951956a…

Findings

  • medium Settings AJAX is nonce-only: any wp-admin user can rewrite store-wide loyalty levels

    missing_capability

    wp_ajax_wllp_save_settings is registered from Route::loadAdminHooks and implemented by Controller::saveSettings. After wp_verify_nonce($input->post('nonce'), WLLP_PLUGIN_SLUG) the handler parse_str()s the posted form blob and update_option('wllp_settings_data', $settings) with no current_user_can() and no allowlist of keys/values. The settings menu is only registered when current_user_can('manage_options'), so shop_manager/editor/author/contributor are not supposed to change this add-on. They still can: loadAssets() is hooked to admin_enqueue_scripts and was meant to localize the nonce only on page=wllp-point-based-level, but the condition is `empty($_GET['page'] || $_GET['page'] != WLLP_PLUGIN_SLUG)` which is never true once ?page= is present, so wllp_localize_data.nonce is printed on every WooCommerce admin screen (admin.php?page=wc-admin, wc-orders, …) and on profile.php?page=… . A shop_manager or contributor copies that nonce and POSTs action=wllp_save_settings&data=levels_from_which_point_based=from_current_balance (or from_order_total, which is commented out of the UI select but accepted because the value is not allowlisted). Observable: option wllp_settings_data changes; every customer's WPLoyalty level is recomputed from the new source; from_order_total additionally turns on getOrderTotal() SQL (separate finding). Default WooCommerce customer/subscriber roles are redirected out of wp-admin before enqueue, so they do not get this nonce unless woocommerce_prevent_admin_access is disabled. Nonce is not a capability check.

    App/Controllers/Controller.php:93-128,176-218

    Recommendation

    Require current_user_can('manage_options') (the same gate as addMenu) immediately after the nonce check, and fail closed. Fix the page-slug test so the nonce is only localized on this plugin's screen. Allowlist keys (levels_from_which_point_based, order_duration, grace_period_enabled, grace_period_days) and values before update_option; do not persist the raw parse_str array.

  • medium Unprepared SQL interpolates customer email when levels are based on order total

    sql_injection

    Actions::getOrderTotal() builds a SELECT SUM() string and runs $wpdb->get_var($query) with no prepare. billing_email is copied from the WPLoyalty user object (stored email, not a current request field, so wp_magic_quotes never runs) and concatenated into LIKE '$billing_email' (HPOS) or meta_value = '$billing_email' (legacy posts). WordPress is_email()/sanitize_email allow apostrophe and % in the local part. Payload a'OR'1@x.com is a valid WP email and turns the HPOS predicate into billing_email LIKE 'a' OR '1@x.com' (tautology); %@example.com is a LIKE wildcard that sums every matching order. The (int) SUM is then used as the customer's points for level assignment, so a tautology/wildcard email can inherit the store-wide order total and land on the highest VIP tier. $order_status from parent setting wlr_earning_status is also concatenated into IN(). Table names are hardcoded as wp_wc_orders / wp_posts rather than $wpdb->prefix. This method is skipped in the admin UI (from_order_total is commented out of levelBasedOnOptions) but resolvePointsBySetting still honors that string, and saveSettings (finding 1) will persist it because it does not allowlist the source. Authors left an explicit TODO that the query must be rewritten before use. Default setting is from_total_earned_points, so a stock install does not call this until the option is written.

    App/Controllers/Actions.php:168-225

    Recommendation

    Replace the raw SQL with wc_get_orders() / $wpdb->prepare using $wpdb->prefix (or the HPOS orders table helper). Compare email with = and $wpdb->esc_like only if LIKE is required. Allowlist order statuses. Do not accept levels_from_which_point_based=from_order_total until that rewrite ships, and keep the saveSettings allowlist in sync with the UI.

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.