{"id":"CVE-2026-31821","aliases":["GHSA-wjmg-4cq5-m8hg"],"url":"https://o3.security/vulnerability/CVE-2026-31821","summary":"Sylius is Missing Authorization in API v2 Add Item Endpoint","details":"### Impact\nThe `POST /api/v2/shop/orders/{tokenValue}/items` endpoint does not verify cart ownership. An unauthenticated attacker can add items to other registered customers' carts by knowing the cart `tokenValue`.\n\n```\nPOST /api/v2/shop/orders/{tokenValue}/items\n```\n\nOther mutation endpoints (PUT, PATCH, DELETE) are **not affected**. API Platform loads the Order entity through the state provider for these operations, which triggers `VisitorBasedExtension` and returns 404 for unauthorized users.\n\nAn attacker who obtains a cart `tokenValue` can add arbitrary items to another customer's cart. The endpoint returns the full cart representation in the response (HTTP 201), potentially leaking:\n\n- Customer email address\n- Cart contents (products, quantities, prices)\n- Address data (billing and shipping if set)\n- Payment and shipment IDs\n- Order totals and tax breakdown\n- Checkout state\n\n### Patches\nThe issue is fixed in versions: 2.0.16, 2.1.12, 2.2.3, and above.\n\n### Workarounds\nAdd an ownership check in `AddItemToCartHandler` by injecting `UserContextInterface` and verifying the current user matches the cart owner before adding items.\n\n#### Step 1. Patch the handler\n\nCreate new  `src/CommandHandler/Cart/AddItemToCartHandler.php`:\n\n```php\n<?php\n\ndeclare(strict_types=1);\n\nnamespace App\\CommandHandler\\Cart;\n\nuse Sylius\\Bundle\\ApiBundle\\Command\\Cart\\AddItemToCart;\nuse Sylius\\Bundle\\ApiBundle\\Context\\UserContextInterface;\nuse Sylius\\Component\\Core\\Factory\\CartItemFactoryInterface;\nuse Sylius\\Component\\Core\\Model\\OrderInterface;\nuse Sylius\\Component\\Core\\Model\\OrderItemInterface;\nuse Sylius\\Component\\Core\\Model\\ProductVariantInterface;\nuse Sylius\\Component\\Core\\Model\\ShopUserInterface;\nuse Sylius\\Component\\Core\\Repository\\OrderRepositoryInterface;\nuse Sylius\\Component\\Core\\Repository\\ProductVariantRepositoryInterface;\nuse Sylius\\Component\\Order\\Modifier\\OrderItemQuantityModifierInterface;\nuse Sylius\\Component\\Order\\Modifier\\OrderModifierInterface;\nuse Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException;\nuse Symfony\\Component\\Messenger\\Attribute\\AsMessageHandler;\n\n#[AsMessageHandler]\nfinal readonly class AddItemToCartHandler\n{\n    public function __construct(\n        private OrderRepositoryInterface $orderRepository,\n        private ProductVariantRepositoryInterface $productVariantRepository,\n        private OrderModifierInterface $orderModifier,\n        private CartItemFactoryInterface $cartItemFactory,\n        private OrderItemQuantityModifierInterface $orderItemQuantityModifier,\n        private UserContextInterface $userContext,\n    ) {\n    }\n\n    public function __invoke(AddItemToCart $addItemToCart): OrderInterface\n    {\n        /** @var ProductVariantInterface|null $productVariant */\n        $productVariant = $this->productVariantRepository->findOneBy(['code' => $addItemToCart->productVariantCode]);\n\n        if ($productVariant === null) {\n            throw new \\InvalidArgumentException('Product variant with given code has not been found.');\n        }\n\n        /** @var OrderInterface|null $cart */\n        $cart = $this->orderRepository->findCartByTokenValue($addItemToCart->orderTokenValue);\n\n        if ($cart === null) {\n            throw new \\InvalidArgumentException('Cart with given token has not been found.');\n        }\n\n        $this->assertCartAccessible($cart);\n\n        /** @var OrderItemInterface $cartItem */\n        $cartItem = $this->cartItemFactory->createNew();\n        $cartItem->setVariant($productVariant);\n\n        $this->orderItemQuantityModifier->modify($cartItem, $addItemToCart->quantity);\n        $this->orderModifier->addToOrder($cart, $cartItem);\n\n        return $cart;\n    }\n\n    private function assertCartAccessible(OrderInterface $cart): void\n    {\n        if ($cart->isCreatedByGuest()) {\n            return;\n        }\n\n        $cartCustomer = $cart->getCustomer();\n\n        if (null === $cartCustomer || null === $cartCustomer->getUser()) {\n            return;\n        }\n\n        $currentUser = $this->userContext->getUser();\n\n        if (\n            $currentUser instanceof ShopUserInterface\n            && $currentUser->getCustomer()?->getId() === $cartCustomer->getId()\n        ) {\n            return;\n        }\n\n        throw new NotFoundHttpException('Cart not found.');\n    }\n}\n```\n\n#### Step 2. Override the service\n\n```diff\n# config/services.yaml\n\nservices:\n    App\\:\n        resource: '../src/*'\n-       exclude: '../src/{Entity,Kernel.php}'                                                                         \n+       exclude: '../src/{Entity,Kernel.php,CommandHandler}'\n\n    sylius_api.command_handler.cart.add_item_to_cart:\n        class: App\\CommandHandler\\Cart\\AddItemToCartHandler\n        arguments:\n            $orderRepository: '@sylius.repository.order'\n            $productVariantRepository: '@sylius.repository.product_variant'\n            $orderModifier: '@sylius.modifier.order'\n            $cartItemFactory: '@sylius.factory.order_item'\n            $orderItemQuantityModifier: '@sylius.modifier.order_item_quantity'\n            $userContext: '@Sylius\\Bundle\\ApiBundle\\Context\\UserContextInterface'\n        tags:\n            - { name: messenger.message_handler, bus: sylius.command_bus }\n```\n\n#### Step 3. Clear cache\n\n```bash\nbin/console cache:clear\n```\n\n### Reporters\n\nWe would like to extend our gratitude to the following individuals for their detailed reporting and responsible disclosure of this vulnerability:\n- @rokorolov\n\n### For more information\nIf you have any questions or comments about this advisory:\n\n- Open an issue in [Sylius issues](https://github.com/Sylius/Sylius/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen)\n- Email us at [security@sylius.com](mailto:security@sylius.com)","published":"2026-03-10T21:25:20.368Z","modified":"2026-08-12T03:51:39.138603193Z","cvss":null,"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Packagist","name":"sylius/sylius","fixedVersion":"2.0.16"},{"ecosystem":"Packagist","name":"sylius/sylius","fixedVersion":"2.1.12"},{"ecosystem":"Packagist","name":"sylius/sylius","fixedVersion":"2.2.3"}],"fix":null,"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/31xxx/CVE-2026-31821.json"},{"type":"ADVISORY","url":"https://github.com/Sylius/Sylius/security/advisories/GHSA-wjmg-4cq5-m8hg"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-31821"},{"type":"PACKAGE","url":"https://github.com/Sylius/Sylius"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:39.138603193Z"}}