Preconditions and environment
- Magento Open Source / Adobe Commerce 2.4.8-p5 with the APSB26-73 security update applied
(isolated patch 248p5-2026-07-001-CE). Confirmed on magento/module-quote 101.2.8-p4 and
magento/module-checkout 100.4.8-p5.
- Magento_Persistent enabled, which is the only extra requirement:
- Stores → Configuration → Customers → Persistent Shopping Cart → Enable Persistence = Yes
- Persist Shopping Cart = Yes
- Clear Persistence on Sign Out = No
- A registered customer with at least one saved address.
This is not the same as #40991 / #40996 / #40997 / #41000 / #41062, which are about the patch
failing to apply or to compile. This is about its behaviour once correctly applied.
Steps to reproduce
- Log in on the storefront with Remember Me checked.
- Add a product to the cart.
- End the session while keeping the persistent cookie: delete the
PHPSESSID cookie in devtools and
leave persistent_shopping_cart in place. (Waiting out session.gc_maxlifetime has the same effect.)
- Reload. The cart is still there and the header shows the "Not you?" link — the shopper is
remembered but not logged in, which is Magento_Persistent working as designed.
- Proceed to checkout, fill in the shipping address and continue to the next step.
Expected result
Checkout completes. Before APSB26-73 the persistent cart was carried through the guest checkout
endpoints and converted to a guest cart at order submit by
Magento\Persistent\Model\Plugin\ConvertCustomerCartToGuest::beforeSubmit().
Actual result
Checkout fails at the shipping step with:
Could not find a cart with ID '<masked_id>'
The quote still exists, is active, and its quote_id_mask row is present and correct.
Root cause
APSB26-73 adds Magento\Quote\Model\GuestCart\GetGuestCart:
public function checkIsGuestCart(int $customerId, string $maskedCartId): void
{
if ($customerId !== 0) {
throw new NoSuchEntityException(
__("Could not find a cart with ID '%masked_cart_id'", ['masked_cart_id' => $maskedCartId])
);
}
}
Magento_Persistent deliberately produces exactly that state — a quote that still carries
customer_id but is addressed by a masked ID — and it does so for the whole duration of checkout:
-
The masked ID is created for a customer-owned cart on purpose.
Magento\Checkout\Model\Session::getQuote() creates a quote_id_mask row when
!isQuoteMasked() && !$this->_customerSession->isLoggedIn() && $this->getQuoteId().
For a remembered shopper isLoggedIn() is false (it ends in && !$this->getIsCustomerEmulated(),
and Magento\Persistent\Observer\EmulateCustomerObserver sets setIsCustomerEmulated(true)), so the
mask is created while the quote still has customer_id.
-
The shopper is routed to the guest endpoints on purpose.
Magento\Persistent\Model\Checkout\ConfigProviderPlugin (mask_quote_id_substitutor) overwrites
quoteData.entity_id in the checkout config with that masked ID whenever
isEnabled() && isPersistent() && !isLoggedIn().
-
The conversion that would make the cart legal happens at the very end.
ConvertCustomerCartToGuest::beforeSubmit() on Magento\Quote\Model\QuoteManagement is what nulls
customer_id. Everything between step 1 and submit is a customer-owned cart being driven through
the guest endpoints — which is precisely the state the new check rejects.
So the check fires before the conversion it is waiting for can ever run.
Affected call sites added by the patch:
| Class |
Method |
Checkout step blocked |
Magento\Checkout\Model\GuestShippingInformationManagement |
saveAddressInformation |
shipping address |
Magento\Checkout\Model\GuestTotalsInformationManagement |
calculate |
totals |
Magento\Checkout\Model\GuestPaymentInformationManagement |
savePaymentInformation |
payment |
Magento\Quote\Model\GuestCart\GuestCartRepository |
get |
any guest cart read |
The payment step shows the conflict most clearly. Magento\Persistent\Model\Checkout\GuestPaymentInformationManagementPlugin::beforeSavePaymentInformation()
runs first and sets customer_is_guest = true, but it does not null customer_id — that is submit's
job — so the check inside the method body still throws. Note that Magento_Persistent registers that
plugin in etc/webapi_rest/di.xml and etc/webapi_soap/di.xml as well as etc/frontend/di.xml, so
persistent carts are expected to work over the REST checkout endpoints.
Reproducing without a browser
Any quote with a customer_id and a quote_id_mask row reproduces it directly:
$om->get(\Magento\Quote\Api\GuestCartRepositoryInterface::class)->get($maskedId);
// Magento\Framework\Exception\NoSuchEntityException:
// Could not find a cart with ID '<masked_id>'
// GuestCartRepository.php:60 GetGuestCart->checkIsGuestCart(<customer_id>, '<masked_id>')
Impact
Any store with Persistent Shopping Cart enabled loses checkout for every shopper whose session has
expired while their remember-me cookie is still valid — the exact scenario the module exists to serve.
Suggested fix
Magento_Persistent should perform the guest conversion when it hands the cart to an unauthenticated
shopper rather than at submit, so customer_id is already cleared before any guest endpoint is
called. QuoteManager::setGuest() already does this work; it simply runs too late.
Alternatively GetGuestCart could permit the cart when the request carries the persistent session of
the customer who owns it — i.e. Magento\Persistent\Helper\Session::isPersistent() and the persistent
session's customer_id matches the quote's. That preserves the security boundary, since an attacker
holding a masked ID would still need that customer's persistent_shopping_cart cookie.
Preconditions and environment
(isolated patch
248p5-2026-07-001-CE). Confirmed onmagento/module-quote 101.2.8-p4andmagento/module-checkout 100.4.8-p5.This is not the same as #40991 / #40996 / #40997 / #41000 / #41062, which are about the patch
failing to apply or to compile. This is about its behaviour once correctly applied.
Steps to reproduce
PHPSESSIDcookie in devtools andleave
persistent_shopping_cartin place. (Waiting outsession.gc_maxlifetimehas the same effect.)remembered but not logged in, which is Magento_Persistent working as designed.
Expected result
Checkout completes. Before APSB26-73 the persistent cart was carried through the guest checkout
endpoints and converted to a guest cart at order submit by
Magento\Persistent\Model\Plugin\ConvertCustomerCartToGuest::beforeSubmit().Actual result
Checkout fails at the shipping step with:
The quote still exists, is active, and its
quote_id_maskrow is present and correct.Root cause
APSB26-73 adds
Magento\Quote\Model\GuestCart\GetGuestCart:Magento_Persistent deliberately produces exactly that state — a quote that still carries
customer_idbut is addressed by a masked ID — and it does so for the whole duration of checkout:The masked ID is created for a customer-owned cart on purpose.
Magento\Checkout\Model\Session::getQuote()creates aquote_id_maskrow when!isQuoteMasked() && !$this->_customerSession->isLoggedIn() && $this->getQuoteId().For a remembered shopper
isLoggedIn()isfalse(it ends in&& !$this->getIsCustomerEmulated(),and
Magento\Persistent\Observer\EmulateCustomerObserversetssetIsCustomerEmulated(true)), so themask is created while the quote still has
customer_id.The shopper is routed to the guest endpoints on purpose.
Magento\Persistent\Model\Checkout\ConfigProviderPlugin(mask_quote_id_substitutor) overwritesquoteData.entity_idin the checkout config with that masked ID wheneverisEnabled() && isPersistent() && !isLoggedIn().The conversion that would make the cart legal happens at the very end.
ConvertCustomerCartToGuest::beforeSubmit()onMagento\Quote\Model\QuoteManagementis what nullscustomer_id. Everything between step 1 and submit is a customer-owned cart being driven throughthe guest endpoints — which is precisely the state the new check rejects.
So the check fires before the conversion it is waiting for can ever run.
Affected call sites added by the patch:
Magento\Checkout\Model\GuestShippingInformationManagementsaveAddressInformationMagento\Checkout\Model\GuestTotalsInformationManagementcalculateMagento\Checkout\Model\GuestPaymentInformationManagementsavePaymentInformationMagento\Quote\Model\GuestCart\GuestCartRepositorygetThe payment step shows the conflict most clearly.
Magento\Persistent\Model\Checkout\GuestPaymentInformationManagementPlugin::beforeSavePaymentInformation()runs first and sets
customer_is_guest = true, but it does not nullcustomer_id— that is submit'sjob — so the check inside the method body still throws. Note that Magento_Persistent registers that
plugin in
etc/webapi_rest/di.xmlandetc/webapi_soap/di.xmlas well asetc/frontend/di.xml, sopersistent carts are expected to work over the REST checkout endpoints.
Reproducing without a browser
Any quote with a
customer_idand aquote_id_maskrow reproduces it directly:Impact
Any store with Persistent Shopping Cart enabled loses checkout for every shopper whose session has
expired while their remember-me cookie is still valid — the exact scenario the module exists to serve.
Suggested fix
Magento_Persistentshould perform the guest conversion when it hands the cart to an unauthenticatedshopper rather than at submit, so
customer_idis already cleared before any guest endpoint iscalled.
QuoteManager::setGuest()already does this work; it simply runs too late.Alternatively
GetGuestCartcould permit the cart when the request carries the persistent session ofthe customer who owns it — i.e.
Magento\Persistent\Helper\Session::isPersistent()and the persistentsession's
customer_idmatches the quote's. That preserves the security boundary, since an attackerholding a masked ID would still need that customer's
persistent_shopping_cartcookie.