getgrav/grav · error · RuntimeError

Twig |map("{arrow}") is not allowed.

Error message

Twig |map("{arrow}") is not allowed.

What it means

Grav's override of Twig's |map throws this RuntimeError before calling twig_array_map when the arrow is neither a \Closure nor a string, or when Utils::isDangerousFunction() flags the string (exec, system, passthru, shell_exec, popen, proc_open, pcntl_exec, assert, preg_replace, create_function, include/require, and callback-style functions). The guard exists because outside the sandbox Twig would happily call map('system', $value, $key) per element, turning a template into arbitrary command execution.

Source

Thrown at system/src/Grav/Common/Twig/Extension/GravExtension.php:2091

        if ($array === null) {
            $array = [];
        }

        return twig_array_filter($env, $array, $arrow);
    }

    /**
     * @param Environment $env
     * @param array $array
     * @param callable|string $arrow
     * @return array|CallbackFilterIterator
     * @throws RuntimeError
     */
    function mapFunc(Environment $env, $array, $arrow)
    {
        if (!$arrow instanceof \Closure && !is_string($arrow) || Utils::isDangerousFunction($arrow)) {
            throw new RuntimeError('Twig |map("' . $arrow . '") is not allowed.');
        }

        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)
    {

View on GitHub (pinned to 6040efed04)

Solutions

  1. Rewrite the callback as an arrow function: {{ items|map(v => v.title|upper) }}
  2. Wrap class methods instead of array callables: {{ items|map(v => MyClass::label(v)) }}
  3. Investigate any denylisted function name in a template as a possible injection, not just a style problem
  4. Initialize callback variables and never pass null to |map

Example fix

{# before: string callable #}
{{ items|map('shell_exec') }}

{# after: arrow function Closure #}
{{ items|map(v => v.title|upper) }}
Defensive patterns

Strategy: validation

Validate before calling

// guard the arrow before render (mirrors mapFunc's check)
$ok = $arrow instanceof \Closure || (is_string($arrow) && !Utils::isDangerousFunction($arrow));
if (!$ok) { $arrow = fn($v) => $v; // 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); // record template name/line for audit
}

Prevention

When it happens

Trigger: {{ items|map('exec') }} or another denylisted function name in any template; a null or array-callable arrow (['Class', 'method']) which fails the Closure-or-string check; legacy Twig templates using PHP function names as the map callback (e.g. |map('ucfirst') works, but any denylisted or non-string callable throws).

Common situations: Porting old themes to current Grav; copy-pasted snippets from old cookbook examples; template-injection attempts showing up in error logs; variables used as callbacks that are unexpectedly null.

Related errors


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/a62b6bd06fd2ffe8. Report an issue: GitHub.