Rewrite the sandbox documentation around what developers need to know

This commit is contained in:
Fabien Potencier
2026-09-26 10:02:08 +02:00
parent 2904612341
commit 9d24327f8d
3 changed files with 222 additions and 337 deletions
+2
View File
@@ -216,6 +216,8 @@ the first argument to the filter call (or the second one if
// ...
}, ['needs_context' => true, 'needs_environment' => true]);
.. _sandbox-aware-filters:
Sandbox-aware Filters
~~~~~~~~~~~~~~~~~~~~~
+4 -3
View File
@@ -432,8 +432,8 @@ Twig comes bundled with the following extensions:
* ``\Twig\Extension\EscaperExtension``: Adds automatic output-escaping and the
possibility to escape/unescape blocks of code.
* ``\Twig\Extension\SandboxExtension``: Adds a sandbox mode to the default Twig
environment, making it safe to evaluate untrusted code.
* ``\Twig\Extension\SandboxExtension``: Enforces the sandbox security policy;
use the ``Twig\Sandbox\Sandbox`` class to evaluate untrusted code.
* ``\Twig\Extension\ProfilerExtension``: Enables the built-in Twig profiler.
@@ -580,7 +580,8 @@ The escaping rules are implemented as follows:
Sandbox Extension
~~~~~~~~~~~~~~~~~
The ``sandbox`` extension can be used to evaluate untrusted code. Read more
To evaluate untrusted code, use the ``Twig\Sandbox\Sandbox`` class, which
registers the ``sandbox`` extension on a dedicated environment. Read more
about it in the :doc:`sandbox` chapter.
Profiler Extension
+216 -334
View File
@@ -1,413 +1,295 @@
Twig Sandbox
============
The sandbox can be used to evaluate untrusted code, restricting what template
authors can reach through explicit allow-lists.
The sandbox renders templates written by untrusted authors, such as your
users. A sandboxed template can only use the tags, filters, functions, and
tests you allow, and only access the object methods and properties you allow.
.. warning::
Twig treats template source as trusted code by default. If an application
accepts templates from untrusted users, it must enable and correctly
configure the sandbox. The regular Twig environment is not a security
boundary, and any behavior caused by rendering an untrusted template
without the sandbox is not a security issue in Twig.
Twig treats templates as trusted code. The sandbox is the only security
boundary for templates written by untrusted authors: rendering such a
template without it gives its author the same power as your application
code.
Rendering Untrusted Templates
-----------------------------
.. versionadded:: 3.29
The ``Twig\Sandbox\SandboxInterface`` interface and
``Twig\Sandbox\Sandbox`` class were added in Twig 3.29.
The ``Twig\Sandbox\Sandbox`` class and its ``SandboxInterface`` were added
in Twig 3.29.
The recommended way to render untrusted templates is the
``Twig\Sandbox\Sandbox`` class, which implements ``SandboxInterface``. Type-hint
``SandboxInterface`` when injecting a sandbox into an application service. A
``Sandbox`` takes ownership of an environment crafted specifically for it and
renders everything through it in
sandbox mode. Being in full control of that environment, you decide exactly
what untrusted templates can reach: its loader defines which templates exist,
the extensions, filters, functions, tests, and globals you register on it
define which capabilities exist, and the security policy defines what is
allowed to execute::
Create a ``Twig\Sandbox\Sandbox`` from an environment dedicated to untrusted
templates and a security policy::
use Twig\Environment;
use Twig\Extra\Intl\IntlExtension;
use Twig\Loader\ArrayLoader;
use Twig\Sandbox\Sandbox;
use Twig\Sandbox\SecurityPolicy;
// craft an environment dedicated to untrusted templates
$env = new Environment(new ArrayLoader($untrustedTemplates), [
$env = new Environment(new ArrayLoader($userTemplates), [
'cache' => '/path/to/sandbox/cache',
]);
// register the capabilities untrusted templates may use
$env->addExtension(new IntlExtension());
$policy = new SecurityPolicy(
allowedTags: ['if'],
allowedFilters: ['upper', 'escape'],
allowedTags: ['if', 'for'],
allowedFilters: ['escape', 'upper'],
allowedMethods: [Article::class => ['getTitle']],
);
$policy->setStrict(true);
$sandbox = new Sandbox($env, $policy);
// render a template known to the environment loader
echo $sandbox->render('newsletter.twig', ['name' => 'Fabien']);
echo $sandbox->render('newsletter.twig', ['article' => $article]);
// render an untrusted template held as a string
echo $sandbox->createTemplate($userTemplate)->render(['name' => 'Fabien']);
// render a template held as a string
echo $sandbox->createTemplate($source)->render(['article' => $article]);
The environment must be dedicated to the sandbox: build a fresh environment
and pass it before its first use (the constructor throws a ``LogicException``
otherwise). In particular, never pass your main application environment: all
your application templates would suddenly be rendered in sandbox mode.
Keeping the two environments separate also guarantees isolation in both
directions: the sandbox cannot load or affect application templates, and
application renders happening while a sandboxed render is in flight are not
sandboxed.
Everything rendered through the sandbox is sandboxed: ``render()``,
``display()``, and ``stream()`` render a template; ``renderBlock()``,
``displayBlock()``, and ``streamBlock()`` render one of its blocks; and
``createTemplate()`` creates a template from a string. Templates included by
a sandboxed template are sandboxed as well. Type-hint ``SandboxInterface``
when injecting a sandbox into your services.
A ``SecurityPolicy`` passed to the sandbox must be strict (call
``setStrict(true)``) so it behaves the same way in Twig 3.x and 4.0; the
constructor throws a ``LogicException`` otherwise.
The environment defines everything untrusted templates can reach: its loader
defines which templates exist, and the extensions, filters, functions, tests,
and globals registered on it define which capabilities exist. That's why it
must be dedicated to the sandbox: build a new environment and pass it to the
sandbox before using it; the constructor throws a ``LogicException`` if the
environment was already used. Never pass your application environment: every
application template would then be rendered in the sandbox.
Everything rendered through a ``Sandbox`` is sandboxed: ``render()``,
``display()``, and ``stream()`` render a template from the environment loader
by name; ``renderBlock()``, ``displayBlock()``, and ``streamBlock()`` render a
single block of such a template; ``createTemplate()`` turns a string into a
sandboxed template. Templates included by a sandboxed template are sandboxed
as well.
A ``SecurityPolicy`` must be strict (``setStrict(true)``): a non-strict policy
allows some tags, functions, and tests implicitly and behaves differently from
Twig 4.0. The constructor throws a ``LogicException`` otherwise.
Data is passed through the render context (or registered as globals on the
environment you crafted); the policy governs any method or property access on
those values either way.
To render an untrusted template from one of your templates, use the
:doc:`render_sandboxed <functions/render_sandboxed>` function.
Rendering From a Trusted Template
---------------------------------
Configuring the Security Policy
-------------------------------
To render an untrusted template from a trusted template, use the
:doc:`render_sandboxed() function <functions/render_sandboxed>`.
A ``SecurityPolicy`` allow-lists what templates can use; everything else is
rejected with a ``Twig\Sandbox\SecurityError`` exception::
.. note::
When auto-escaping is enabled (the default), the ``escape`` filter is
applied to every printed expression, so it must be part of the filter
allow-list for sandboxed templates to render.
.. caution::
PHP code invoked during a sandboxed render (a filter, function, or
extension you registered on the sandbox environment) runs with its full
PHP capabilities: the sandbox only restricts what the template source can
express. Only register extensions and callables that are safe to call
with attacker-chosen arguments.
Using the Sandbox Extension Directly
------------------------------------
.. deprecated:: 3.29
The ``SandboxExtension`` is internal as of Twig 3.29 and should not be
used directly anymore; the ``sandboxed`` argument of the ``include``
function and the ``enableSandbox()``, ``disableSandbox()``, and
``isSandboxedGlobally()`` methods are deprecated. Use the ``Sandbox``
class instead.
Before the ``Sandbox`` class existed, sandboxing was configured by registering
the ``SandboxExtension`` on the environment via the ``addExtension()``
method::
$twig->addExtension(new \Twig\Extension\SandboxExtension($policy));
By default, the sandbox mode is then disabled and gets enabled when including
untrusted template code by using the ``sandboxed`` option of the ``include``
function:
.. code-block:: twig
{{ include('user.html.twig', sandboxed: true) }}
You can also sandbox all templates by passing ``true`` as the second argument
of the extension constructor::
$twig->addExtension(new \Twig\Extension\SandboxExtension($policy, true));
Configuring the Sandbox Policy
------------------------------
The security policy is enforced the same way whether templates are rendered
through a ``Sandbox`` or through the ``SandboxExtension`` directly.
The sandbox security is managed by a policy instance, which must be passed to
the ``SandboxExtension`` constructor.
By default, Twig comes with one policy class: ``\Twig\Sandbox\SecurityPolicy``.
This class allows you to allow-list some tags, filters, functions, and
properties and methods on objects::
$tags = ['if'];
$filters = ['upper'];
$methods = [
'Article' => ['getTitle', 'getBody'],
];
$properties = [
'Article' => ['title', 'body'],
];
$functions = ['range'];
$tests = ['my_test'];
$policy = new \Twig\Sandbox\SecurityPolicy($tags, $filters, $methods, $properties, $functions, $tests);
With the above configuration, the security policy will only allow usage of the
``if`` tag, the ``upper`` filter, and the ``my_test`` test (on top of the
built-in tests that are always allowed, see below). Moreover, the templates
will only be able to call the ``getTitle()`` and ``getBody()`` methods on
``Article`` objects, and the ``title`` and ``body`` public properties.
Everything else won't be allowed and will generate a
``\Twig\Sandbox\SecurityError`` exception.
.. note::
The ``allowedTests`` argument is available since Twig 3.28 (in earlier
versions all tests were always allowed). Most built-in tests (``empty``,
``defined``, ``even``, ``same as``, ``iterable``, etc.) are always allowed
and do not need to be listed. Only custom tests and the built-in
``constant`` test must be allow-listed like filters and functions.
.. note::
As of Twig 3.14.1 (and on Twig 3.11.2), if the ``Article`` class implements
the ``ArrayAccess`` interface, the templates will only be able to access
the ``title`` and ``body`` attributes.
Note that native array-like classes (like ``ArrayObject``) are always
allowed, you don't need to configure them.
.. note::
When an attribute resolves through a PHP magic ``__call()`` method (the
class has no real method or property with that name), the sandbox checks
the **virtual method name written in the template**, not ``__call``. For
example, ``{{ article.slug }}`` on an object that handles ``slug`` via
``__call()`` requires ``slug`` in the method allow-list::
$methods = [
'Article' => ['slug'],
];
Allow-listing ``__call`` itself has no effect: it would only match a
template that literally writes ``{{ article.__call }}``. Allow each virtual
method by its own name so the policy stays granular.
.. caution::
The ``extends`` and ``use`` tags, the ``parent``, ``block``, and
``attribute`` functions, the ``constant`` test, and any custom test are
always allowed in a sandboxed template. That behavior will change in 4.0
where they will need to be explicitly allowed like any other tag, filter,
function, or test. To opt-in to the 4.0 behavior now (so they need to be
allow-listed or get rejected), enable strict mode on the security policy::
$policy->setStrict(true);
Marking Filters, Functions, Tests, and Tags as Always Allowed
-------------------------------------------------------------
$policy = new SecurityPolicy(
allowedTags: ['if', 'for'],
allowedFilters: ['escape', 'upper', 'date'],
allowedMethods: [
Article::class => ['getTitle', 'getBody', '__toString'],
],
allowedProperties: [
Article::class => ['title'],
],
allowedFunctions: ['range'],
allowedTests: ['published'],
);
.. versionadded:: 3.28
The ``always_allowed_in_sandbox`` option for filters, functions, and tests,
and the ``isAlwaysAllowedInSandbox()`` method for token parsers, were added
in Twig 3.28.
The ``allowedTests`` argument was added in Twig 3.28. Before, all tests
were allowed.
Some filters, functions, tests, and tags are inherently safe and should always
be usable in sandboxed templates without forcing every policy to allow-list
them. Mark such callables by setting the ``always_allowed_in_sandbox`` option
to ``true``::
When auto-escaping is enabled (the default), Twig applies the ``escape``
filter to every printed expression, so allow it. The ``..`` operator calls the
``range`` function, so ``{% for i in 1..10 %}`` requires allowing both the
``for`` tag and the ``range`` function.
$twig->addFilter(new \Twig\TwigFilter('upper', 'strtoupper', [
'always_allowed_in_sandbox' => true,
]));
Tags, filters, functions, and tests are checked when each template starts
rendering, and methods and properties when a template uses them. A render can
therefore fail after ``display()`` or ``stream()`` sent part of the output,
for instance when an included template uses a forbidden filter; use
``render()`` to get the output only if the whole template renders.
$twig->addFunction(new \Twig\TwigFunction('max', 'max', [
'always_allowed_in_sandbox' => true,
]));
Objects
~~~~~~~
$twig->addTest(new \Twig\TwigTest('even', null, [
'always_allowed_in_sandbox' => true,
]));
Templates can only access the methods and properties allowed for the class of
an object. An entry applies to the class, its subclasses, and, for an
interface, the classes implementing it. Method names are case-insensitive,
property names are case-sensitive:
For tags, override ``isAlwaysAllowedInSandbox()`` on your token parser to
return ``true``::
* ``{{ article.title }}`` is allowed if the ``title`` public property or the
method it resolves to (``title()``, ``getTitle()``, ``isTitle()``, or
``hasTitle()``) is allowed;
final class MyTagTokenParser extends \Twig\TokenParser\AbstractTokenParser
{
public function isAlwaysAllowedInSandbox(): bool
{
return true;
}
* ``{{ article.getTitle() }}`` requires the ``getTitle`` method;
// ...
}
* ``{{ article['title'] }}`` on an object implementing ``ArrayAccess`` reads
the offset if the ``title`` property is allowed, and otherwise resolves like
``article.title``; native classes like ``ArrayObject``, ``ArrayIterator``,
``SplFixedArray``, or ``SplObjectStorage`` don't need to be allowed, but
their subclasses do;
Marked filters, functions, tests, and tags are skipped by the sandbox security
check entirely, so they incur no runtime overhead, and they do not need to be
listed in the ``SecurityPolicy`` allow-lists.
* printing an object or converting it to a string in any other way (with a
filter like ``upper``, a concatenation, a comparison with a string, and so
on) requires its ``__toString`` method, except for ``Twig\Markup`` objects,
like the output of ``include()``.
The sandbox assumes that attackers control template source, not the Twig
environment, registered extensions, runtime configuration, security policy,
custom escaping strategies, or context values passed by the application. Treat
those application-provided pieces as trusted. If a callable or a value is not
safe for untrusted template authors, don't register or expose it in the
sandboxed environment.
When a method is handled by ``__call()``, allow the name used in the
template: ``{{ article.slug }}`` requires the ``slug`` method. Never allow
magic methods like ``__call``, ``__get``, or ``__set``: a template could call
them directly, as in ``{{ article.__call('anyMethod', ['argument']) }}``, with
any name and arguments. Reading a property handled by ``__get()`` or by a
property hook runs that code, so only allow such a property if that code is
safe to run.
Criteria for Marking an Item as Always Allowed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Always Allowed Built-ins
~~~~~~~~~~~~~~~~~~~~~~~~
Only mark a callable or tag as always allowed when **all** the following
conditions hold:
The built-in ``defined``, ``divisible by``, ``empty``, ``even``,
``iterable``, ``mapping``, ``none``, ``null``, ``odd``, ``same as``,
``sequence``, and ``true`` tests are always allowed. The ``constant`` test
reads PHP constants, so allow it explicitly if you need it.
* **No new capability.** The item must not expose anything beyond what the
sandbox already accepts. Pure value predicates (``is even``, ``is empty``),
pure value transformations (``upper``, ``trim``, ``abs``), and pure control
flow (``if``, ``for``, ``set``) qualify.
* **No PHP runtime access.** The item must not read arbitrary PHP constants,
call arbitrary classes or functions, instantiate objects from
user-controlled names, or otherwise reach into the PHP runtime. This rules
out ``constant``, ``enum``, ``invoke``, and similar.
* **No callable arguments.** The item must not accept a callable parameter it
dispatches to. This rules out higher-order operations like ``map``,
``filter``, ``reduce``, ``find``, ``sort``, and ``column``: applications may
have deliberate reasons to forbid those, and they need the policy gate to do
so.
* **No cross-template resolution.** The item must not resolve template names
at runtime or pivot through the loader. This rules out ``include``,
``extends``, ``embed``, ``use``, ``import``, ``from``, ``source``, and
``template_from_string``.
* **No output-safety bypass.** The item must not let the template declare
its own output safe. This rules out ``raw``.
* **No dedicated introspection or debugging surface.** The item must not be
intended to dump arbitrary object internals or call user-defined
serialization hooks. This rules out ``json_encode`` and ``dump``.
* **No side effects on the PHP environment.** The item must not flush
output buffers, trigger deprecations, or otherwise affect global state.
This rules out ``flush`` and ``deprecated``.
* **Deterministic output.** The item must return the same value for the same
arguments across renders. Applications that rely on sandboxed templates being
reproducible (for caching, content hashing, golden-output tests, or audit
comparisons) lose that property if a template can pull from the PHP random
number generator without the policy opting in. This rules out ``random`` and
``shuffle``: applications that want them can still allow-list them
explicitly.
In Twig 4.0, the following built-ins will be always allowed as well; allow
them explicitly in 3.x:
Note that several allowed items will still interact with PHP interfaces on
objects passed as arguments (``Countable::count()``,
``IteratorAggregate::getIterator()``, ``Stringable::__toString()`` on
iterated items). That transitive behavior is documented separately under
:ref:`Allowed Operations Apply Transitively to Their Arguments
<allowed-operations-transitive>` and is considered an accepted property of
the sandbox model. The criteria above are about what the item itself
exposes, not about how its arguments behave.
* Tags: ``apply``, ``block``, ``do``, ``for``, ``if``, ``macro``, ``set``,
``types``, ``with``;
Built-ins That Will Be Always Allowed in 4.0
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following Twig built-ins meet the criteria above and will have the
``always_allowed_in_sandbox`` flag set in Twig 4.0. They still need to be
explicitly allow-listed in 3.x.
* Tags: ``apply``, ``block``, ``do``, ``for``, ``guard``, ``if``, ``macro``,
``set``, ``types``, ``with``.
* Filters: ``abs``, ``batch``, ``capitalize``, ``convert_encoding``,
``default``, ``e``, ``escape``, ``first``, ``format``, ``join``, ``keys``,
``last``, ``length``, ``lower``, ``merge``, ``nl2br``, ``number_format``,
``replace``, ``reverse``, ``round``, ``slice``, ``split``, ``striptags``,
``title``, ``trim``, ``upper``, ``url_encode``.
``title``, ``trim``, ``upper``, ``url_encode``;
* Functions: ``cycle``, ``max``, ``min``.
When upgrading to 4.0, you can drop these names from your ``SecurityPolicy``
allow-lists. Leaving them in is harmless: listing a name that is always
allowed has no effect.
Keeping them in your policy after upgrading is harmless.
The corresponding built-in tests (``defined``, ``divisible by``, ``empty``,
``even``, ``iterable``, ``mapping``, ``none``, ``null``, ``odd``, ``same as``,
``sequence``, ``true``) are **already** flagged as always allowed since Twig
3.28, so they never need to be allow-listed. This is safe because tests were
never enforced by the sandbox before 3.28: flagging them keeps existing
templates working unchanged. The ``constant`` test is the exception: it reaches
into the PHP runtime, so it is not always allowed and must be allow-listed (it
is still implicitly allowed in 3.x with a deprecation, and rejected in 4.0).
Unlike the tags above, never allow the ``guard`` tag: sandboxed templates are
written for a known environment, so they have no reason to check which
filters, functions, and tests it registers.
.. _allowed-operations-transitive:
.. _sandbox-limits:
Allowed Operations Apply Transitively to Their Arguments
--------------------------------------------------------
What the Sandbox Does Not Protect Against
-----------------------------------------
The method and property allow-lists only restrict attribute access written
explicitly in the template (``obj.foo`` and ``obj.foo()``). Once an object is
passed as an argument to an allowed tag, filter, function, or test, that
operation can interact with it in any way PHP allows, without going through
the sandbox allow-list.
The sandbox assumes that template authors control the template source, and
nothing else: the environment, its extensions, the policy, and the data you
pass are yours, and Twig trusts them. Keep the following in mind when deciding
what to expose.
This is especially easy to miss for implicit calls made through PHP
interfaces. For example, allowing ``json_encode`` may expose public object
properties and call ``JsonSerializable::jsonSerialize()``; allowing sequence
operations such as ``for``, ``keys``, ``slice``, ``random``, or ``join`` may
call ``IteratorAggregate::getIterator()``, ``Iterator`` methods, or
``Countable::count()``; allowing ``cycle`` with an ``ArrayAccess`` value may
call ``offsetGet()``; allowing ``url_encode`` on arrays may expose public
object properties through PHP's query-string serialization; allowing ``max``
or ``min`` may compare objects by their public properties. None of these calls
appear in the template source.
**Data is visible.** Only pass the data, and register the globals, a template
needs: a template can iterate over every variable through ``_context``. The
policy only governs objects: array keys and values are readable without any
check. If you show error messages to template authors, they also reveal names:
security errors name the class of the object, reading a missing key with
``strict_variables`` enabled lists the existing ones, and an unknown tag,
filter, function, or test suggests registered names close to it.
Only allow operations whose behavior is safe for the objects you expose to
sandboxed templates. If this is not guaranteed, convert objects to plain
arrays or scalars before passing them in.
**Allowed operations use their arguments freely.** Allow-lists only restrict
what the template source writes explicitly. An allowed filter, function, test,
or tag can use the objects it receives in any way PHP allows: iteration calls
``getIterator()``, ``Iterator``, or ``Countable`` methods, ``cycle`` calls
``offsetGet()`` on ``ArrayAccess`` objects, ``json_encode`` calls
``jsonSerialize()`` and, like ``url_encode``, exposes public properties,
``min`` and ``max`` compare objects by their properties, and your own filters
and functions do whatever their code does. Only allow operations that are safe
for the objects you pass, or pass arrays and scalars instead.
Limiting Resource Usage
-----------------------
**Objects must behave consistently.** Before an operation converts the
elements of an iterable to strings (like ``join``), the sandbox iterates it to
check them, then the operation iterates it again. An object producing
different elements on each iteration (because it consumes a queue, re-runs a
query, or reads from a stream) can give the operation elements that were never
checked. Twig does not consider such bypasses security issues: convert these
values to arrays before passing them.
The sandbox prevents untrusted templates from reaching code, data, methods, or
properties they shouldn't. It does **not** prevent a template from consuming
CPU, memory, or wall-clock time, even under the strictest allow-list.
**Your code runs with full power.** Filters, functions, and extensions
registered on the sandbox environment run as regular PHP code; only register
code that is safe to call with arguments chosen by template authors.
This is by design: any limit baked into Twig itself would be both arbitrary
and trivial to work around, since there are many ways a template can burn
resources (large ranges, nested loops, large string operations, recursive
macros, expensive filters, deeply nested includes, and so on).
**Output is not sanitized.** The text of a template is output as written by
its author, HTML and JavaScript included; auto-escaping only applies to
printed expressions. Only use the output where content from its author is
acceptable.
If you render untrusted templates, you should contain them at the process level
rather than at the template engine level.
**Resources are not limited.** A template can consume as much CPU, memory, or
time as it wants, even under the strictest policy: large ranges, nested loops,
or recursive macros are enough. Contain sandboxed renders at the process level
(time limits, memory limits, dedicated workers).
Accepting Callables Arguments
-----------------------------
Defining Callables for Sandboxed Templates
------------------------------------------
The Twig sandbox allows you to configure which functions, filters, tests and
dot operations are allowed. Many of these calls can accept arguments. As these
arguments are not validated by the sandbox, you must be very careful.
Filters, functions, and tests receive arguments chosen by template authors.
Never accept a PHP ``callable`` argument, and never leave such an argument
untyped: a template could pass the name of any PHP function, like ``system``.
Type-hint ``\Closure`` instead, so that templates can only pass arrow
functions::
For instance, accepting a PHP ``callable`` as an argument is dangerous as it
allows end user to call any PHP function (by passing a ``string``) or any
static methods (by passing an ``array``). For instance, it would accept any PHP
built-in functions like ``system()`` or ``exec()``::
$twig->addFilter(new \Twig\TwigFilter('custom', function (callable $callable) {
$custom = function (iterable $items, \Closure $callback) {
// ...
$callable();
// ...
}));
};
$env->addFilter(new \Twig\TwigFilter('custom', $custom));
To avoid this security issue, don't type-hint such arguments with ``callable``
but use ``\Closure`` instead (not using a type-hint would also be problematic).
This restricts the allowed callables to PHP closures only, which is enough to
accept Twig arrow functions::
To adapt the behavior of a filter, function, or test in sandboxed templates,
use the ``needs_is_sandboxed`` option (see :ref:`sandbox-aware-filters`).
$twig->addFilter(new \Twig\TwigFilter('custom', function (\Closure $callable) {
// ...
$callable();
// ...
}));
.. versionadded:: 3.28
{{ people|custom(p => p.username|join(', ') }}
The ``always_allowed_in_sandbox`` option and the
``isAlwaysAllowedInSandbox()`` token parser method were added in Twig 3.28.
Any PHP callable can easily be converted to a closure by using the `first-class callable syntax`_.
A filter, function, or test can be always allowed, so that policies don't have
to list it, with the ``always_allowed_in_sandbox`` option::
.. _`first-class callable syntax`: https://www.php.net/manual/en/functions.first_class_callable_syntax.php
$env->addFilter(new \Twig\TwigFilter('rot13', 'str_rot13', [
'always_allowed_in_sandbox' => true,
]));
For a tag, return ``true`` from ``isAlwaysAllowedInSandbox()`` in its token
parser. Only do so for items meeting all these criteria:
* they expose no new capability: pure value predicates (``even``), value
transformations (``upper``), or control flow (``if``);
* they don't reach the PHP runtime: no constants, classes, or functions chosen
by the template;
* they don't accept callables, so applications can still forbid higher-order
operations like ``map`` or ``filter``;
* they don't load templates, like ``include`` or ``source``;
* they don't let templates mark arbitrary content as safe, like ``raw``;
* they don't dump object internals or call serialization hooks, like
``json_encode``;
* they have no side effects on the PHP environment, like ``flush``;
* they are deterministic, unlike ``random``.
These criteria cover what the item itself exposes; how it uses the objects it
receives is covered in :ref:`sandbox-limits`.
Upgrading From the Sandbox Extension
------------------------------------
.. deprecated:: 3.29
The ``SandboxExtension`` is internal as of Twig 3.29, and its
``enableSandbox()``, ``disableSandbox()``, and ``isSandboxedGlobally()``
methods and the ``sandboxed`` argument of the ``include`` function are
deprecated. Use the ``Sandbox`` class instead.
Before the ``Sandbox`` class, the sandbox was enabled by registering the
``SandboxExtension`` on an environment, either for all templates or only for
templates included with the ``sandboxed`` argument::
$twig->addExtension(new \Twig\Extension\SandboxExtension($policy, true));
To migrate, move untrusted templates to an environment dedicated to a
``Sandbox``, and make the policy strict. A non-strict policy always allows the
``extends`` and ``use`` tags, the ``parent``, ``block``, and ``attribute``
functions, and every test; a strict one requires allowing them like anything
else, as Twig 4.0 will. Replace ``include()`` calls using the ``sandboxed``
argument, and the deprecated ``sandbox`` tag, with the
:doc:`render_sandboxed <functions/render_sandboxed>` function.