GitHub Advisoryadvisory · Packagist
GHSA-2xc6-348p-c2x6 Sylius affected by IDOR in Cart and Checkout LiveComponents
HIGH
Official page ↗
- Aliases
- CVE-2026-31820
- Ecosystem
- Packagist
- Packages
- sylius/sylius
- Severity
- High
- CVSS vector
- CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
- Weaknesses
- CWE-639
- Reviewed
- 2026-03-11
Affected packages
• Packagist sylius/sylius: >= 2.0.0, < 2.0.16 → fixed in 2.0.16
• Packagist sylius/sylius: >= 2.1.0, < 2.1.12 → fixed in 2.1.12
• Packagist sylius/sylius: >= 2.2.0, < 2.2.3 → fixed in 2.2.3
Description
Impact
An authenticated Insecure Direct Object Reference (IDOR) vulnerability exists in multiple shop LiveComponents due to unvalidated resource IDs accepted via `#[LiveArg]` parameters. Unlike props, which are protected by LiveComponent's `@checksum`, `args` are fully user-controlled - any action that accepts a resource ID via `#[LiveArg]` and loads it with `->find()` without ownership validation is vulnerable.
Checkout address FormComponent (`addressFieldUpdated` action): Accepts an `addressId` via `#[LiveArg]` and loads it without verifying ownership, exposing another user's first name, last name, company, phone number, street, city, postcode, and country.
Cart WidgetComponent (`refreshCart` action): Accepts a `cartId` via `#[LiveArg]` and loads any order directly from the repository, exposing order total and item count.
Cart SummaryComponent (`refreshCart` action): Accepts a `cartId` via `#[LiveArg]` and loads any order directly from the repository, exposing subtotal, discount, shipping cost, taxes (excluded and included), and order total.
Since `sylius_order` contains both active carts (`state=cart`) and completed orders (`state=new/fulfilled`) in the same ID space, the cart IDOR exposes data from all orders, not just active carts.
Patches
The issue is fixed in versions: 2.0.16, 2.1.12, 2.2.3 and above.
Workarounds
Override vulnerable LiveComponent classes at the project level to add authorization checks to `#[LiveArg]` parameters.
Step 1. Exclude component overrides from default autowiring
In `config/services.yaml`, add `Twig/Component` to the exclude list to prevent duplicate service registration:
```yaml
App\:
resource: '../src/*'
exclude: '../src/{Entity,Kernel.php,Twig/Components}'
```
Step 2. Override checkout address FormComponent
Create `src/Twig/Components/Checkout/Address/FormComponent.php`:
```php
*/
use ResourceFormComponentTrait;
use TemplatePropTrait;
#[LiveProp]
public bool $emailExists = false;
/**
• @param OrderRepositoryInterface $repository
• @param UserRepositoryInterface $shopUserRepository
*/
public function __construct(
OrderRepositoryInterface $repository,
FormFactoryInterface $formFactory,
string $resourceClass,
string $formClass,
protected readonly CustomerContextInterface $customerContext,
protected readonly UserRepositoryInterface $shopUserRepository,
protected readonly AddressRepositoryInterface $addressRepository,
) {
$this->initialize($repository, $formFactory, $resourceClass, $formClass);
}
#[PreReRender(priority: -100)]
public function checkEmailExist(): void
{
$email = $this->formValues['customer']['email'] ?? null;
if (null !== $email) {
$this->emailExists = $this->shopUserRepository->findOneByEmail($email) !== null;
}
}
#[LiveListener(AddressBookComponent::SYLIUS_SHOP_ADDRESS_UPDATED)]
public function addressFieldUpdated(#[LiveArg] mixed $addressId, #[LiveArg] string $field): void
{
$customer = $this->customerContext->getCustomer();
if (null === $customer) {
return;
}
// Fix: findOneByCustomer instead of find — validates ownership
$address = $this->addressRepository->findOneByCustomer((string) $addressId, $customer);
if (null === $address) {
return;
}
$newAddress = [];
$newAddress['firstName'] = $address->getFirstName();
$newAddress['lastName'] = $address->getLastName();
$newAddress['phoneNumber'] = $address->getPhoneNumber();
$newAddress['company'] = $address->getCompany();
$newAddress['countryCode'] = $address->getCountryCode();
if ($address->getProvinceCode() !== null) {
$newAddress['provinceCode'] = $address->getProvinceCode();
}
if ($address->getProvinceName() !== null) {
$newAddress['provinceName'] = $address->getProvinceName();
}
$newAddress['street'] = $address->getStreet();
$newAddress['city'] = $address->getCity();
$newAddress['postcode'] = $address->getPostcode();
$this->formValues[$field] = $newAddress;
}
protected function instantiateForm(): FormInterface
{
return $this->formFactory->create(
$this->formClass,
$this->resource,
['customer' => $this->customerContext->getCustomer()],
);
}
}
```
Step 3. Override cart WidgetComponent
Create `src/Twig/Components/Cart/WidgetComponent.php`:
```php
*/
use ResourceLivePropTrait;
#[LiveProp(hydrateWith: 'hydrateResource', dehydrateWith: 'dehydrateResource')]
public ?ResourceInterface $cart = null;
public function __construct(
protected readonly CartContextInterface $cartContext,
OrderRepositoryInterface $orderRepository,
) {
$this->initialize($orderRepository);
}
#[PreMount]
public function initializeCart(): void
{
$this->cart = $this->getCart();
}
#[LiveListener(FormComponent::SYLIUS_SHOP_CART_CHANGED)]
#[LiveListener(FormComponent::SYLIUS_SHOP_CART_CLEARED)]
public function refreshCart(#[LiveArg] mixed $cartId = null): void
{
// Fix: ignore user-supplied cartId, always load from session
$this->cart = $this->getCart();
}
private function getCart(): ?OrderInterface
{
try {
return $this->cartContext->getCart();
} catch (CartNotFoundException) {
return null;
}
return $cart;
}
}
```
Step 4. Override cart SummaryComponent
Create `src/Twig/Components/Cart/SummaryComponent.php`:
```php
<?php
declare(strict_types=1);
…
Related
Alias · CVE (1)
- CVE-2026-31820 Sylius is an Open Source eCommerce Framework on Symfony. vulnerability
Weakness · CWE (1)
- CWE-639 Authorization Bypass Through User-Controlled Key weakness