CVE-2026-81892: EasyAdmin custom-action dispatcher bypasses access_control on other routes
Summary
EasyAdmin serves all backend requests through a single dashboard route and, for custom actions (Action::linkToRoute() / MenuItem::linkToRoute()), swaps the executed controller based on the routeName query parameter on the kernel.controller event.
That swap happens after Symfony's security firewall has already evaluated accesscontrol against the original dashboard URL, and the routeName value was not validated. As a result, a path-based accesscontrol rule protecting the target route was never evaluated, so a low-privilege backend user could reach a more restricted route by name.
Impact
Any application where accesscontrol (or another path-based Symfony security rule) protects some routes more strictly than the dashboard URL used to reach EasyAdmin. An attacker who can reach a single EasyAdmin URL and knows a target route's name can execute that route's controller, bypassing the path-based rule.
Only path-based protections are bypassed. Routes whose controller enforces its own authorization with #[IsGranted] / denyAccessUnlessGranted() remain protected, because those checks are recomputed against the swapped-in controller.
Patches
Fixed in 4.29.16 and 5.5.1. Before dispatching a custom-action route, EasyAdmin now re-evaluates the target route's accesscontrol rule and denies the request if the current user is not granted access.
Workarounds
Add controller-level authorization (#[IsGranted] / denyAccessUnlessGranted()) to any sensitive route, since controller-level checks are still enforced. Upgrading is the recommended fix.
Credits
Reported by @TungNGo02.
Other sources
EasyAdmin is a fast and modern admin generator for Symfony applications. From 4.0.0 until 4.29.16 and 5.5.1, EasyAdmin serves all backend requests through a single dashboard route and, for custom actions (Action::linkToRoute() and MenuItem::linkToRoute()), swaps the executed controller based on the routeName query parameter on the kernel.controller event. The swap happens after Symfony's security firewall has already evaluated accesscontrol against the original dashboard URL, and the routeName value was not validated. As a result, a path-based accesscontrol rule protecting the target route was never evaluated, so a low-privilege backend user who can reach a single EasyAdmin URL and knows a target route's name can execute that route's controller, bypassing the path-based rule. Only path-based protections are bypassed. Routes whose controller enforces its own authorization with #[IsGranted] or denyAccessUnlessGranted() remain protected because those checks are recomputed against the swapped-in controller. This issue is fixed in versions 4.29.16 and 5.5.1.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
composer/easycorp/easyadmin-bundleto a version that resolves this vulnerability.Fixed in 5.5.1 - Upgrade
Upgrade
composer/easycorp/easyadmin-bundleto a version that resolves this vulnerability.Fixed in 4.29.16 - Upgrade
Upgrade
EasyAdminto a version that resolves this vulnerability.Fixed in 4.29.16 - Upgrade
Upgrade
EasyAdminto a version that resolves this vulnerability.Fixed in 5.5.1 - Compensating control
Add controller-level authorization checks to sensitive routes in your Symfony app, e.g., use `#[IsGranted]` / `denyAccessUnlessGranted()` on the controller methods for any sensitive route, since controller-level checks are enforced even after EasyAdmin’s custom-action route swapping.
Event History
Frequently Asked Questions
Which deployments are exposed to this bypass?
Deployments using EasyAdmin versions from 4.0.0 through 4.29.16, or version 5.5.1, are affected when they expose custom actions via Action::linkToRoute() or MenuItem::linkToRoute() and rely on path-based Symfony access_control rules for the target routes. A low-privilege backend user must be able to reach at least one EasyAdmin URL.
What does an attacker need to exploit the issue?
The attacker needs low-privilege access to the backend, access to a reachable EasyAdmin URL, and knowledge of the target route name. They can supply that name through the routeName query parameter to cause EasyAdmin to execute the target controller without Symfony evaluating the target path's access_control rule.
Are routes protected by controller-level authorization affected?
No. Authorization enforced by #[IsGranted] or denyAccessUnlessGranted() remains effective because those checks are evaluated against the controller that EasyAdmin swaps in.
What should be done if updating cannot happen immediately?
Do not rely solely on path-based access_control rules for routes reachable through EasyAdmin custom-action dispatch. Add controller-level authorization using #[IsGranted] or denyAccessUnlessGranted() for sensitive target routes until the fixed version can be deployed.
How can teams identify potentially affected routes?
Review EasyAdmin custom actions and menu items created with Action::linkToRoute() or MenuItem::linkToRoute(). Identify target routes protected only by path-based access_control and lacking controller-level authorization checks.