{"record":{"id":"ddc81b7a8951cd6c","repo":"getgrav/grav","slug":"test-s-is-not-allowed","errorCode":null,"errorMessage":"Test \"%s\" is not allowed.","messagePattern":"Test \"(.+?)\" is not allowed\\.","errorType":"exception","errorClass":"SecurityNotAllowedTestError","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Common/Twig/Sandbox/GravSecurityPolicy.php","lineNumber":58,"sourceCode":"        private array $allowedMethods = [],\n        private array $allowedProperties = [],\n        private array $allowedFunctions = [],\n        private ?array $allowedTests = null,\n    ) {\n    }\n\n    /**\n     * Twig 3.28 added the `$tests` argument, required from Twig 4.0.\n     *\n     * Grav has no allowlist for tests yet, so the default is to leave them unrestricted, which is\n     * what the sandbox did before 3.28. Pass `$allowedTests` to the constructor to start enforcing.\n     */\n    public function checkSecurity($tags, $filters, $functions, array $tests = []): void\n    {\n        if (null !== $this->allowedTests) {\n            foreach ($tests as $test) {\n                if (!in_array($test, $this->allowedTests, true)) {\n                    throw new SecurityNotAllowedTestError(sprintf('Test \"%s\" is not allowed.', $test), $test);\n                }\n            }\n        }\n\n        foreach ($tags as $tag) {\n            if (!in_array($tag, $this->allowedTags, true)) {\n                throw new SecurityNotAllowedTagError(sprintf('Tag \"%s\" is not allowed.', $tag), $tag);\n            }\n        }\n\n        foreach ($filters as $filter) {\n            if (!in_array($filter, $this->allowedFilters, true)) {\n                throw new SecurityNotAllowedFilterError(sprintf('Filter \"%s\" is not allowed.', $filter), $filter);\n            }\n        }\n\n        foreach ($functions as $function) {\n            if (!in_array($function, $this->allowedFunctions, true)) {","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Common/Twig/Sandbox/GravSecurityPolicy.php#L40-L76","documentation":"GravSecurityPolicy::checkSecurity() enforces the sandbox allowlists Twig asks about before executing a template body; this branch covers Twig tests (the `is` operator, e.g. `x is iterable`). Grav ships with allowedTests = null, leaving tests unrestricted to match pre-3.28 Twig behavior, but any policy constructed with a concrete list (Twig 3.28 added the $tests argument, required from Twig 4.0) throws SecurityNotAllowedTestError for every test not on it.","triggerScenarios":"Sandboxed content using {{ x is iterable }}, {{ n is odd }}, or a custom test while the GravSecurityPolicy was built with an allowedTests list that omits it; enabling stricter security.twig_content settings that start enforcing tests; preparing for Twig 4 by passing an explicit test allowlist.","commonSituations":"Tightening sandbox configuration after a security review; Grav/Twig upgrades where previously-unchecked tests become checked; plugin-provided tests used inside sandboxed page content.","solutions":["Add the test name to the allowlist when the policy is constructed (the security.twig_content configuration / Security::buildTwigSandboxPolicy)","Replace the test with an allowed construct in sandboxed content (e.g. use a filter or explicit comparison that is already permitted)","Move the logic into an unsandboxed theme template or plugin if the test is essential"],"exampleFix":"// before: policy enforces tests but omits 'iterable'\nnew GravSecurityPolicy($tags, $filters, $methods, $props, $functions, ['constant', 'defined']);\n\n// after: allowlist the tests sandboxed content needs\nnew GravSecurityPolicy($tags, $filters, $methods, $props, $functions, ['constant', 'defined', 'iterable', 'odd']);","handlingStrategy":"try-catch","validationCode":"// before rendering sandboxed content with a policy that enforces tests\n$testsUsed = ['iterable', 'odd']; // extracted from the template\n$allowed = $policyTests ?? [];\n$disallowed = array_diff($testsUsed, $allowed);\nif ($disallowed) { // fix the template or extend the allowlist before render\n    throw new InvalidArgumentException('Tests not allowed in sandbox: ' . implode(',', $disallowed));\n}","typeGuard":null,"tryCatchPattern":"use Twig\\Sandbox\\SecurityNotAllowedTestError;\ntry { echo $twig->render($sandboxedTemplate, $data); }\ncatch (SecurityNotAllowedTestError $e) { log_refused_test($e->getTestName()); echo '<!-- test refused by sandbox -->'; }","preventionTips":["Decide the tests allowlist once when configuring security.twig_content and document it for content authors","Avoid exotic/custom tests in sandboxed content","Re-test sandboxed pages after enabling test enforcement or preparing for Twig 4"],"tags":["twig","sandbox","security","policy","tests"],"backgroundTag":"twig-sandbox-violation","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}