getgrav/grav · error · RuntimeError

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

Error message

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

What it means

Grav's hardened |sort override refuses a comparator arrow that is neither a \Closure nor a string, or that Utils::isDangerousFunction() flags (e.g. sort('system') would be invoked as system($a, $b) outside the sandbox). A null arrow is explicitly allowed and produces a plain sort via CoreExtension::sort; the resolved sandbox state is passed through so string comparators are additionally refused in sandbox mode (GHSA-p6qj-p5m7-f62h) instead of relying on the denylist alone.

Source

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

     * Hardened `sort` filter. Same rationale as findFunc(): a string comparator
     * such as `sort('system')` would otherwise be called as `system($a, $b)` when
     * rendered outside the sandbox. Plain sorts (no comparator) are unaffected.
     *
     * The resolved sandbox state is passed through so a string comparator is refused
     * in sandbox mode (GHSA-p6qj-p5m7-f62h); hardcoding it off left the denylist as
     * the only guard, and the denylist does not list every two-argument callable.
     *
     * @param Environment $env
     * @param bool $isSandboxed
     * @param mixed $array
     * @param callable|string|null $arrow
     * @return array
     * @throws RuntimeError
     */
    function sortFunc(Environment $env, bool $isSandboxed, $array, $arrow = null)
    {
        if ($arrow !== null && (!$arrow instanceof \Closure && !is_string($arrow) || Utils::isDangerousFunction($arrow))) {
            throw new RuntimeError('Twig |sort("' . $arrow . '") is not allowed.');
        }

        return CoreExtension::sort($env, $isSandboxed, $array ?? [], $arrow);
    }
}

View on GitHub (pinned to 6040efed04)

Solutions

  1. Rewrite the comparator as an arrow function: {{ items|sort((a, b) => a.date <=> b.date) }}
  2. For plain value sorting, drop the comparator entirely: {{ items|sort }}
  3. Wrap class methods instead of array callables: {{ items|sort((a, b) => MyClass::rank(a) <=> MyClass::rank(b)) }}
  4. Audit the template source if a denylisted name appears — treat it as attempted injection

Example fix

{# before: string comparator #}
{{ items|sort('system') }}

{# after: arrow function comparator (or plain |sort) #}
{{ items|sort((a, b) => a.date <=> b.date) }}
Defensive patterns

Strategy: validation

Validate before calling

// guard the comparator before render (mirrors sortFunc's check; null means plain sort)
$ok = $arrow === null || $arrow instanceof \Closure || (is_string($arrow) && !Utils::isDangerousFunction($arrow));
if (!$ok) { $arrow = null; // fall back to plain sort or fail fast
}

Type guard

function isSafeTwigComparator(mixed $arrow): bool
{
    return $arrow === null || $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 any denylisted comparator name
}

Prevention

When it happens

Trigger: {{ items|sort('system') }} or another denylisted function name as comparator; passing an array callable like ['MyClass', 'compare']; a null/non-string non-Closure value accidentally used as the comparator (note: no comparator at all — |sort — is fine); legacy templates using usort-style string comparators.

Common situations: Sorting snippets copied from old Twig 1.x/2.x examples; injected content attempting command execution through comparator callables; refactors that leave a comparator variable null.

Related errors


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