{"record":{"id":"b80f7426703ffac3","repo":"getgrav/grav","slug":"tag-s-is-not-allowed","errorCode":null,"errorMessage":"Tag \"%s\" is not allowed.","messagePattern":"Tag \"(.+?)\" is not allowed\\.","errorType":"exception","errorClass":"SecurityNotAllowedTagError","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Common/Twig/Sandbox/GravSecurityPolicy.php","lineNumber":65,"sourceCode":"    /**\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)) {\n                throw new SecurityNotAllowedFunctionError(sprintf('Function \"%s\" is not allowed.', $function), $function);\n            }\n        }\n    }\n\n    public function checkMethodAllowed($obj, $method): void\n    {","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Common/Twig/Sandbox/GravSecurityPolicy.php#L47-L83","documentation":"GravSecurityPolicy::checkSecurity() enforces the sandbox's tag allowlist: every Twig tag used in the template body ({% for %}, {% if %}, {% include %}, ...) must appear in allowedTags or a SecurityNotAllowedTagError is thrown naming the tag. This is the primary gate that keeps sandboxed content (page markdown/Twig rendered under security.twig_content) from reaching template-level constructs like includes or module loading.","triggerScenarios":"Sandboxed content containing {% include 'partials/x.html.twig' %} when 'include' is not in the allowed tags; using {% embed %}, {% macro %}, {% do %}, or a plugin-provided tag inside sandboxed page content; enabling the content sandbox on a site whose pages already use rich Twig.","commonSituations":"Turning on security.twig_content for the first time on an existing site; copying full-template Twig into page markdown; plugins injecting tags into rendered content.","solutions":["Whitelist the tag in the sandbox configuration if it is safe for your content authors (edit the allowed tags under security.twig_content)","Replace the construct in sandboxed content: use a permitted equivalent (e.g. literal markup instead of include) or plain Twig that is allowed","Move the construct into an unsandboxed theme template or partial and keep sandboxed content simple","Audit pages that fail after enabling the sandbox and simplify their Twig"],"exampleFix":"{# before, in sandboxed page content: tag not allowlisted #}\n{% include 'partials/author.html.twig' %}\n\n{# after: theme template handles the include; content stays simple #}\n{{ page.header.author }}","handlingStrategy":"try-catch","validationCode":"// before rendering sandboxed content, check the tags it uses against the policy\n$tagsUsed = ['for', 'if', 'include']; // extracted from the template body\n$disallowed = array_diff($tagsUsed, $allowedTags);\nif ($disallowed) { // simplify the template or extend the allowlist before render\n    throw new InvalidArgumentException('Tags not allowed in sandbox: ' . implode(',', $disallowed));\n}","typeGuard":null,"tryCatchPattern":"use Twig\\Sandbox\\SecurityNotAllowedTagError;\ntry { echo $twig->render($sandboxedTemplate, $data); }\ncatch (SecurityNotAllowedTagError $e) { log_refused_tag($e->getTagName()); echo '<!-- tag refused by sandbox -->'; }","preventionTips":["Keep sandboxed page content to simple Twig (if/for + output) and put structure in theme templates","Document the allowed tag list for content authors","Audit existing pages for {% include %}/{% embed %} before enabling security.twig_content","Never widen the tag allowlist just to silence an error without assessing what the tag can reach"],"tags":["twig","sandbox","security","policy","tags"],"backgroundTag":"twig-sandbox-violation","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}