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 \stdClass

View on GitHub (pinned to 6040efed04)

Solutions

  1. Serialize only the fields you need instead of the whole structure: {{ page.header.title }} or {{ page.header.taxonomy.category|join(', ') }}
  2. Reduce the nesting in the source YAML, or sanitize the header in a plugin (onPageContentProcessed or similar) before render
  3. Move genuinely needed deep dumps into a real theme template, which is not sandboxed
  4. 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

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


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