getgrav/grav · error · SecurityNotAllowedTestError
Test "%s" is not allowed.
Error message
Test "%s" is not allowed.
What it means
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.
Source
Thrown at system/src/Grav/Common/Twig/Sandbox/GravSecurityPolicy.php:58
private array $allowedMethods = [],
private array $allowedProperties = [],
private array $allowedFunctions = [],
private ?array $allowedTests = null,
) {
}
/**
* 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)) {View on GitHub (pinned to 6040efed04)
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
Example fix
// before: policy enforces tests but omits 'iterable' new GravSecurityPolicy($tags, $filters, $methods, $props, $functions, ['constant', 'defined']); // after: allowlist the tests sandboxed content needs new GravSecurityPolicy($tags, $filters, $methods, $props, $functions, ['constant', 'defined', 'iterable', 'odd']);
Defensive patterns
Strategy: try-catch
Validate before calling
// before rendering sandboxed content with a policy that enforces tests
$testsUsed = ['iterable', 'odd']; // extracted from the template
$allowed = $policyTests ?? [];
$disallowed = array_diff($testsUsed, $allowed);
if ($disallowed) { // fix the template or extend the allowlist before render
throw new InvalidArgumentException('Tests not allowed in sandbox: ' . implode(',', $disallowed));
} Try / catch
use Twig\Sandbox\SecurityNotAllowedTestError;
try { echo $twig->render($sandboxedTemplate, $data); }
catch (SecurityNotAllowedTestError $e) { log_refused_test($e->getTestName()); echo '<!-- test refused by sandbox -->'; } Prevention
- 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
When it happens
Trigger: 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.
Common situations: Tightening sandbox configuration after a security review; Grav/Twig upgrades where previously-unchecked tests become checked; plugin-provided tests used inside sandboxed page content.
Related errors
- Filter "%s" is not allowed on a "%s" object inside sandboxed
- Tag "%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/ddc81b7a8951cd6c.
Report an issue: GitHub.