Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐘
🐘 Packagist
Not in CISA KEV
HIGH severity

CVE-2026-56827

HIGH

CVE-2026-56827 is a high-severity (CVSS 8.1) vulnerability in shopper/framework. O3 Security confirms whether CVE-2026-56827 is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

Shopper: Authorization bypass in Filament bulk actions allows browse-only staff to mass-delete attributes/tags and mass-toggle visibility of brands/categories/suppliers

Published
Sep 11, 2026
Updated
Sep 11, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Sep 11, 2026 · OSV.dev, FIRST.org (EPSS)

Real-World Exposure

1 pkg affected
🐘shopper/framework

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.

Description

Summary

Five Filament groupedBulkActions blocks across the Shopper admin Livewire pages omit the ->authorize(...) permission gate, while their per-record sibling actions (and other Shopper Index pages such as Pages/Settings/Currencies.php, Pages/Reviews/Index.php, Pages/Collection/Index.php, and Pages/Discount/Index.php) correctly chain ->authorize(...). Each affected page's mount() only requires the read-only browse_* permission, so a low-privilege staff user holding only the read permission can drive the bulk endpoint via the standard Livewire callTableBulkAction flow and execute state-mutating operations they were never granted. The vulnerability is the same class as GHSA-f946-9qp6-vgch and GHSA-j328-xmgp-j4q3 (read-only permission gating a write action), just on a different surface (Filament 4 groupedBulkActions rather than top-level Livewire methods).

A staff user holding only browse_attributes can permanently delete every product attribute in the catalog (cascading break of every dependent product variant). A user holding only browse_tags can permanently delete every product tag. Users holding browse_brands, browse_categories, or browse_suppliers can flip the visibility (is_enabled) of every brand/category/supplier in bulk, sabotaging storefront catalog visibility.

CVSS 3.1: AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H = 8.1 High. CWE-285 (Improper Authorization) and CWE-862 (Missing Authorization). The attacker has low privilege (browse-only staff role), no user interaction, network reachable.

Vulnerable components (paths relative to repo root)

All references are HEAD = commit ac9a760 on master (the very commit that closed the previous wave of authorization-drift bugs from GHSA-j328-xmgp-j4q3).

1) packages/admin/src/Livewire/Pages/Attribute/Browse.php

Mount at line 36–39 requires only browse_attributes.

  • Lines 106–122: DeleteBulkAction::make() has NO ->authorize(...) chain (the surrounding per-record delete action at lines 95–104 correctly does ->authorize('delete_attributes')).
  • Lines 123–138: BulkAction::make('enabled') has NO ->authorize(...).
  • Lines 139–155: BulkAction::make('disabled') has NO ->authorize(...).

Net effect: a browse_attributes-only user can delete every row in the attributes table, and toggle is_enabled on every attribute in one request. Deleting an attribute cascades into every product variant that references it via the attribute_product pivot.

2) packages/admin/src/Livewire/Pages/Tag/Index.php

Mount at line 39 requires only browse_tags.

  • Lines 96–108: DeleteBulkAction::make() has NO ->authorize(...) chain (the per-record delete action at lines 79–94 correctly does ->authorize('delete_tags')).

Net effect: a browse_tags-only user can delete every ProductTag row.

3) packages/admin/src/Livewire/Pages/Brand/Index.php

Mount at line 37–40 requires only browse_brands.

  • Lines 97–112: BulkAction::make('enabled') has NO ->authorize(...).
  • Lines 113–129: BulkAction::make('disabled') has NO ->authorize(...).

Net effect: a browse_brands-only user can flip is_enabled on every brand. Disabling all brands removes them from the storefront catalog. The per-record edit/delete actions and the DeleteBulkAction at lines 130–148 are correctly ->authorize(...) gated — only the visibility bulk actions were missed.

4) packages/admin/src/Livewire/Pages/Category/Index.php

Mount at line 38–41 requires only browse_categories.

  • Lines 102–117: BulkAction::make('enabled') has NO ->authorize(...).
  • Lines 118–133: BulkAction::make('disabled') has NO ->authorize(...).

Net effect: a browse_categories-only user can flip is_enabled on every category. Same shape as Brand.

5) packages/admin/src/Livewire/Pages/Supplier/Index.php

Mount at line 38 requires only browse_suppliers.

  • Lines 93–108: BulkAction::make('enabled') has NO ->authorize(...).
  • Lines 109–125: BulkAction::make('disabled') has NO ->authorize(...).

Net effect: a browse_suppliers-only user can flip is_enabled on every supplier.

Reference comparison: places that ARE correctly gated

For reference, here is what the same pattern looks like in files that DID get the fix:

  • packages/admin/src/Livewire/Pages/Settings/Currencies.php lines 90–129: every BulkAction chains ->authorize('access_setting').
  • packages/admin/src/Livewire/Pages/Reviews/Index.php lines 105–119: DeleteBulkAction chains ->authorize('delete_reviews').
  • packages/admin/src/Livewire/Pages/Collection/Index.php lines 109–128: DeleteBulkAction chains ->authorize('delete_collections').
  • packages/admin/src/Livewire/Pages/Discount/Index.php lines 126–145: DeleteBulkAction chains ->authorize('delete_discounts').

The convention is established and applied elsewhere — these five files just missed it.

Proof of Concept

The attached file tests/Admin/Livewire/Pages/Brand/AuthBypassPocTest.php (added in this report) contains seven Pest tests, each acting as a browse_*-only staff user and invoking the bulk endpoint. All seven pass on master @ ac9a760:

   PASS  Tests\Admin\Livewire\Pages\Brand\AuthBypassPocTest
  ✓ it SHOPPER-2 PoC: read-only viewer can mass-DISABLE all brands via unguarded BulkAction
  ✓ it SHOPPER-2 PoC: read-only viewer can mass-ENABLE all brands via unguarded BulkAction
  ✓ it SHOPPER-2 PoC: read-only viewer can mass-DISABLE all categories via unguarded BulkAction
  ✓ it SHOPPER-2 PoC: read-only viewer can mass-DISABLE all suppliers via unguarded BulkAction
  ✓ it SHOPPER-2 PoC: read-only viewer can DELETE all attributes via unguarded DeleteBulkAction
  ✓ it SHOPPER-2 PoC: read-only viewer can mass-DISABLE all attributes via unguarded BulkAction
  ✓ it SHOPPER-2 PoC: browse_tags viewer can DELETE all product tags via unguarded DeleteBulkAction

  Tests:    7 passed (32 assertions)

Each test seeds three records, signs in a user holding only the corresponding browse_* permission, calls Livewire::test(<Page>::class)->callTableBulkAction(...), and asserts the side effect (records flipped or deleted). For example, the attribute mass-delete test:

$this->viewer = User::factory()->create();
$this->viewer->givePermissionTo('browse_attributes');
$this->actingAs($this->viewer);

Attribute::factory()->count(3)->create();
expect($this->viewer->can('delete_attributes'))->toBeFalse();

Livewire::test(AttributeBrowse::class)
    ->callTableBulkAction(\Filament\Actions\DeleteBulkAction::class, Attribute::pluck('id')->toArray())
    ->assertHasNoErrors();

expect(Attribute::count())->toBe(0);

The call uses the same callTableBulkAction helper Shopper's own test suite uses everywhere, which in turn drives the same Livewire update payload the browser would emit — so this is a faithful HTTP-level reproduction.

Suggested fix

Add ->authorize(<correct_permission>) to each of the five vulnerable groups, mirroring the pattern already used elsewhere:

 // Pages/Attribute/Browse.php
 ->groupedBulkActions([
     DeleteBulkAction::make()
+        ->authorize('delete_attributes')
         ->label(__('shopper::forms.actions.delete'))
         ->requiresConfirmation()
         ->action(function (Collection $records): void { /* ... */ }),
     BulkAction::make('enabled')
+        ->authorize('edit_attributes')
         ->label(__('shopper::forms.actions.enable'))
         ->action(function (Collection $records): void { /* ... */ }),
     BulkAction::make('disabled')
+        ->authorize('edit_attributes')
         ->label(__('shopper::forms.actions.disable'))
         ->action(function (Collection $records): void { /* ... */ }),
 ])

Apply the equivalent change to Pages/Tag/Index.php (delete_tags), Pages/Brand/Index.php (edit_brands for enable/disable), Pages/Category/Index.php (edit_categories), and Pages/Supplier/Index.php (edit_suppliers).

A regression test for each file (acting as a browse_*-only user and expecting assertHasErrors/AuthorizationException) would lock in the fix, matching the regression tests added for #514.

Resources

  • Prior advisories of the same class (read-only permission gating a write action): GHSA-f946-9qp6-vgch, GHSA-j328-xmgp-j4q3 / GHSA-vw82-3966-f9mr.
  • Same-shape fix: commit ac9a760 (PR #514). Five Filament bulk-action groups did not receive the corresponding ->authorize(...) chain.
  • CWE-285 Improper Authorization, CWE-862 Missing Authorization.

Credits

Reported by Vishal Shukla(@shukla304) using sechub.dev AI Agent

Support

If this disclosure was useful and userswould like to support continued open-source security research and responsible-disclosure work, they can sponsor at https://github.com/sponsors/therawdev — Shopper is thankful for those keeping open source safe.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐘Packagistshopper/frameworkall versions2.9.2

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for shopper/framework. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update shopper/framework to 2.9.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-56827 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether CVE-2026-56827 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to CVE-2026-56827. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary Five Filament `groupedBulkActions` blocks across the Shopper admin Livewire pages omit the `->authorize(...)` permission gate, while their per-record sibling actions (and other Shopper Index pages such as `Pages/Settings/Currencies.php`, `Pages/Reviews/Index.php`, `Pages/Collection/Index.php`, and `Pages/Discount/Index.php`) correctly chain `->authorize(...)`. Each affected page's `mount()` only requires the read-only `browse_*` permission, so a low-privilege staff user holding only the read permission can drive the bulk endpoint via the standard Livewire `callTableBulkAction` flow
O3 Security · Impact-Aware SCA

Is CVE-2026-56827 in your dependencies?

O3 detects CVE-2026-56827 across Packagist dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.

CVE-2026-56827: shopper/framework | O3 Security