getgrav/grav · error · RuntimeError

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

Error message

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

What it means

Grav overrides Twig core's |filter and rejects the arrow before delegating to twig_array_filter when it is neither a \Closure nor a string, or when Utils::isDangerousFunction() flags the string. That denylist covers command execution (exec, system, passthru, shell_exec, popen, proc_open, pcntl_exec), code execution (assert, preg_replace, create_function, include/require) and callback-style PHP functions. Without the guard, |filter('system') in an unsandboxed template would invoke system($v, $k) per element — remote code execution.

Source

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

            'numeric' => is_numeric($var),
            'object' => is_object($var),
            'scalar' => is_scalar($var),
            'string' => is_string($var),
            default => false,
        };
    }

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

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

View on GitHub (pinned to 6040efed04)

Solutions

  1. Rewrite the callback as a Twig arrow function: {{ list|filter(v => v.published) }}
  2. For class-method callables, wrap them: {{ list|filter(v => MyClass::isVisible(v)) }}
  3. If a denylisted name like system appears in a template, treat it as a security incident: audit the template source and how it was written, do not just patch around it
  4. Ensure the arrow argument is never null — guard variables used as callbacks

Example fix

{# before: string callable (legacy or injected) #}
{{ items|filter('system') }}

{# after: arrow function Closure #}
{{ items|filter(v => v.published) }}
Defensive patterns

Strategy: validation

Validate before calling

// guard the arrow before render (mirrors filterFunc's check)
$ok = $arrow instanceof \Closure || (is_string($arrow) && !Utils::isDangerousFunction($arrow));
if (!$ok) { $arrow = fn($v) => $v; // replace with a safe Closure 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_and_source($e->getSourceContext()); // audit, don't silently retry
}

Prevention

When it happens

Trigger: A template containing {{ list|filter('system') }} or another denylisted function name (attack payload or leftover debug code); passing a null arrow ({{ list|filter(null-var) }} — null is neither Closure nor string, so it throws); array-style callables like ['MyClass', 'method'] which are neither Closure nor string; old Twig 1.x/2.x-era snippets using string callables.

Common situations: Migrating legacy themes to current Grav/Twig; attempted template injection visible in logs; static analysis flagging string callables in templates; passing uninitialized variables as the arrow.

Related errors


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