{"record":{"id":"3ac688380ec7e04d","repo":"getgrav/grav","slug":"twig-sort-arrow-is-not-allowed","errorCode":null,"errorMessage":"Twig |sort(\"{arrow}\") is not allowed.","messagePattern":"Twig \\|sort\\(\"(.+?)\"\\) is not allowed\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Common/Twig/Extension/GravExtension.php","lineNumber":2169,"sourceCode":"     * Hardened `sort` filter. Same rationale as findFunc(): a string comparator\n     * such as `sort('system')` would otherwise be called as `system($a, $b)` when\n     * rendered outside the sandbox. Plain sorts (no comparator) are unaffected.\n     *\n     * The resolved sandbox state is passed through so a string comparator is refused\n     * in sandbox mode (GHSA-p6qj-p5m7-f62h); hardcoding it off left the denylist as\n     * the only guard, and the denylist does not list every two-argument callable.\n     *\n     * @param Environment $env\n     * @param bool $isSandboxed\n     * @param mixed $array\n     * @param callable|string|null $arrow\n     * @return array\n     * @throws RuntimeError\n     */\n    function sortFunc(Environment $env, bool $isSandboxed, $array, $arrow = null)\n    {\n        if ($arrow !== null && (!$arrow instanceof \\Closure && !is_string($arrow) || Utils::isDangerousFunction($arrow))) {\n            throw new RuntimeError('Twig |sort(\"' . $arrow . '\") is not allowed.');\n        }\n\n        return CoreExtension::sort($env, $isSandboxed, $array ?? [], $arrow);\n    }\n}\n","sourceCodeStart":2151,"sourceCodeEnd":2175,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Common/Twig/Extension/GravExtension.php#L2151-L2175","documentation":"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.","triggerScenarios":"{{ 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.","commonSituations":"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.","solutions":["Rewrite the comparator as an arrow function: {{ items|sort((a, b) => a.date <=> b.date) }}","For plain value sorting, drop the comparator entirely: {{ items|sort }}","Wrap class methods instead of array callables: {{ items|sort((a, b) => MyClass::rank(a) <=> MyClass::rank(b)) }}","Audit the template source if a denylisted name appears — treat it as attempted injection"],"exampleFix":"{# before: string comparator #}\n{{ items|sort('system') }}\n\n{# after: arrow function comparator (or plain |sort) #}\n{{ items|sort((a, b) => a.date <=> b.date) }}","handlingStrategy":"validation","validationCode":"// guard the comparator before render (mirrors sortFunc's check; null means plain sort)\n$ok = $arrow === null || $arrow instanceof \\Closure || (is_string($arrow) && !Utils::isDangerousFunction($arrow));\nif (!$ok) { $arrow = null; // fall back to plain sort or fail fast\n}","typeGuard":"function isSafeTwigComparator(mixed $arrow): bool\n{\n    return $arrow === null || $arrow instanceof \\Closure || (is_string($arrow) && !\\Grav\\Common\\Utils::isDangerousFunction($arrow));\n}","tryCatchPattern":"use Twig\\Error\\RuntimeError;\ntry { echo $twig->render($template, $data); }\ncatch (RuntimeError $e) { log_template_error($e); // audit any denylisted comparator name\n}","preventionTips":["Prefer plain |sort when you don't need a custom comparator (null arrow is always allowed)","Write comparators as arrow functions: |sort((a, b) => a <=> b)","CI-grep for |sort(' and reject","Audit templates containing command-function names as comparators"],"tags":["twig","security","filter","callable","rce-guard","sorting"],"backgroundTag":"twig-unsafe-callable","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}