Średnia

Shopping privilege escalation through missing authorization in Settings components

  • Ujawniono: 11.09.2026
  • Aktualizacja: 11.10.2026 10:22

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.

Czego dotyczy

Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Paczka Composer shopper/framework >= 2.0.0, < 2.9.2 2.9.2

Odnośniki

Ocena CVSS 5.4/10
Wektor CVSS
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L
Słabość (CWE)
CWE-862
Platforma
Composer
Paczka
shopper/framework
Ujawniono
11.09.2026
Aktualizacja
11.10.2026 10:22

Źródła i licencje

OSV / GitHub Advisory Database - GHSA-f7h9-qv4x-9x57 - CC BY 4.0

Historia podatności: shopper/framework

Wszystkie opisane podatności tej paczki (Paczka Composer), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.

Wszystkie (12) w archiwum
Wszystkich wpisów
12
Pierwsza → ostatnia
05.2026 09.2026
Bez poprawki
0
Wykorzystywana w atakach
0

Rozkład istotności

  • Krytyczna 1
  • Wysoka 6
  • Średnia 5

Ujawnienia w latach

  1. 2026: 12
Istotność:
  1. Wysoka · 8.8 CVE-2026-56828 >= 2.8.0, < 2.9.2 2.9.2
  2. Shopping privilege escalation through missing authorization in Settings components ten wpis
    Średnia · 5.4 CVE-2026-56826 >= 2.0.0, < 2.9.2 2.9.2

Dowiedz się, które podatności dotyczą Twojej aplikacji

Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.