GHSA-243P-F3CV-C5WH
Vulnerability from github – Published: 2026-09-11 20:47 – Updated: 2026-09-11 20:47Summary
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-recorddeleteaction 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-recorddeleteaction 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.phplines 90–129: everyBulkActionchains->authorize('access_setting').packages/admin/src/Livewire/Pages/Reviews/Index.phplines 105–119:DeleteBulkActionchains->authorize('delete_reviews').packages/admin/src/Livewire/Pages/Collection/Index.phplines 109–128:DeleteBulkActionchains->authorize('delete_collections').packages/admin/src/Livewire/Pages/Discount/Index.phplines 126–145:DeleteBulkActionchains->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": [
{
"package": {
"ecosystem": "Packagist",
"name": "shopper/framework"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.9.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-56827"
],
"database_specific": {
"cwe_ids": [
"CWE-862"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-11T20:47:17Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\nFive Filament `groupedBulkActions` blocks across the Shopper admin Livewire pages omit the `-\u003eauthorize(...)` 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 `-\u003eauthorize(...)`. Each affected page\u0027s `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).\n\nA 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.\n\nCVSS 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.\n\n## Vulnerable components (paths relative to repo root)\n\nAll references are HEAD = commit `ac9a760` on `master` (the very commit that closed the previous wave of authorization-drift bugs from GHSA-j328-xmgp-j4q3).\n\n### 1) `packages/admin/src/Livewire/Pages/Attribute/Browse.php`\n\nMount at line 36\u201339 requires only `browse_attributes`.\n\n- Lines 106\u2013122: `DeleteBulkAction::make()` has NO `-\u003eauthorize(...)` chain (the surrounding per-record `delete` action at lines 95\u2013104 correctly does `-\u003eauthorize(\u0027delete_attributes\u0027)`).\n- Lines 123\u2013138: `BulkAction::make(\u0027enabled\u0027)` has NO `-\u003eauthorize(...)`.\n- Lines 139\u2013155: `BulkAction::make(\u0027disabled\u0027)` has NO `-\u003eauthorize(...)`.\n\nNet 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.\n\n### 2) `packages/admin/src/Livewire/Pages/Tag/Index.php`\n\nMount at line 39 requires only `browse_tags`.\n\n- Lines 96\u2013108: `DeleteBulkAction::make()` has NO `-\u003eauthorize(...)` chain (the per-record `delete` action at lines 79\u201394 correctly does `-\u003eauthorize(\u0027delete_tags\u0027)`).\n\nNet effect: a `browse_tags`-only user can delete every `ProductTag` row.\n\n### 3) `packages/admin/src/Livewire/Pages/Brand/Index.php`\n\nMount at line 37\u201340 requires only `browse_brands`.\n\n- Lines 97\u2013112: `BulkAction::make(\u0027enabled\u0027)` has NO `-\u003eauthorize(...)`.\n- Lines 113\u2013129: `BulkAction::make(\u0027disabled\u0027)` has NO `-\u003eauthorize(...)`.\n\nNet 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\u2013148 are correctly `-\u003eauthorize(...)` gated \u2014 only the visibility bulk actions were missed.\n\n### 4) `packages/admin/src/Livewire/Pages/Category/Index.php`\n\nMount at line 38\u201341 requires only `browse_categories`.\n\n- Lines 102\u2013117: `BulkAction::make(\u0027enabled\u0027)` has NO `-\u003eauthorize(...)`.\n- Lines 118\u2013133: `BulkAction::make(\u0027disabled\u0027)` has NO `-\u003eauthorize(...)`.\n\nNet effect: a `browse_categories`-only user can flip `is_enabled` on every category. Same shape as Brand.\n\n### 5) `packages/admin/src/Livewire/Pages/Supplier/Index.php`\n\nMount at line 38 requires only `browse_suppliers`.\n\n- Lines 93\u2013108: `BulkAction::make(\u0027enabled\u0027)` has NO `-\u003eauthorize(...)`.\n- Lines 109\u2013125: `BulkAction::make(\u0027disabled\u0027)` has NO `-\u003eauthorize(...)`.\n\nNet effect: a `browse_suppliers`-only user can flip `is_enabled` on every supplier.\n\n## Reference comparison: places that ARE correctly gated\n\nFor reference, here is what the same pattern looks like in files that DID get the fix:\n\n- `packages/admin/src/Livewire/Pages/Settings/Currencies.php` lines 90\u2013129: every `BulkAction` chains `-\u003eauthorize(\u0027access_setting\u0027)`.\n- `packages/admin/src/Livewire/Pages/Reviews/Index.php` lines 105\u2013119: `DeleteBulkAction` chains `-\u003eauthorize(\u0027delete_reviews\u0027)`.\n- `packages/admin/src/Livewire/Pages/Collection/Index.php` lines 109\u2013128: `DeleteBulkAction` chains `-\u003eauthorize(\u0027delete_collections\u0027)`.\n- `packages/admin/src/Livewire/Pages/Discount/Index.php` lines 126\u2013145: `DeleteBulkAction` chains `-\u003eauthorize(\u0027delete_discounts\u0027)`.\n\nThe convention is established and applied elsewhere \u2014 these five files just missed it.\n\n## Proof of Concept\n\nThe 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`:\n\n```\n PASS Tests\\Admin\\Livewire\\Pages\\Brand\\AuthBypassPocTest\n \u2713 it SHOPPER-2 PoC: read-only viewer can mass-DISABLE all brands via unguarded BulkAction\n \u2713 it SHOPPER-2 PoC: read-only viewer can mass-ENABLE all brands via unguarded BulkAction\n \u2713 it SHOPPER-2 PoC: read-only viewer can mass-DISABLE all categories via unguarded BulkAction\n \u2713 it SHOPPER-2 PoC: read-only viewer can mass-DISABLE all suppliers via unguarded BulkAction\n \u2713 it SHOPPER-2 PoC: read-only viewer can DELETE all attributes via unguarded DeleteBulkAction\n \u2713 it SHOPPER-2 PoC: read-only viewer can mass-DISABLE all attributes via unguarded BulkAction\n \u2713 it SHOPPER-2 PoC: browse_tags viewer can DELETE all product tags via unguarded DeleteBulkAction\n\n Tests: 7 passed (32 assertions)\n```\n\nEach test seeds three records, signs in a user holding only the corresponding `browse_*` permission, calls `Livewire::test(\u003cPage\u003e::class)-\u003ecallTableBulkAction(...)`, and asserts the side effect (records flipped or deleted). For example, the attribute mass-delete test:\n\n```php\n$this-\u003eviewer = User::factory()-\u003ecreate();\n$this-\u003eviewer-\u003egivePermissionTo(\u0027browse_attributes\u0027);\n$this-\u003eactingAs($this-\u003eviewer);\n\nAttribute::factory()-\u003ecount(3)-\u003ecreate();\nexpect($this-\u003eviewer-\u003ecan(\u0027delete_attributes\u0027))-\u003etoBeFalse();\n\nLivewire::test(AttributeBrowse::class)\n -\u003ecallTableBulkAction(\\Filament\\Actions\\DeleteBulkAction::class, Attribute::pluck(\u0027id\u0027)-\u003etoArray())\n -\u003eassertHasNoErrors();\n\nexpect(Attribute::count())-\u003etoBe(0);\n```\n\nThe call uses the same `callTableBulkAction` helper Shopper\u0027s own test suite uses everywhere, which in turn drives the same Livewire `update` payload the browser would emit \u2014 so this is a faithful HTTP-level reproduction.\n\n## Suggested fix\n\nAdd `-\u003eauthorize(\u003ccorrect_permission\u003e)` to each of the five vulnerable groups, mirroring the pattern already used elsewhere:\n\n```diff\n // Pages/Attribute/Browse.php\n -\u003egroupedBulkActions([\n DeleteBulkAction::make()\n+ -\u003eauthorize(\u0027delete_attributes\u0027)\n -\u003elabel(__(\u0027shopper::forms.actions.delete\u0027))\n -\u003erequiresConfirmation()\n -\u003eaction(function (Collection $records): void { /* ... */ }),\n BulkAction::make(\u0027enabled\u0027)\n+ -\u003eauthorize(\u0027edit_attributes\u0027)\n -\u003elabel(__(\u0027shopper::forms.actions.enable\u0027))\n -\u003eaction(function (Collection $records): void { /* ... */ }),\n BulkAction::make(\u0027disabled\u0027)\n+ -\u003eauthorize(\u0027edit_attributes\u0027)\n -\u003elabel(__(\u0027shopper::forms.actions.disable\u0027))\n -\u003eaction(function (Collection $records): void { /* ... */ }),\n ])\n```\n\nApply 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`).\n\nA 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.\n\n## Resources\n\n- 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.\n- Same-shape fix: commit `ac9a760` (PR #514). Five Filament bulk-action groups did not receive the corresponding `-\u003eauthorize(...)` chain.\n- CWE-285 Improper Authorization, CWE-862 Missing Authorization.\n\n### Credits\n\nReported by Vishal Shukla(@shukla304) using sechub.dev AI Agent\n\n### Support\n\nIf 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 \u2014 Shopper is thankful for those keeping open source safe.",
"id": "GHSA-243p-f3cv-c5wh",
"modified": "2026-09-11T20:47:17Z",
"published": "2026-09-11T20:47:17Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/shopperlabs/shopper/security/advisories/GHSA-243p-f3cv-c5wh"
},
{
"type": "PACKAGE",
"url": "https://github.com/shopperlabs/shopper"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Shopper: Authorization bypass in Filament bulk actions allows browse-only staff to mass-delete attributes/tags and mass-toggle visibility of brands/categories/suppliers"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.