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
- 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
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
- 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
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
- Filter "%s" is not allowed on a "%s" object inside sandboxed
- Test "%s" is not allowed.
- Filter "%s" is not allowed on deeply nested data inside sand
- The callable passed to the "array_group_by" filter must be a
- Twig |filter("{arrow}") is not allowed.
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/b80f7426703ffac3.
Report an issue: GitHub.