Sylius CVE-2026-53637
MEDIUMSeverity by source
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
Attacker controls the race condition timing themselves (AC:L); authenticated customer session required (PR:L); order deletion is integrity impact only, no confidentiality or availability component.
Primary rating from Vendor (https://github.com/Sylius/Sylius).
CVSS VectorVendor: https://github.com/Sylius/Sylius
Lifecycle Timeline
1DescriptionCVE.org
Impact
A user opens the cart page in the browser. In the background, the order gets completed, e.g. an admin changes the status, or the user finalizes payment in another tab. The browser still displays the old cart: the LiveComponent is unaware the underlying order state has changed.
If the user then:
- clears the cart →
clearCart()callsmanager->remove()on the
completed order: the order is permanently deleted from the database;
- removes a product →
removeItem()mutates an item on the completed
order;
- changes quantity →
saveCart()overwrites data on the completed order.
In all cases, the customer's order data is irreversibly corrupted or lost, even though the order has already been placed and paid for. The same vector can be triggered deliberately by an authenticated customer (keep the cart page open, complete checkout in another tab, then modify the "cart" to add quantity beyond what was paid for).
Patches
The issue is fixed in versions: 2.0.18, 2.1.15, 2.2.6 and above.
Workarounds
If users cannot update Sylius immediately, they should create a patched copy of the affected class in their application's src/ directory and override the Sylius service definition to use it.
Step 1. Create src/Twig/Component/Cart/FormComponent.php
<?php
declare(strict_types=1);
namespace App\Twig\Component\Cart;
use Doctrine\Persistence\ObjectManager;
use Sylius\Bundle\UiBundle\Twig\Component\ResourceFormComponentTrait;
use Sylius\Bundle\UiBundle\Twig\Component\TemplatePropTrait;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Core\OrderCheckoutStates;
use Sylius\Component\Core\Repository\OrderRepositoryInterface;
use Sylius\Component\Order\SyliusCartEvents;
use Sylius\Resource\Model\ResourceInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\Attribute\PreReRender;
use Symfony\UX\LiveComponent\ComponentToolsTrait;
class FormComponent
{
use ComponentToolsTrait;
/** @use ResourceFormComponentTrait<OrderInterface> */
use ResourceFormComponentTrait;
use TemplatePropTrait;
public const SYLIUS_SHOP_CART_CHANGED = 'sylius:shop:cart_changed';
public const SYLIUS_SHOP_CART_CLEARED = 'sylius:shop:cart_cleared';
public bool $shouldSaveCart = true;
/** @param OrderRepositoryInterface<OrderInterface> $orderRepository */
public function __construct(
OrderRepositoryInterface $orderRepository,
FormFactoryInterface $formFactory,
string $resourceClass,
string $formClass,
protected readonly ObjectManager $manager,
protected readonly EventDispatcherInterface $eventDispatcher,
) {
$this->initialize($orderRepository, $formFactory, $resourceClass, $formClass);
}
public function hydrateResource(mixed $value): ?ResourceInterface
{
if (empty($value)) {
return $this->createResource();
}
/** @var OrderInterface|null $order */
$order = $this->repository->find($value);
if (
!$order instanceof OrderInterface
|| $order->getCheckoutState() === OrderCheckoutStates::STATE_COMPLETED
) {
return $this->createResource();
}
return $order;
}
#[PreReRender(priority: -100)]
public function saveCart(): void
{
if ($this->shouldSaveCart && $this->resource?->getId() !== null) {
$form = $this->getForm();
if ($form->isValid()) {
$this->eventDispatcher->dispatch(new GenericEvent($form->getData()), SyliusCartEvents::CART_CHANGE);
$this->manager->flush();
$this->emit(self::SYLIUS_SHOP_CART_CHANGED, ['cartId' => $this->resource->getId()]);
}
}
}
#[LiveAction]
public function removeItem(#[LiveArg] int $index): void
{
if ($this->resource?->getId() === null) {
return;
}
$data = $this->formValues['items'];
unset($data[$index]);
$this->formValues['items'] = array_values($data);
$orderItem = $this->resource->getItems()->get($index);
$this->eventDispatcher->dispatch(new GenericEvent($orderItem), SyliusCartEvents::CART_ITEM_REMOVE);
$this->manager->persist($this->resource);
$this->manager->flush();
$this->manager->refresh($this->resource);
$this->shouldSaveCart = false;
$this->submitForm();
$this->emit(self::SYLIUS_SHOP_CART_CHANGED, ['cartId' => $this->resource->getId()]);
}
#[LiveAction]
public function clearCart(): void
{
if ($this->resource?->getId() === null) {
return;
}
$this->formValues['items'] = [];
$this->eventDispatcher->dispatch(new GenericEvent($this->resource), SyliusCartEvents::CART_CLEAR);
$this->manager->remove($this->resource);
$this->manager->flush();
$this->resource = $this->createResource();
$this->resetForm();
$this->isValidated = false;
$this->validatedFields = [];
$this->shouldSaveCart = false;
$this->submitForm();
$this->emit(self::SYLIUS_SHOP_CART_CLEARED);
}
#[LiveAction]
public function removeCoupon(): void
{
$this->formValues['promotionCoupon'] = '';
$this->submitForm();
}
private function getDataModelValue(): string
{
return 'debounce(500)|*';
}
}Step 2. Override the Sylius service in config/services.yaml
Append to the application's config/services.yaml (or a dedicated file loaded by the kernel, e.g. config/packages/sylius_security_cart.yaml):
services:
sylius_shop.twig.component.cart.form:
class: App\Twig\Component\Cart\FormComponent
arguments:
- '@sylius.repository.order'
- '@form.factory'
- '%sylius.model.order.class%'
- 'Sylius\Bundle\ShopBundle\Form\Type\CartType'
- '@doctrine.orm.entity_manager'
- '@event_dispatcher'
calls:
- [setLiveResponder, ['@ux.live_component.live_responder']]
tags:
- { name: sylius.live_component.shop, key: 'sylius_shop:cart:form' }This redeclares the existing Sylius service id sylius_shop.twig.component.cart.form so it instantiates the patched class from App\ while preserving every argument, call and tag from the original Sylius XML definition. The cart twig hook keeps resolving to the same Live Component key (sylius_shop:cart:form).
Step 3. Clear the cache
bin/console cache:clearReporters
We would like to extend our gratitude to the following individuals for their detailed reporting and responsible disclosure of this vulnerability:
- Kévin Gonella (@kgonella)
- Sam V.
For more information
If there are any questions or comments about this advisory:
- Open an issue in Sylius issues
- Send an email to [security@sylius.com](mailto:security@sylius.com)
AnalysisAI
Order data corruption and permanent deletion in Sylius affects authenticated customers across versions prior to 2.0.18, 2.1.15, and 2.2.6 due to a stale-state race condition in the cart LiveComponent. When an order transitions to STATE_COMPLETED while a customer's cart page remains open in a browser tab, subsequent cart actions - clearing, removing items, or adjusting quantities - are applied to the completed order rather than a fresh cart, irreversibly deleting or mutating finalized order records. No public exploit code has been identified at time of analysis, but the attack is trivially reproducible by any authenticated customer without specialized tooling, as the attacker can deliberately engineer the race condition by completing checkout in one tab while exploiting the stale cart in another.
Technical ContextAI
Sylius is a PHP e-commerce platform distributed as the Composer package sylius/sylius and built on the Symfony framework. The affected component is the Twig LiveComponent sylius_shop.twig.component.cart.form (class Sylius\Bundle\ShopBundle\Twig\Component\Cart\FormComponent), which uses Symfony UX LiveComponent for real-time cart management. CWE-672 (Operation on a Resource after Expiration or Release) precisely describes the root cause: the hydrateResource() method retrieves an order entity by its persisted ID without validating whether its checkoutState has transitioned to OrderCheckoutStates::STATE_COMPLETED. The LiveComponent's clearCart() method calls manager->remove() on the hydrated order, removeItem() mutates order items via the Doctrine ObjectManager, and saveCart() flushes quantity changes - all without re-checking order state. The fix introduces a guard in hydrateResource() that returns a freshly created (empty) cart resource whenever the loaded order is already completed, nullifying all subsequent mutation actions against finalized orders.
RemediationAI
Upgrade to Sylius 2.0.18, 2.1.15, or 2.2.6 (or any later release within the respective branch), which introduce a completed-state guard in hydrateResource() that prevents cart mutations from operating on finalized orders. The vendor advisory is at https://github.com/Sylius/Sylius/security/advisories/GHSA-5597-7rmh-97q5. For installations that cannot immediately update, the vendor provides a service-override workaround: create a patched App\Twig\Component\Cart\FormComponent class under src/Twig/Component/Cart/FormComponent.php incorporating the STATE_COMPLETED guard in hydrateResource(), override the sylius_shop.twig.component.cart.form service in config/services.yaml to instantiate the patched class while preserving all original arguments and tags, then run bin/console cache:clear. This workaround trades upgrade simplicity for a local maintenance burden - the patched class must be kept in sync with Sylius upstream changes to the cart component until the application is upgraded. There are no meaningful network-layer compensating controls since exploitation requires only a normal authenticated HTTP session to the cart endpoint.
In PHP versions 7.1.x below 7.1.33, 7.2.x below 7.2.24 and 7.3.x below 7.3.11 in certain configurations of FPM setup it
sapi/cgi/cgi_main.c in PHP before 5.3.12 and 5.4.x before 5.4.2, when configured as a CGI script (aka php-cgi), does not
(1) boardData102.php, (2) boardData103.php, (3) boardDataJP.php, (4) boardDataNA.php, and (5) boardDataWW.php in Netgear
The '/common/download_agent_installer.php' script in the Quest KACE System Management Appliance 8.0.318 is accessible by
ProjectSend versions prior to r1720 are affected by an improper authentication vulnerability. Rated critical severity (C
Roundcube Webmail contains a critical PHP object deserialization vulnerability (CVE-2025-49113, CVSS 9.9) that allows au
Util/PHP/eval-stdin.php in PHPUnit before 4.8.28 and 5.x before 5.6.3 allows remote attackers to execute arbitrary PHP c
Palo Alto Networks PAN-OS management web interface contains an authentication bypass allowing unauthenticated attackers
Nagios XI version xi-5.7.5 is affected by OS command injection. Rated high severity (CVSS 8.8), this vulnerability is re
Nagios XI version xi-5.7.5 is affected by OS command injection. Rated high severity (CVSS 8.8), this vulnerability is re
The get_referers function in /opt/ws/bin/sblistpack in Sophos Web Appliance before 3.7.9.1 and 3.8 before 3.8.1.1 allows
The Backup Migration plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 1
Same technique Information Disclosure
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-5597-7rmh-97q5