WP Manifestindependent plugin directory
manifest / themes / mai-theme-engine

Mai Theme Engine WP Registry grade D+. High-severity findings · 3 findingsWP RegistryHigh-severity findings · 3 findingsOpen the reportD+

The required helper plugin to power Mai Theme child themes.

★ 19stars
1.13.2latest · 4 years ago

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/maithemewp/mai-theme-engine/archive/refs/heads/master.zip
Updates itself. It ships its own updater (Plugin Update Checker), so new versions appear under Dashboard → Updates.

The required plugin to power Mai Theme v1 child themes.

Releases

Newest 25 of 60 recorded releases.

Tag
Published
1.13.2 latest
Jul 15, 2022 4 years ago
Apr 27, 2022 4 years ago
Apr 25, 2022 4 years ago
Jun 25, 2021 5 years ago
Feb 13, 2021 5 years ago
Jan 4, 2021 5 years ago
Sep 3, 2020 6 years ago
Aug 21, 2020 6 years ago
Mar 12, 2020 6 years ago
Mar 11, 2020 6 years ago
Mar 6, 2020 6 years ago
Feb 3, 2020 6 years ago
Feb 3, 2020 6 years ago
Feb 3, 2020 6 years ago
Nov 13, 2019 6 years ago
May 6, 2019 7 years ago
May 6, 2019 7 years ago
May 3, 2019 7 years ago
May 2, 2019 7 years ago
May 2, 2019 7 years ago
Mar 27, 2019 7 years ago
Feb 13, 2019 7 years ago
Feb 12, 2019 7 years ago
Feb 9, 2019 7 years ago
Feb 8, 2019 7 years ago
All releases on GitHub →

These releases are tags only. The author does not attach a packaged zip, so there are no download counts to report.

D+ grade

Security

Mai Theme Engine 1.13.2 · audited by WP Registry

High-severity findings.

3 high
Audited release
1.13.2
Findings
3
Worst severity
high
Content hash
a9c0cfdf1db73bdf5c67cc5d…

Findings

  • high Stored XSS via [section]/[grid] title_wrap and grid_title_wrap tag interpolation

    xss_stored

    Mai_Section and Mai_Grid interpolate attacker-controlled shortcode attributes as HTML tag names around attacker-controlled text. title_wrap, wrapper, and grid_title_wrap are only passed through sanitize_key() (which allows 'script'), and title / grid_title through sanitize_text_field() (which does not strip JavaScript text). A Contributor or Author can publish [section title="alert(document.domain)" title_wrap="script"]x[/section] or [grid grid_title="alert(document.domain)" grid_title_wrap="script"] and the frontend emits a real <script class="heading">alert(document.domain)</script>. The same pattern exists for [section wrapper="script"] (outer element) and [grid title_wrap="script"] around entry titles. WordPress kses does not treat shortcode attributes as HTML, so unfiltered_html is not required. Visitors executing the published page run the injected script (account takeover / skimmer-class payload).

    lib/classes/class-section.php:69-71,136-137,455-458

    Recommendation

    Allowlist heading/wrapper tags (e.g. h1–h6, p, div, section, header) instead of sanitize_key(), and escape title text with esc_html(). Apply the same allowlist to grid title_wrap / grid_title_wrap in Mai_Grid::get_grid_title() and the entry-title sprintf.

  • high [grid] shortcode discloses private, draft, and non-public post types (including WooCommerce coupons/orders)

    information_disclosure

    Mai_Grid::get_posts() assigns the unsanitized shortcode `status` attribute directly to WP_Query['post_status'] and uses `content` as post_type. get_content_type() treats any registered post type (get_post_types() with no public flag) as a post query, and an explicit content_type="post" bypasses even that intersect. WP_Query without perm=readable honors explicit private/draft/pending statuses for unauthenticated viewers. An Author (or a Contributor whose post is published) can place [grid status="private,draft,pending" show="title,content,excerpt"] on a public page and leak other users' unpublished content. With WooCommerce, [grid content="shop_coupon" content_type="post" show="title"] lists published coupon codes; [grid content="shop_order" status="wc-completed,wc-processing" content_type="post" show="title,content"] can expose order titles/notes. No current_user_can() or public-status allowlist is applied.

    lib/classes/class-grid.php:268-274,383-386,1014-1022

    Recommendation

    Force post_status to public statuses (publish, and inherit for attachments) unless the current user can read_private_posts, and set 'perm' => 'readable'. Restrict `content` to publicly queryable post types / public taxonomies; ignore content_type overrides that would query shop_order, shop_coupon, or other non-public types.

  • high Ungated admin_init upgrade plus pre_update_option_genesis-settings copies raw POST into site-wide scripts/settings

    csrf

    mai_enforce_custom_genesis_settings runs on every update of the genesis-settings option. isset($_POST) is always true, so any request that also contains $_POST['genesis-settings'] replaces the incoming $new_value with that raw POST array, stripslashes header_scripts/footer_scripts, then wp_parse_args-merges into the existing option. mai_update_database_version is hooked to admin_init with no capability or nonce check; while mai_db_version < 1600 (fresh install or upgrade), any logged-in user visiting wp-admin — including Subscriber hitting profile.php or admin-ajax.php — can run genesis_update_settings() (mai_upgrade_1600 does so on default Genesis 3.2+ entry meta; 1100/1161/1400 may as well). An unauthenticated CSRF of a logged-in administrator during that window persists header_scripts/footer_scripts, which Genesis echoes unescaped on every front-end page (skimmer-class stored XSS). Subscriber-owned requests cannot rely on unfiltered_html, but Mai-specific keys that Genesis's sanitizer does not know are stored as submitted. After mai_db_version is 1600 the upgrade trigger closes, but the filter remains a confused-deputy on any later genesis-settings write that happens to share the request with attacker POST.

    lib/options.php:14-36

    Recommendation

    Do not assign $new_value from $_POST inside pre_update_option. Preserve unknown keys from $old_value without trusting request body. Gate mai_update_database_version behind current_user_can('manage_options') (or run it only on plugin upgrade hooks), and require a nonce if request data is consulted at all.

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.