{"record":{"id":"f79d1ad043822f1b","repo":"getgrav/grav","slug":"filter-s-is-not-allowed-on-a-s-object-inside","errorCode":null,"errorMessage":"Filter \"%s\" is not allowed on a \"%s\" object inside sandboxed content.","messagePattern":"Filter \"(.+?)\" is not allowed on a \"(.+?)\" object inside sandboxed content\\.","errorType":"exception","errorClass":"SecurityNotAllowedFilterError","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Common/Twig/Extension/GravExtension.php","lineNumber":395,"sourceCode":"        }\n\n        if (is_array($var)) {\n            foreach ($var as $item) {\n                $this->scanSandboxDump($policy, $item, $filter, $reflective, $depth + 1);\n            }\n            return;\n        }\n\n        if (!is_object($var)) {\n            return;\n        }\n\n        $allowed = $reflective\n            ? $var instanceof \\stdClass\n            : ($policy !== null && $policy->isClassAllowed($var));\n\n        if (!$allowed) {\n            throw new SecurityNotAllowedFilterError(\n                sprintf('Filter \"%s\" is not allowed on a \"%s\" object inside sandboxed content.', $filter, $var::class),\n                $filter\n            );\n        }\n    }\n\n    /**\n     * @param Environment $env\n     * @param mixed $var\n     * @return string\n     */\n    public function printRGuarded(Environment $env, mixed $var)\n    {\n        $this->assertSandboxDumpSafe($env, $var, 'print_r', true);\n        return $this->print_r($var);\n    }\n\n    /**","sourceCodeStart":377,"sourceCodeEnd":413,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Common/Twig/Extension/GravExtension.php#L377-L413","documentation":"Same dump-guard family as the nesting limit: when the value passed to a guarded filter (print_r, vardump, yaml_encode, json_encode, string) in sandboxed content is an object, its class must be explicitly allowed. Reflective filters (print_r, vardump) accept only stdClass; policy filters (yaml_encode, json_encode, string) require GravSecurityPolicy::isClassAllowed() to be true, i.e. the class appears in the sandbox's allowedMethods map. Serializing anything else would bypass the sandbox's member gate by dumping PHP object state directly (GHSA-mc5q-6hpj-rp7j).","triggerScenarios":"{{ page|print_r }} or {{ grav.page|json_encode }} in sandboxed content where Page is not in the policy allowlist; piping raw Config/Data objects when security.twig_content.config_access is off (only the redacting SandboxConfig facade stays allowed); dumping any service object from markdown-rendered Twig.","commonSituations":"Theme snippets pasted into page content (sandboxed) that worked in theme templates (not sandboxed); attempts to inspect Grav services from sandboxed markdown; sites updated to a Grav version that tightened dump filters.","solutions":["Serialize plain arrays/scalars instead of objects: {{ page.header|print_r }} (array) rather than {{ page|print_r }} (Page object)","Move the debug/serialization into an unsandboxed theme template or a plugin","If the object must be exposed, add its class to the sandbox policy allowedMethods via the security.twig_content configuration — weigh this carefully, it widens the sandbox","For config access, use the provided SandboxConfig facade rather than raw Config/Data objects"],"exampleFix":"{# before (sandboxed content): Page object is refused #}\n{{ page|print_r }}\n\n{# after: plain array header data passes #}\n{{ page.header|print_r }}","handlingStrategy":"type-guard","validationCode":"// only pass values the sandbox will serialize\nif (is_object($value) && !$value instanceof stdClass && !$policy->isClassAllowed($value)) {\n    $value = is_callable([$value, 'toArray']) ? $value->toArray() : (array) $value; // or render a placeholder\n}","typeGuard":"function isSandboxSerializable(mixed $v, ?GravSecurityPolicy $policy, bool $reflective): bool\n{\n    if (!is_object($v)) { return true; }\n    return $reflective ? $v instanceof \\stdClass : ($policy !== null && $policy->isClassAllowed($v));\n}","tryCatchPattern":"use Twig\\Sandbox\\SecurityNotAllowedFilterError;\ntry { echo $twig->render($sandboxedTemplate, $data); }\ncatch (SecurityNotAllowedFilterError $e) { log_refused_class($e->getFilterName()); echo '<!-- object dump refused by sandbox -->'; }","preventionTips":["Pipe plain arrays (page.header) into dump filters, never service objects (page, grav, config)","Do debugging in unsandboxed theme templates or plugins, not in content","Use the SandboxConfig facade for config access in sandboxed content","Review sandbox policy changes on Grav security releases"],"tags":["twig","sandbox","security","serialization","policy"],"backgroundTag":"twig-sandbox-violation","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}