CVE-2026-56826: Shopper/framework Shopping Privilege Escalation Through Missing Vulnerability

MODERATE
Advisory Identifier: GHSA-f7h9-qv4x-9x57
CVSS 5.4
CWE-862
Published
Advisory Summary

CVE-2026-56826 is a MODERATE (CVSS 5.4) shopping privilege escalation through missing vulnerability (CWE-862: Missing Authorization) affecting Shopper/framework libraries in the Packagist ecosystem. The issue involves shopping privilege escalation through missing authorization in settings components. Remediation is available by upgrading affected packages to version 2.9.2.

CVE-2026-56826 at a Glance

CVE Identifier CVE-2026-56826
GHSA Identifier GHSA-f7h9-qv4x-9x57
Severity MODERATE
CVSS v3.1 Base Score 5.4
CWE Classification CWE-862: Missing Authorization
Affected Product Shopper/framework
Package Ecosystem Packagist
Published Date
Last Updated

Security Impact

CVSS: 5.4

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity Low (I:L)
Availability Low (A:L)

How to Fix CVE-2026-56826

To remediate this vulnerability, upgrade all affected Shopper/framework dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

shopper/framework fixed: 2.9.2
Upgrade shopper/framework to 2.9.2
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for shopper/framework

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-56826, inspect your project dependencies for Shopper/framework:

inspect dependencies for shopper/framework

Analyze the terminal output to locate any version of Shopper/framework in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 2.9.2), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-862: Missing Authorization, where the product does not perform an authorization check when an actor attempts to access a resource or perform an action. In Shopper/framework, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary Four Livewire components in the Settings area expose destructive Filament actions (`delete` / `edit`) that perform **no server-side authorization**. Any authenticated user who can reach the Settings pages — i.e. holding only the coarse `access_setting` permission, **without** being an admin and **without** any `delete_*`/`edit_*` permission — can delete tax zones, tax rates, shipping zones, and carrier (shipping-rate) options by invoking the component action directly over the Livewire endpoint. These records sit on the storefront checkout path, so deleting them breaks shipping-rate calculation, removes region-scoped payment methods, and corrupts tax resolution at checkout. This is inconsistent with the rest of the admin, where destructive actions are gated by granular permissions (e.g. `Settings/Locations/Index` uses `->authorize('delete_inventories')`, and `Order/Detail` gates mutating actions with `edit_orders`). ## Affected components | Component | File | Unauthorized action | |---|---|---| | `Settings\Zones\ZoneShippingOptions` | `packages/admin/src/Livewire/Components/Settings/Zones/ZoneShippingOptions.php:47` | `delete` → `CarrierOption::query()->find($arguments['id'])->delete()` (id is client-supplied) | | `Settings\Zones\Detail` | `packages/admin/src/Livewire/Components/Settings/Zones/Detail.php:46` | `delete` → `DeleteAction` on the bound `Zone` | | `Settings\Taxes\Detail` | `packages/admin/src/Livewire/Components/Settings/Taxes/Detail.php:42` | `delete` → `DeleteAction` on the bound `TaxZone` | | `Settings\Taxes\TaxRates` | `packages/admin/src/Livewire/Components/Settings/Taxes/TaxRates.php:97` | `delete` → `DeleteAction` on a `TaxRate` | Each file contains **zero** `authorize` calls, and the actions declare neither `->authorize()` nor an enforced `->visible()` guard. ## Details The Settings pages mount these as child Livewire components. The parent page authorizes `access_setting` (e.g. `Pages/Settings/Taxes.php:29`), but the child components do not re-check authorization, and their destructive actions carry no `->authorize()`. Because each Livewire component handles its own `/livewire/update` requests, the action executes purely on the page-level `access_setting` gate — there is no per-resource permission, and `delete_zones` / `delete_taxes` permissions are never even generated by the seeder (`packages/admin/database/seeders/PermissionsTableSeeder.php`). `ZoneShippingOptions::deleteAction()` is the clearest case — it deletes by an id taken straight from the client action arguments with no scoping and no permission check: ```php // packages/admin/src/Livewire/Components/Settings/Zones/ZoneShippingOptions.php public function deleteAction(): Action { return Action::make('delete') ->requiresConfirmation() // ... no ->authorize(), no ->visible() ->action(function (array $arguments): void { CarrierOption::query()->find($arguments['id'])->delete(); // client-controlled id // ... }); } ``` ## Proof of Concept Confirmed with the project's own test harness (Pest + Orchestra Testbench, SQLite) — the real Livewire/Filament code path, executed as a non-admin user holding only `access_setting`. ```php use Livewire\Livewire; use Shopper\Core\Models\{CarrierOption, Zone}; use Shopper\Livewire\Components\Settings\Zones\ZoneShippingOptions; use Tests\Core\Stubs\User; uses(Tests\Admin\TestCase::class); it('low-priv access_setting user deletes a CarrierOption with no authorization', function (): void { $attacker = User::factory()->create(); $attacker->givePermissionTo('access_setting'); // NOT admin, NO delete_* permission $this->actingAs($attacker, config('shopper.auth.guard')); $zone = Zone::factory()->create(); $option = CarrierOption::factory()->create(['zone_id' => $zone->id]); Livewire::test(ZoneShippingOptions::class, ['selectedZoneId' => $zone->id]) ->callAction('delete', arguments: ['id' => $option->id]); expect(CarrierOption::query()->find($option->id))->toBeNull(); // deleted -> vulnerable }); ``` Result: ``` Attacker: isAdmin()=false, can('access_setting')=true, can('delete_zones')=false, can('edit_zones')=false [BEFORE] CarrierOption count = 1 (target #1 'DHL Express' exists = YES) [ATTACK] callAction('delete', id=1) on ZoneShippingOptions [AFTER ] CarrierOption count = 0 (target #1 exists = NO -> deleted) PASS 3 passed (11 assertions) ✓ CONTROL — Order/Detail::markPaid is correctly hidden without edit_orders (harness enforces declared authz) ✓ a CarrierOption is deleted by the low-priv user ✓ a shipping Zone is deleted by the low-priv user ``` The CONTROL case rules out a false positive: the same harness correctly denies `Order/Detail::markPaid` for a user lacking `edit_orders`, proving authorization is enforced when a component declares it — these four components simply declare none. ## Impact A low-privileged staff member (or a compromised low-privileged account) can sabotage the storefront's checkout/revenue path without any delete permission: - **Delete a `CarrierOption`** → that shipping rate disappears from checkout for the zone. - **Delete a `Zone`** → removes the country → carrier/payment-method/currency mapping; customers shipping to those countries lose all shipping and payment options (`CarrierRateService::getRatesForZone` / `getManualRates` read these directly). - **Delete a `TaxZone` / `TaxRate`** → `TaxCalculator::resolveZone()` can no longer resolve the zone, corrupting tax calculation at checkout. Net effect: integrity and availability damage to live commerce configuration, performed by a principal who was never granted that authority (least-privilege violation). ## Secondary issue found while reproducing `Zones\Detail::deleteAction()->after()` calls `$this->reset('zone')`, but `zone` is a `#[Computed]` method (not a property), so it throws `ReflectionException` **after** the row is deleted. Worth fixing alongside the authorization gap. ## Suggested remediation Add an authorization check to each action, and ideally a `mount()` guard on each child component, matching the pattern already used in `Settings/Locations/Index.php` and `Team/RolePermission.php`: ```php public function deleteAction(): Action { return Action::make('delete') ->authorize('access_setting') // or a new granular delete_zones / delete_taxes permission ->requiresConfirmation() // ... } ``` Apply to the `delete` (and `edit`) actions in all four components. Consider also generating granular `*_zones` / `*_taxes` permissions so settings access can follow least privilege, and fix the `$this->reset('zone')` call in `Zones\Detail`.

CVE-2026-56826 Affected Shopper/framework Versions

1 component(s)
Fixed Versions:
2.9.2
Product / Package Ecosystem Affected Versions Fixed Version
shopper/framework
Packagist
≥ 2.0.0
< 2.9.2
2.9.2

References

Frequently Asked Questions

What is CVE-2026-56826?
CVE-2026-56826 is a security vulnerability in Shopper/framework involving Shopping privilege escalation through missing authorization in Settings components under CWE-862: Missing Authorization.
Which Shopper/framework versions are affected?
Affected versions include releases prior to 2.9.2 across affected packages: shopper/framework.
What version fixes CVE-2026-56826?
Upgrading to 2.9.2 (or the latest patched release for your respective branch) resolves the vulnerability.
Is shopper/framework affected?
Yes, shopper/framework is affected in releases prior to 2.9.2. Upgrade your dependency to 2.9.2 or later.
What is the CVSS score of CVE-2026-56826?
CVE-2026-56826 has a CVSS v3.1 base score of 5.4 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L.
What CWE is associated with CVE-2026-56826?
CVE-2026-56826 is classified under CWE-862: Missing Authorization (The product does not perform an authorization check when an actor attempts to access a resource or perform an action.).
How can I check Packagist dependencies for CVE-2026-56826?
Run the dependency tree command: 'inspect dependencies for shopper/framework'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-f7h9-qv4x-9x57",
  "modified" : "2026-09-11T21:28:20Z",
  "published" : "2026-09-11T21:28:20Z",
  "aliases" : [ "CVE-2026-56826" ],
  "summary" : "Shopping privilege escalation through missing authorization in Settings components",
  "details" : "## Summary\n\nFour Livewire components in the Settings area expose destructive Filament actions (`delete` / `edit`) that perform **no server-side authorization**. Any authenticated user who can reach the Settings pages — i.e. holding only the coarse `access_setting` permission, **without** being an admin and **without** any `delete_*`/`edit_*` permission — can delete tax zones, tax rates, shipping zones, and carrier (shipping-rate) options by invoking the component action directly over the Livewire endpoint.\n\nThese records sit on the storefront checkout path, so deleting them breaks shipping-rate calculation, removes region-scoped payment methods, and corrupts tax resolution at checkout.\n\nThis is inconsistent with the rest of the admin, where destructive actions are gated by granular permissions (e.g. `Settings/Locations/Index` uses `->authorize('delete_inventories')`, and `Order/Detail` gates mutating actions with `edit_orders`).\n\n## Affected components\n\n| Component | File | Unauthorized action |\n|---|---|---|\n| `Settings\\Zones\\ZoneShippingOptions` | `packages/admin/src/Livewire/Components/Settings/Zones/ZoneShippingOptions.php:47` | `delete` → `CarrierOption::query()->find($arguments['id'])->delete()` (id is client-supplied) |\n| `Settings\\Zones\\Detail` | `packages/admin/src/Livewire/Components/Settings/Zones/Detail.php:46` | `delete` → `DeleteAction` on the bound `Zone` |\n| `Settings\\Taxes\\Detail` | `packages/admin/src/Livewire/Components/Settings/Taxes/Detail.php:42` | `delete` → `DeleteAction` on the bound `TaxZone` |\n| `Settings\\Taxes\\TaxRates` | `packages/admin/src/Livewire/Components/Settings/Taxes/TaxRates.php:97` | `delete` → `DeleteAction` on a `TaxRate` |\n\nEach file contains **zero** `authorize` calls, and the actions declare neither `->authorize()` nor an enforced `->visible()` guard.\n\n## Details\n\nThe Settings pages mount these as child Livewire components. The parent page authorizes `access_setting` (e.g. `Pages/Settings/Taxes.php:29`), but the child components do not re-check authorization, and their destructive actions carry no `->authorize()`. Because each Livewire component handles its own `/livewire/update` requests, the action executes purely on the page-level `access_setting` gate — there is no per-resource permission, and `delete_zones` / `delete_taxes` permissions are never even generated by the seeder (`packages/admin/database/seeders/PermissionsTableSeeder.php`).\n\n`ZoneShippingOptions::deleteAction()` is the clearest case — it deletes by an id taken straight from the client action arguments with no scoping and no permission check:\n\n```php\n// packages/admin/src/Livewire/Components/Settings/Zones/ZoneShippingOptions.php\npublic function deleteAction(): Action\n{\n    return Action::make('delete')\n        ->requiresConfirmation()\n        // ... no ->authorize(), no ->visible()\n        ->action(function (array $arguments): void {\n            CarrierOption::query()->find($arguments['id'])->delete();   // client-controlled id\n            // ...\n        });\n}\n```\n\n## Proof of Concept\n\nConfirmed with the project's own test harness (Pest + Orchestra Testbench, SQLite) — the real Livewire/Filament code path, executed as a non-admin user holding only `access_setting`.\n\n```php\nuse Livewire\\Livewire;\nuse Shopper\\Core\\Models\\{CarrierOption, Zone};\nuse Shopper\\Livewire\\Components\\Settings\\Zones\\ZoneShippingOptions;\nuse Tests\\Core\\Stubs\\User;\n\nuses(Tests\\Admin\\TestCase::class);\n\nit('low-priv access_setting user deletes a CarrierOption with no authorization', function (): void {\n    $attacker = User::factory()->create();\n    $attacker->givePermissionTo('access_setting');          // NOT admin, NO delete_* permission\n    $this->actingAs($attacker, config('shopper.auth.guard'));\n\n    $zone   = Zone::factory()->create();\n    $option = CarrierOption::factory()->create(['zone_id' => $zone->id]);\n\n    Livewire::test(ZoneShippingOptions::class, ['selectedZoneId' => $zone->id])\n        ->callAction('delete', arguments: ['id' => $option->id]);\n\n    expect(CarrierOption::query()->find($option->id))->toBeNull();   // deleted -> vulnerable\n});\n```\n\nResult:\n\n```\nAttacker: isAdmin()=false, can('access_setting')=true, can('delete_zones')=false, can('edit_zones')=false\n[BEFORE] CarrierOption count = 1  (target #1 'DHL Express' exists = YES)\n[ATTACK] callAction('delete', id=1) on ZoneShippingOptions\n[AFTER ] CarrierOption count = 0  (target #1 exists = NO -> deleted)\n\nPASS  3 passed (11 assertions)\n  ✓ CONTROL — Order/Detail::markPaid is correctly hidden without edit_orders (harness enforces declared authz)\n  ✓ a CarrierOption is deleted by the low-priv user\n  ✓ a shipping Zone is deleted by the low-priv user\n```\n\nThe CONTROL case rules out a false positive: the same harness correctly denies `Order/Detail::markPaid` for a user lacking `edit_orders`, proving authorization is enforced when a component declares it — these four components simply declare none.\n\n## Impact\n\nA low-privileged staff member (or a compromised low-privileged account) can sabotage the storefront's checkout/revenue path without any delete permission:\n\n- **Delete a `CarrierOption`** → that shipping rate disappears from checkout for the zone.\n- **Delete a `Zone`** → removes the country → carrier/payment-method/currency mapping; customers shipping to those countries lose all shipping and payment options (`CarrierRateService::getRatesForZone` / `getManualRates` read these directly).\n- **Delete a `TaxZone` / `TaxRate`** → `TaxCalculator::resolveZone()` can no longer resolve the zone, corrupting tax calculation at checkout.\n\nNet effect: integrity and availability damage to live commerce configuration, performed by a principal who was never granted that authority (least-privilege violation).\n\n## Secondary issue found while reproducing\n\n`Zones\\Detail::deleteAction()->after()` calls `$this->reset('zone')`, but `zone` is a `#[Computed]` method (not a property), so it throws `ReflectionException` **after** the row is deleted. Worth fixing alongside the authorization gap.\n\n## Suggested remediation\n\nAdd an authorization check to each action, and ideally a `mount()` guard on each child component, matching the pattern already used in `Settings/Locations/Index.php` and `Team/RolePermission.php`:\n\n```php\npublic function deleteAction(): Action\n{\n    return Action::make('delete')\n        ->authorize('access_setting')   // or a new granular delete_zones / delete_taxes permission\n        ->requiresConfirmation()\n        // ...\n}\n```\n\nApply to the `delete` (and `edit`) actions in all four components. Consider also generating granular `*_zones` / `*_taxes` permissions so settings access can follow least privilege, and fix the `$this->reset('zone')` call in `Zones\\Detail`.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "shopper/framework"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "2.0.0"
      }, {
        "fixed" : "2.9.2"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/shopperlabs/shopper/security/advisories/GHSA-f7h9-qv4x-9x57"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/shopperlabs/shopper"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-862" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-09-11T21:28:20Z",
    "nvd_published_at" : null
  }
}