Twig in the control panel: Craft's sandbox keeps getting escaped
Here's a thing Craft lets you do that made my Drupal brain itch the first time I saw it: a logged-in control-panel user can type Twig into a settings field, and the server will run it. Entry URL formats, title templates, asset paths — these "object templates" are live Twig, authored through the admin UI. Craft sandboxes it. And over July to October 2026, that sandbox got escaped, repeatedly, in a run of authenticated remote-code-execution advisories that taught me to watch the Craft changelog the way I watch a Drupal security Wednesday.
The setup: Twig in the control panel
In Craft, "object templates" are single-line Twig snippets, usually defined in project config and rendered at runtime — an entry's URI, a title format, an asset subpath. Convenient, and genuinely powerful. There's an opt-in sandbox, enableTwigSandbox, added in 5.9.0 and off by default, which the docs describe only as "whether user-defined Twig templates should be sandboxed." (That the docs don't spell out what it actually restricts is, itself, a small tell.)
The security model here is fundamentally different from Drupal's, and that's the whole story. Drupal's stance is: code runs only from deployed files; admins configure, they don't author executable logic through the UI. Drupal even removed the old PHP filter module from core back in Drupal 8 and pushed it to contrib, precisely because "let admins run code from the database" is a footgun. Craft's stance is the opposite bet: authenticated control-panel users can write Twig that executes in the server process, and we'll sandbox it.
The run of escapes
Sandboxing a dynamic PHP object graph — Yii components, behaviors, event handlers — turns out to be a hard, long-tail problem, and the 2026 advisories are the proof. A sample of what shipped:
- CVE-2026-72781 (GHSA-f5wm-88jv-g5hx, CVSS 8.8, published 6 August, fixed in 5.10.7/4.18.3): the sandbox allowlists whole classes including inherited members.
ElementInterfaceis marked safe, and the concreteElementinherits fromyii\base\Component, which carries a function-call gadget used in earlier RCEs. It works with the sandbox enabled, and needs only a low-privilege control-panel account. - CVE-2026-55794 (July, fixed in 5.10.0): on entry save, the HTTP
Refererheader was rendered as unsandboxed Twig. A header. As template code. - An October batch (nine advisories, all affecting versions before 5.11.0 and patched in it): the Twig
groupfilter could be coaxed into calling an array-callable like['craft\helpers\FileHelper','writeToFile']— i.e. write a webshell (GHSA-xmwr-88vw-5ghh); andgetNext()/getPrev()/getOwners()passed criteria toCraft::configure(), which honors Yii'sonandaskeys, letting an attacker inject an event handler (GHSA-wr79-9v6x-5rfq).
By my own count of the changelog (grepping the severity tags, so read that as "my tally," not an official figure), the 5.9→5.11.5.1 window carried dozens of severity-tagged fixes, a double-digit share of them RCE. That's a lot of sandbox-escape surface in one minor series.
A sandbox around a language with access to a live PHP object graph isn't a wall — it's an ongoing argument with every class the allowlist transitively reaches. Drupal sidestepped that argument by never handing admins the gun.
Credit where it's due — and no gloating
I want to be fair, because the lazy version of this post ("Craft is insecure!") is both unkind and wrong. Craft's fix cadence is fast and its process is transparent: a published security policy, severity-rated releases, and a 30-day disclosure window (that's why the 1 September 5.11.0 fixes were publicly detailed on 5 October — a patch-adoption window, in their words, not a secrecy one). And Drupal is in no position to throw stones: as I write this there's a critical PSA-2026-10-12 warning of a core security release landing 14 October. Both ecosystems patch constantly. That's not a weakness of either; it's what maintained software looks like.
The honest difference is architectural, not about who has more CVEs. Craft chose editor-authored Twig as a feature and accepted a sandbox as the price; Drupal chose to keep executable logic in deployed code and accepted less flexibility as the price. Neither is wrong. But they ask different things of you as the operator.
What I actually do on a Craft site
- Treat the control panel as a privileged surface, even for "just editors." In a world where a low-privilege CP account has been an RCE vector more than once this year, "they can only edit content" is not the reassurance it sounds like.
- Know your
enableTwigSandboxposture on purpose — on or off, decided deliberately, not left at whatever the default was. - Patch within days, not sprints. These are authenticated RCEs with public advisories 30 days later; the window between disclosure and exploitation is exactly that patch-adoption month.
- Keep
allowAdminChanges=falsein production — which also narrows who can author object templates there at all, and which Craft's own policy treats as the expected hardening.
Coming from Drupal, the mental adjustment isn't fear — it's a new reflex. I used to only think about admin-authored code as a Drupal 7-era mistake we'd designed away. On Craft it's a current, first-class feature with a current, first-class attack surface, and the correct response is the boring one: watch the changelog, patch fast, and respect the control panel as production.