getgrav/grav · error · SecurityNotAllowedFilterError
Filter "%s" is not allowed on deeply nested data inside sand
Error message
Filter "%s" is not allowed on deeply nested data inside sandboxed content.
What it means
Grav hardens dump-style filters (print_r, vardump, yaml_encode, json_encode, string) when rendering sandboxed Twig content. scanSandboxDump() walks the value before serializing and refuses structures nested deeper than 16 levels, raising SecurityNotAllowedFilterError naming the filter. This catches both pathological/cyclic data that would recurse or hang the serializer and attempts to exfiltrate deep object graphs through a dump filter.
Source
Thrown at system/src/Grav/Common/Twig/Extension/GravExtension.php:373
/**
* Recursive worker for assertSandboxDumpSafe(). Throws
* SecurityNotAllowedFilterError on the first object that is not permitted, so
* the sandboxed render soft-fails and logs the violation like any other
* sandbox block.
*
* @param GravSecurityPolicy|null $policy
* @param mixed $var
* @param string $filter
* @param bool $reflective
* @param int $depth
* @return void
*/
private function scanSandboxDump(?GravSecurityPolicy $policy, mixed $var, string $filter, bool $reflective, int $depth = 0): void
{
if ($depth > 16) {
// Pathological nesting / cycles: refuse rather than recurse forever.
throw new SecurityNotAllowedFilterError(
sprintf('Filter "%s" is not allowed on deeply nested data inside sandboxed content.', $filter),
$filter
);
}
if (is_array($var)) {
foreach ($var as $item) {
$this->scanSandboxDump($policy, $item, $filter, $reflective, $depth + 1);
}
return;
}
if (!is_object($var)) {
return;
}
$allowed = $reflective
? $var instanceof \stdClassView on GitHub (pinned to 6040efed04)
Solutions
- Serialize only the fields you need instead of the whole structure: {{ page.header.title }} or {{ page.header.taxonomy.category|join(', ') }}
- Reduce the nesting in the source YAML, or sanitize the header in a plugin (onPageContentProcessed or similar) before render
- Move genuinely needed deep dumps into a real theme template, which is not sandboxed
- If this fires on data you believe is shallow, inspect it for unintended cycles (objects referencing their parents) — that is usually the real bug
Example fix
{# before, in sandboxed content: deep/cyclic header #}
{{ page.header|print_r }}
{# after: explicit field access #}
{{ page.header.title }} — {{ page.header.taxonomy.category|join(', ') }} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check nesting depth (and cycles) before passing a value to dump filters in sandboxed content
function maxNestingDepth(mixed $v, int $depth = 0, array &$seen = []): int
{
if ($depth > 16 || (is_object($v) && isset($seen[spl_object_id($v)]))) { return PHP_INT_MAX; }
if (is_object($v)) { $seen[spl_object_id($v)] = true; $values = get_object_vars($v); }
elseif (is_array($v)) { $values = $v; } else { return $depth; }
$max = $depth;
foreach ($values as $child) { $max = max($max, maxNestingDepth($child, $depth + 1, $seen)); }
return $max;
}
// if (maxNestingDepth($value) > 16) { render a placeholder instead of dumping } Try / catch
use Twig\Sandbox\SecurityNotAllowedFilterError;
try { echo $twig->render($sandboxedTemplate, $data); }
catch (SecurityNotAllowedFilterError $e) { log_refused_filter($e->getFilterName()); echo '<!-- dump refused by sandbox -->'; } Prevention
- Keep |print_r / |vardump / |json_encode / |yaml_encode / |string out of sandboxed content; use explicit field access
- Cap and sanitize page-header nesting in plugins that build headers
- Watch for cycles when attaching parent/back-references to header objects
When it happens
Trigger: In sandboxed content: {{ page.header|print_r }} where the header nests more than 16 levels; |yaml_encode or |json_encode on deeply nested arrays; structures containing circular references (parent points to child points back to parent), which make the scan depth explode; plugin-built headers embedding entire objects or collections.
Common situations: Debug statements (|print_r, |vardump) left in sandboxed page content; page headers built by plugins that embed deep structures; user-supplied YAML with unbounded nesting rendered through a dump filter.
Related errors
- Filter "%s" is not allowed on a "%s" object inside sandboxed
- The callable passed to the "array_group_by" filter must be a
- Test "%s" is not allowed.
- Tag "%s" is not allowed.
- Twig |filter("{arrow}") is not allowed.
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/a7e25f6629941b7d.
Report an issue: GitHub.