Twig 3.27.0 ships five sandbox security fixes (one low, four medium) and a new opt-in strict mode for SecurityPolicy that lets you adopt the forthcoming 4.0 sandbox defaults today. We recommend upgrading any application that renders user-controlled templates in a sandbox; details and CVE references are below so you can assess your exposure.

Sandbox: opt into 4.0 strict mode today

Fabien Potencier
Contributed by Fabien Potencier in #4813 and #4817

In a sandbox, the extends and use tags as well as the parent, block, and attribute functions have always been implicitly allowed, regardless of what your SecurityPolicy declared. Twig 3.12 started emitting a deprecation for the two tags; this release extends it to the three functions, because 4.0 will require all of them to be listed explicitly.

To preview the 4.0 behavior on 3.x (and silence the deprecations), enable strict mode on SecurityPolicy:

1
2
3
4
5
6
7
8
use Twig\Sandbox\SecurityPolicy;

$policy = new SecurityPolicy(
    allowedTags: ['extends', 'if', 'for'],
    allowedFilters: ['escape', 'upper'],
    allowedFunctions: ['parent', 'block'],
);
$policy->setStrict(true);

With strict mode on, every tag and every function must appear in the relevant allow-list to be usable. Any template that relied on the historical implicit allowance will throw a SecurityNotAllowedTagError or SecurityNotAllowedFunctionError, which is exactly what 4.0 will do.

Profiler: harden HtmlDumper and Profile::unserialize()

Fabien Potencier
Contributed by Fabien Potencier in #4807 and #4808

Two hardening fixes land in the profiler. HtmlDumper now escapes the root profile name, so a profile created with a hostile name no longer emits raw HTML into the dump output. And Profile::unserialize() now restricts the classes PHP is allowed to instantiate to Profile itself, preventing arbitrary __unserialize() / __wakeup() magic methods from running when an untrusted profile blob is deserialized.

SourcePolicyInterface is deprecated

Fabien Potencier
Contributed by Fabien Potencier in #4803

Twig\Sandbox\SourcePolicyInterface is now deprecated with no replacement. The feature sees no real-world use, and the recommended way to render templates written by untrusted authors is to point a dedicated sandboxed Environment at a loader that restricts what those templates can see, rather than toggling sandbox state per source from within a shared environment.

Security fixes

This release fixes five sandbox vulnerabilities. All of them are policy enforcement gaps rather than direct code execution issues; the realistic impact is unauthorized disclosure of data exposed through __toString() methods or property allow-lists. Applications that do not render sandboxed templates are unaffected.

Medium

A cached Template instance kept the filter, tag, and function allow-list verdict computed at construction time, so any later change of sandbox state on the same Environment was ignored. Long-lived workers (FrankenPHP, RoadRunner, Symfony Messenger consumers) sharing a single Environment between sandboxed and non-sandboxed renders are the most exposed: a non-sandboxed render of a shared layout pre-warmed its instance, after which any sandboxed render that extended, included, or imported from that layout silently skipped the allow-list. The check now runs at every entry point and is re-evaluated against the current sandbox state (CVE-2026-46636).

A dynamic mapping key such as {% set arr = {(obj): "value"} %} emitted a raw (string) cast on the key expression, so PHP invoked __toString() on the resolved object without ever consulting the sandbox policy. ArrayExpression now declares its dynamic keys as string-coercion sites, and as a side effect any expression (not only context variables) is accepted as a dynamic mapping key (CVE-2026-48806).

The join and replace filters, and the in / not in operators, coerced Stringable objects to strings without going through ensureToStringAllowed(). Both filters materialize Traversable arguments through implode() / strtr(); the operators fall through to PHP's <=> comparison. The fix recurses into Traversable operands when computing the policy check, and wraps both sides of in / not in so the policy is consulted before any coercion happens (CVE-2026-48807).

When sandboxing was enabled through SourcePolicyInterface, the column filter routed property reads through SandboxExtension with a null Source, which short-circuited the per-source decision and skipped the property allow-list entirely. The filter now calls the security policy directly using the sandbox state already computed at the call site (CVE-2026-48808).

Low

The deprecated internal wrappers in src/Resources/core.php (twig_check_arrow_in_sandbox(), twig_array_some(), twig_array_every()) were not updated when 3.26.0 changed the underlying CoreExtension signatures to take an explicit $isSandboxed boolean. As a result, array.some and array.every silently dropped the Closure-only restriction when invoked through the wrappers, and twig_check_arrow_in_sandbox() threw a TypeError on PHP 8+. The wrappers now resolve the sandbox state through the same helper that compiled templates use. Compiled templates themselves were never affected (CVE-2026-48805).

Credits

Thanks to El Kharoubi Iosif for reporting CVE-2026-48805 and CVE-2026-48806, Vincent55 Yang for reporting CVE-2026-48808 and co-reporting CVE-2026-48807, and Fabien Potencier for reporting CVE-2026-46636, co-reporting CVE-2026-48807, and providing the fixes for all five advisories.

Full Changelog

  • CVE-2026-46636 Fix sandbox filter/tag/function allow-list bypass when sandbox state changes between renders
  • CVE-2026-48805 Fix sandbox bypass in deprecated internal wrappers
  • CVE-2026-48806 Fix sandbox __toString policy bypass via dynamic mapping keys
  • CVE-2026-48807 Fix sandbox __toString bypasses via Traversable in join/replace filters and the in/not in operators
  • CVE-2026-48808 Fix sandbox bypass in the column filter under SourcePolicyInterface
  • #4817 Add a strict mode to SecurityPolicy to opt-in to the 4.0 sandbox behavior for the extends/use tags and the parent/block/attribute functions (@fabpot)
  • #4813 Deprecate the fact that the parent, block, and attribute functions are always allowed in a sandboxed template (@fabpot)
  • #4812 Fix PHP 8.1+ implicit float-to-int deprecation in sandboxed array access (@fabpot)
  • #4807 Escape root profile name in HtmlDumper (@fabpot)
  • #4808 Restrict allowed classes in Profile::unserialize() (@fabpot)
  • #4803 Deprecate the Twig\Sandbox\SourcePolicyInterface interface (@fabpot)
Published in #Releases #Twig