getgrav/grav · error · RuntimeError
Twig |reduce("{arrow}") is not allowed.
Error message
Twig |reduce("{arrow}") is not allowed. What it means
Grav's override of Twig's |reduce applies the same guard as |filter and |map: the arrow must be a \Closure or a string that is not flagged by Utils::isDangerousFunction(), otherwise a RuntimeError is thrown before delegation. A string like 'system' passed to reduce would be invoked as system($accumulator, $value) outside the sandbox, hence the refusal. Note that in this codebase reduceFunc currently delegates to twig_array_map rather than a reduce implementation — if |reduce silently behaves like |map after passing the guard, check your Grav version against upstream.
Source
Thrown at system/src/Grav/Common/Twig/Extension/GravExtension.php:2111
if ($array === null) {
$array = [];
}
return twig_array_map($env, $array, $arrow);
}
/**
* @param Environment $env
* @param array $array
* @param callable|string $arrow
* @return array|CallbackFilterIterator
* @throws RuntimeError
*/
function reduceFunc(Environment $env, $array, $arrow)
{
if (!$arrow instanceof \Closure && !is_string($arrow) || Utils::isDangerousFunction($arrow)) {
throw new RuntimeError('Twig |reduce("' . $arrow . '") is not allowed.');
}
if ($array === null) {
$array = [];
}
return twig_array_map($env, $array, $arrow);
}
/**
* Hardened `find` filter. Twig core only rejects a dangerous string callable
* (e.g. `find('system')`, invoked as `system($v, $k)`) when the template is
* sandboxed. Editor-authorable strings rendered OUTSIDE the sandbox — such as
* the Email plugin's form action params — reached that unguarded call and gave
* a page editor RCE (GHSA-xx48-97m4-h7qm). Apply the same dangerous-arrow guard
* used by filter/map/reduce, regardless of sandbox state; a real arrow closure
* still passes.
*View on GitHub (pinned to 6040efed04)
Solutions
- Rewrite the reduction as an arrow function: {{ items|reduce((carry, v) => carry + v.price, 0) }}
- Wrap class methods instead of array callables: {{ items|reduce((carry, v) => MyClass::combine(carry, v), '') }}
- Treat denylisted names in templates as a security signal and audit the template's origin
- Never pass an uninitialized (null) arrow to |reduce
Example fix
{# before: string callable #}
{{ items|reduce('system') }}
{# after: arrow function Closure #}
{{ items|reduce((carry, v) => carry + v.price, 0) }} Defensive patterns
Strategy: validation
Validate before calling
// guard the arrow before render (mirrors reduceFunc's check)
$ok = $arrow instanceof \Closure || (is_string($arrow) && !Utils::isDangerousFunction($arrow));
if (!$ok) { $arrow = fn($carry, $v) => $carry; // safe default or fail fast
} Type guard
function isSafeTwigArrow(mixed $arrow): bool
{
return $arrow instanceof \Closure || (is_string($arrow) && !\Grav\Common\Utils::isDangerousFunction($arrow));
} Try / catch
use Twig\Error\RuntimeError;
try { echo $twig->render($template, $data); }
catch (RuntimeError $e) { log_template_error($e); // audit template provenance
} Prevention
- Write |reduce callbacks as arrow functions: |reduce((carry, v) => expr, initial)
- Migrate legacy string-callable reductions when upgrading Twig/Grav
- CI-grep for |reduce(' and reject
- Audit any denylisted function name found in a template
When it happens
Trigger: {{ items|reduce('system') }} or any denylisted function name; passing a null arrow or an array callable (['MyClass', 'combine']) which is neither Closure nor string; legacy templates written against Twig versions where string callables were accepted unchecked.
Common situations: Migrating pre-hardening themes; aggregation snippets (summing, concatenating) written with string callables; injected templates tripping the RCE guard.
Related errors
- Twig |filter("{arrow}") is not allowed.
- Twig |map("{arrow}") is not allowed.
- Twig |find("{arrow}") is not allowed.
- Twig |sort("{arrow}") is not allowed.
- The callable passed to the "array_group_by" filter must be a
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/d41d07bc9680a941.
Report an issue: GitHub.