PS Summit! We’re organizing a major event
6 min. read
Categories: Security
Security release: Sylius 1.12.25, 1.13.17, 1.14.20, 2.1.16 and 2.2.9

Security is an ongoing commitment; sometimes, despite our best efforts, vulnerabilities slip through the cracks. Over the past weeks we received several independent security reports concerning the API authentication layer, administrator account recovery, and the Payment Request pipeline. Rather than fixing each report in isolation, we traced every one of them back to its root cause and reviewed the surrounding code paths – which is why some of the patches below sit deeper in the framework than the reported symptom, and why one of them extends an area we had already touched in the June advisories.

Four distinct advisories came out of that work. Two of them – the JWT audience confusion and the administrator password-reset link – allow a full takeover of an administrator account, and both need nothing more than an administrator’s e-mail address to get started. The other two concern payments: one lets a customer have an order marked as fully paid after paying only a fraction of its total, the other lets a customer trigger a refund at the payment service provider for their own paid order.

If you are using Sylius in versions below 1.12.25, 1.13.17, 1.14.20, 2.1.16 or 2.2.9, we strongly urge you to update immediately. Releases older than those lines – 2.0.x and 1.x below 1.12 – are affected by the issues that apply to their code base and are no longer covered by security support; the only remedy there is an upgrade, or the per-issue workarounds documented in the advisories.

Two of these upgrades change observable behaviour, so plan them rather than deploying them blind – both are described under their respective advisories below: JWTs issued before the upgrade stop being accepted, and applications that override the administrator password-reset e-mail template have to update that override.

Keeping your store secure is our top priority, and we appreciate your trust and vigilance in maintaining a safe eCommerce environment. Below, we outline the details of each issue, its impact, and the necessary steps to protect your business.

Sylius 1.12 and above

CVE-2026-?: JWT audience confusion allows shop customer to authenticate as administrator

The original security advisory has been published on GitHub at Sylius/Sylius repository.

Impact

Sylius issues JWTs from two separate firewalls, one for the Admin API and one for the Shop API, but the tokens carried no claim identifying which firewall had issued them. Each firewall validated only the token’s signature and then resolved the subject through its own user provider, keyed by e-mail address. Because administrators and shop customers live in separate tables, the same e-mail address can legitimately exist as both – so a token minted for a shop customer resolved, on the admin firewall, to the administrator sharing that address.

Exploiting it required nothing but the address: register as a customer in the shop using an administrator’s e-mail address, log in to the Shop API with your own password, and send the resulting token to the Admin API, which granted full administrative access. The same confusion applied in the opposite direction, with an administrator’s token accepted by the Shop API.

Patches

The issue is fixed in versions: 1.12.25, 1.13.17, 1.14.20, 2.1.16, 2.2.9 and above. Tokens are now stamped with an aud claim and a principal_type claim on creation, and rejected on decoding when they do not match the firewall handling the request.

Two consequences are worth planning for. Tokens issued before the upgrade carry neither claim and are rejected by both API sections afterwards, so every API client has to authenticate once again – this is intentional. And if your application renamed either API firewall, or serves the API through an additional JWT firewall, extend the listener’s firewall map accordingly, otherwise the tokens of those firewalls are rejected too.

Workarounds

If an immediate upgrade is not possible, replace the JWT authenticators of both API firewalls with subclasses that verify the audience and principal type, and add a listener that stamps both claims on token creation. See the full workaround in the advisory.

CVE-2026-?: Administrator password-reset link is built from the request Host header

The original security advisory has been published on GitHub at Sylius/Sylius repository.

Impact

Anyone able to trigger a password-reset e-mail for an administrator – which needs only that administrator’s e-mail address and a single unauthenticated request – could make the reset link in that e-mail point at a domain of their choosing. Sylius built the absolute reset URL with Twig’s url() helper, which derives scheme and host from the incoming request; Symfony resolves those from the Host header unless framework.trusted_hosts is configured, and Sylius ships no default for it.

The e-mail was otherwise entirely genuine – real sender, real branding, real token – and only the link’s domain was attacker-controlled. When the administrator clicked it, the valid reset token reached the attacker’s server, who could then set a new password on the real shop. Both the /admin/forgotten-password form and the Admin API reset endpoint behaved the same way. The shop-side password reset was never affected, because it builds its link from the channel’s configured hostname.

Patches

The issue is fixed in versions: 1.12.25, 1.13.17, 1.14.20, 2.1.16, 2.2.9 and above. The administrator reset URL is no longer derived from the request: it is built from the hostname configured on the channel resolved for that request. When no channel can be resolved, or the channel has no hostname set, the e-mail contains the bare reset token instead of a link, which the administrator pastes into the shop manually.

If your application overrides the adminPasswordReset.html.twig e-mail template, update that override – otherwise it keeps building the link from the request and remains vulnerable. To keep administrators receiving a clickable link, make sure the channel serving the administration panel has its hostname set.

Workarounds

Pinning framework.trusted_hosts, or rejecting unknown Host values at the web server, closes this and every other Host-header injection in the application – it is worth doing on every installation, patched or not. Alternatively, decorate the reset-password mailer so it builds the URL from the channel hostname. See the full workaround in the advisory.

Sylius 2.1 and above

CVE-2026-?: Order total can be inflated after a gateway transaction has started, marking the order fully paid

The original security advisory has been published on GitHub at Sylius/Sylius repository.

Impact

A guest, with no authentication and no administrator involvement, could pay a small, legitimate amount for an order, enlarge that same order after the gateway had already captured the funds, and have Sylius mark the enlarged order as fully paid – while the gateway’s ledger showed only the original, much smaller capture.

OrderPaymentProcessor rewrote the last payment’s amount and currency to the order’s current total on every cart recalculation, with no check for whether that payment had already been handed to a gateway; OrderPaymentsRemover had the mirror problem when the total reached zero. When the gateway later reported success, Sylius verified the status but never the captured amount, and resolved the order as paid. Offline gateways such as bank_transfer do not reproduce this, because their capture moves the payment out of cart state synchronously – an accidental side effect rather than a safeguard. Asynchronous gateways such as Stripe, PayPal and Adyen keep the payment mutable until a status check or webhook resolves it, and get no such protection.

Patches

The issue is fixed in versions: 2.1.16, 2.2.9 and above. Both classes now check whether a payment has an outstanding capture or authorize payment request before touching it: a claimed payment is never overwritten or removed, and an order whose total grew gets a new payment for the difference. The order then ends up with the original payment for the amount that actually arrived plus one for the outstanding difference, and its payment state becomes awaiting_payment or partially_paid rather than paid.

Workarounds

If an immediate upgrade is not possible, decorate the order payment processor – both the checkout and the after-checkout instance – and the order payments remover, so that payments already claimed by a gateway are left alone. See the full workaround in the advisory.

CVE-2026-?: Unrestricted Payment Request action on the Shop API endpoint allows a customer-triggered refund

The original security advisory has been published on GitHub at Sylius/Sylius repository.

Impact

The Shop API endpoint for creating payment requests accepted any action value the caller supplied, stored it verbatim, and announced it on the payment request command bus. The only authorization performed was order ownership; nothing constrained which action a shop-context caller may request.

Gateways resolve that action through an action-indexed command provider, which is the documented way to implement operations such as refunds. Where a gateway wires refundcancel or payout into that provider, a customer could request a refund against their own paid order and have it executed at the payment service provider, while Sylius kept the payment completed and the order paid – so the money went back to the customer and the merchant still saw an order to fulfil. A plain Sylius-Standard installation ships no gateway that wires such an action, so a default install is not exploitable; installations running a gateway integration that exposes refund-class actions are.

This is a different issue from the June advisory about ownership on the same endpoints (CVE-2026-53639): here the attacker is the legitimate owner of the order, so every ownership check added there passes. The two fixes are complementary.

Patches

The issue is fixed in versions: 2.1.16, 2.2.9 and above. The requested action is now validated against an allowlist before the payment method is looked up and before any gateway command provider is consulted, and rejected with 422 Unprocessable Entity otherwise. The allowlist defaults to captureauthorizestatus and notify – so refundcancelpayout and sync are refused from shop context – and is configurable through sylius_api.shop_payment_request.allowed_actions. Administrator-initiated refunds are unaffected: they go through the payment state machine, not through this endpoint.

If you maintain a gateway plugin, this is a good moment to review how it exposes privileged actions: an action wired into the shop-resolvable command provider is reachable by whoever can create a payment request, so refunds are better dispatched from a trusted code path than from caller-supplied data.

Workarounds

If an immediate upgrade is not possible, reject disallowed actions on that endpoint with a single kernel event listener, before the command is built and announced. See the full workaround in the advisory.

Summary

Checking whether you were affected

The two administrator-takeover issues leave traces worth looking for. After upgrading, every token rejected for a mismatched audience is logged, so a spike there indicates active attempts; reviewing which administrator e-mail addresses also exist as shop customers tells you which accounts were reachable in the first place. For the reset-password issue, look for requests to the administrator reset endpoints whose Host header does not match a canonical shop domain, and treat any administrator whose password was reset through a link they did not request as compromised.

For the payment issues, reconcile Sylius against the gateway rather than against itself: for every order marked as paid, compare what the gateway reports as captured with the order total, and check for payment requests whose action is outside captureauthorizestatus and notify.

Keeping your store secure is crucial – please update your Sylius installation and inform your peers in the community about these fixes to ensure a safer environment for all.

We would like to thank everyone who participated in responsible disclosure of these vulnerabilities. If you believe you have found a security vulnerability in Sylius, please do not post it publicly. Instead, report it to [email protected].

Tags: news Sylius
Share:
Kamil Grygierzec
Kamil started to be interested in programming in 2016, because of the local CoderDojo sessions. He always wanted to be front-end developer, but at the end felt more suitable in back-end. IT technician since childhood, huge IOT enthusiast, occasionally football player and madridista wholeheartedly.
More from our blog
Ecosystem News Sylius 6 min read 01.09.2026
Stripe has officially joined the Sylius ecosystem as our Preferred Payment Partner, marking another important step in the development of our payments ecosystem. Stripe is a financial infrastructure platform for businesses, used by millions of companies to accept payments and grow revenue.  This partnership also represents… Read More
Ecosystem 6 min read 12.08.2026
Summer slowdown? Not here. While half of Europe was off chasing sunshine, the Sylius ecosystem kept shipping, and July turned into one of the busiest months yet, headlined by a new official partnership with Stripe and a fresh MCP tool for admin panel. From new go-lives to amazing events, the… Read More
Ecosystem News 6 min read 20.07.2026
THREE YEARSCOUNTLESS CONTRIBUTIONSONE INCREDIBLE COMMUNITY It’s been 3 years since the first Month of Sylius launched as we know it today – though it wasn’t our first attempt at a monthly recap: an earlier numbered summary series ran from #1 to #9 back in April 2018. Since then, the community… Read More
Comments