{"record":{"id":"72c6076c446675cd","repo":"twigphp/Twig","slug":"calling-s-property-on-a-s-object-is-not-allowed","errorCode":null,"errorMessage":"Calling \"%s\" property on a \"%s\" object is not allowed.","messagePattern":"Calling \"(.+?)\" property on a \"(.+?)\" object is not allowed\\.","errorType":"exception","errorClass":"SecurityNotAllowedPropertyError","httpStatus":null,"severity":"error","filePath":"src/Sandbox/SecurityPolicy.php","lineNumber":180,"sourceCode":"        if (!$allowed) {\n            $class = $obj::class;\n            throw new SecurityNotAllowedMethodError(\\sprintf('Calling \"%s\" method on a \"%s\" object is not allowed.', $method, $class), $class, $method);\n        }\n    }\n\n    public function checkPropertyAllowed($obj, $property): void\n    {\n        $allowed = false;\n        foreach ($this->allowedProperties as $class => $properties) {\n            if ($obj instanceof $class && \\in_array($property, \\is_array($properties) ? $properties : [$properties], true)) {\n                $allowed = true;\n                break;\n            }\n        }\n\n        if (!$allowed) {\n            $class = $obj::class;\n            throw new SecurityNotAllowedPropertyError(\\sprintf('Calling \"%s\" property on a \"%s\" object is not allowed.', $property, $class), $class, $property);\n        }\n    }\n}\n","sourceCodeStart":162,"sourceCodeEnd":184,"githubUrl":"https://github.com/twigphp/Twig/blob/a414c3a491defb5a60f2fc88ef79ff37c90010cd/src/Sandbox/SecurityPolicy.php#L162-L184","documentation":"checkPropertyAllowed() throws SecurityNotAllowedPropertyError when sandboxed code accesses an object property that is not allowlisted for that object's class in $allowedProperties. The sandbox invokes this check before any property read on template objects.","triggerScenarios":"A sandboxed template accesses $obj->property (directly or via {{ obj.prop }} / attribute()) where the class/property pair is missing from the policy's $allowedProperties map.","commonSituations":"Exposing new entities/DTOs to sandboxed templates without extending the property whitelist; refactoring classes so a formerly public property is accessed differently; strict tenant isolation policies rejecting unexpected property access.","solutions":["Allow the property: $policy->setAllowedProperties([MyClass::class => ['prop', ...]]) or extend the constructor's $allowedProperties array.","Catch SecurityNotAllowedPropertyError to report the class and property and update the allowlist.","Pass only explicitly whitelisted view-models into sandboxed templates.","Prefer allowed getter methods over raw property access if methods are already allowlisted."],"exampleFix":"// before\n $policy = new SecurityPolicy($tags, $filters, [], [], []); // User::$name blocked\n// after\n $policy->setAllowedProperties([User::class => ['name', 'email']]);","handlingStrategy":"try-catch","validationCode":"if (!isset($allowedProperties[$obj::class]) || !in_array($prop, $allowedProperties[$obj::class], true)) {\n    // access would throw; adjust policy\n}","typeGuard":"function propertyAllowed(object $obj, string $prop, array $allowedProperties): bool {\n    return in_array($prop, $allowedProperties[$obj::class] ?? [], true);\n}","tryCatchPattern":"try {\n    $html = $twig->render($tpl, ['user' => $userObj]);\n} catch (\\Twig\\Sandbox\\SecurityNotAllowedPropertyError $e) {\n    $logger->warning('Sandbox blocked property', ['class' => $e->getClassName(), 'property' => $e->getPropertyName()]);\n}","preventionTips":["Whitelist only the exact properties templates read.","Prefer allowlisted accessor methods over raw property access.","Convert domain entities into DTO view-models for sandboxed rendering.","Add a sandbox regression test per exposed class."],"tags":["twig","sandbox","security-policy","properties"],"backgroundTag":"permission-denied","analyzedSha":"a414c3a491defb5a60f2fc88ef79ff37c90010cd","analyzedAt":"2026-09-13T15:10:46.849Z","contentChangedAt":"2026-09-13T15:10:46.849Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}