getgrav/grav · error · SecurityNotAllowedTagError

Tag "%s" is not allowed.

Error message

Tag "%s" is not allowed.

What it means

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.

Source

Thrown at system/src/Grav/Common/Twig/Sandbox/GravSecurityPolicy.php:65

    /**
     * Twig 3.28 added the `$tests` argument, required from Twig 4.0.
     *
     * Grav has no allowlist for tests yet, so the default is to leave them unrestricted, which is
     * what the sandbox did before 3.28. Pass `$allowedTests` to the constructor to start enforcing.
     */
    public function checkSecurity($tags, $filters, $functions, array $tests = []): void
    {
        if (null !== $this->allowedTests) {
            foreach ($tests as $test) {
                if (!in_array($test, $this->allowedTests, true)) {
                    throw new SecurityNotAllowedTestError(sprintf('Test "%s" is not allowed.', $test), $test);
                }
            }
        }

        foreach ($tags as $tag) {
            if (!in_array($tag, $this->allowedTags, true)) {
                throw new SecurityNotAllowedTagError(sprintf('Tag "%s" is not allowed.', $tag), $tag);
            }
        }

        foreach ($filters as $filter) {
            if (!in_array($filter, $this->allowedFilters, true)) {
                throw new SecurityNotAllowedFilterError(sprintf('Filter "%s" is not allowed.', $filter), $filter);
            }
        }

        foreach ($functions as $function) {
            if (!in_array($function, $this->allowedFunctions, true)) {
                throw new SecurityNotAllowedFunctionError(sprintf('Function "%s" is not allowed.', $function), $function);
            }
        }
    }

    public function checkMethodAllowed($obj, $method): void
    {

View on GitHub (pinned to 6040efed04)

Solutions

  1. Whitelist the tag in the sandbox configuration if it is safe for your content authors (edit the allowed tags under security.twig_content)
  2. Replace the construct in sandboxed content: use a permitted equivalent (e.g. literal markup instead of include) or plain Twig that is allowed
  3. Move the construct into an unsandboxed theme template or partial and keep sandboxed content simple
  4. Audit pages that fail after enabling the sandbox and simplify their Twig

Example fix

{# before, in sandboxed page content: tag not allowlisted #}
{% include 'partials/author.html.twig' %}

{# after: theme template handles the include; content stays simple #}
{{ page.header.author }}
Defensive patterns

Strategy: try-catch

Validate before calling

// before rendering sandboxed content, check the tags it uses against the policy
$tagsUsed = ['for', 'if', 'include']; // extracted from the template body
$disallowed = array_diff($tagsUsed, $allowedTags);
if ($disallowed) { // simplify the template or extend the allowlist before render
    throw new InvalidArgumentException('Tags not allowed in sandbox: ' . implode(',', $disallowed));
}

Try / catch

use Twig\Sandbox\SecurityNotAllowedTagError;
try { echo $twig->render($sandboxedTemplate, $data); }
catch (SecurityNotAllowedTagError $e) { log_refused_tag($e->getTagName()); echo '<!-- tag refused by sandbox -->'; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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