Email Template Security Policy Checking
OroEmailBundle validates email templates against the Twig sandbox security policy before they are
saved or rendered. The mechanism performs static analysis of each template field — by default
subject and content — and reports disallowed tags, filters, functions, and entity property or
method accesses as structured violations. At render time, any access that slips through validation is
caught gracefully so that a non-compliant template does not break the rendering pipeline.
Architecture
The following classes form the security policy checking subsystem:
Oro\Bundle\EmailBundle\Twig\SecurityPolicy\EmailTemplateSecurityPolicyCheckerInterfacePublic interface for the email-template security checker. Accepts an
EmailTemplateInterfaceand returns a list ofEmailTemplateSecurityPolicyViolationInterfaceobjects.Oro\Bundle\EmailBundle\Twig\SecurityPolicy\EmailTemplateSecurityPolicyCheckerImplements the interface. For each configured template field it resolves the associated entity class, dispatches
EmailTemplateSecurityPolicyCheckBeforeso listeners can enrich the variable-type map, then delegates toTemplateSecurityPolicyChecker(from OroEntityBundle). RawSecurityPolicyViolationInterfaceresults are wrapped in email-specific violation objects that also carry the field name where the violation was found. Checked fields can be overridden at any time by callingsetEmailTemplateFields().Oro\Bundle\EmailBundle\Twig\SecurityPolicy\Violation\EmailTemplateSecurityPolicyViolationInterfaceExtends
SecurityPolicyViolationInterfacewith agetTemplateField()method that identifies which email template field (e.g.,subjectorcontent) contained the violation. Concrete subtypes exist for each violation kind: tag, filter, function, property, and method.Oro\Bundle\EmailBundle\Validator\Constraints\EmailTemplateSecurityPolicySymfony Validator constraint applied at the class level on
EmailTemplateInterfaceimplementations. Defines five error codes (one per violation kind) and five configurable message translation keys.Oro\Bundle\EmailBundle\Validator\Constraints\EmailTemplateSecurityPolicyValidatorConstraint validator. Validates the default template and every non-fallback localized translation by calling
EmailTemplateSecurityPolicyCheckerInterface::checkSecurityPolicy(). Each violation is reported as a Symfony constraint violation with a message tailored to the violation kind. Twig syntax errors (SyntaxError) are silently ignored — syntax validation is handled separately.Oro\Bundle\EmailBundle\SecurityPolicyInspector\EmailTemplateSecurityPolicyInspectorHigh-level service used by the console command. Loads templates from the database (one by name or all) and runs validation through the Symfony Validator using the
EmailTemplateSecurityPolicyconstraint. ReturnsEmailTemplateSecurityPolicyInspectionResultobjects, each pairing a template with its fullConstraintViolationList.Oro\Bundle\EmailBundle\Event\EmailTemplateSecurityPolicyCheckBeforeEvent dispatched before each security check. Carries the email template being validated and a mutable
variableTypesmap (variable name to FQCN). Listeners add extra variable type entries to improve the accuracy of the static analysis for templates that rely on variables beyond the root entity.Oro\Bundle\EmailBundle\Provider\SubstitutableEmailTemplateAttributeProviderHolds the attributes declared with the
oro_email.email_template_substitutable_attributetag, along with the email template parameter each one is resolved from at render time.EmailTemplateSecurityPolicyValidatorasks it for that parameter, so that the violation it reports names the variable the template should read instead. See Email Templates Rendering Sandbox.Oro\Bundle\EmailBundle\Twig\SafeGetAttributeNodeExtensionTwig extension registered on the sandboxed email template environment. Installs the node visitors that replace every attribute access and every string coercion with a safe variant, and carries the event dispatcher those nodes report a violation through.
Oro\Bundle\EmailBundle\Twig\NodeVisitor\SafeGetAttrNodeVisitorAST node visitor (priority 1, runs after
GetAttrNodeVisitor). Replaces everyGetAttrNodeinstance in the compiled template AST with aSafeGetAttrNode.Oro\Bundle\EmailBundle\Twig\Node\SafeGetAttrNodeOverrides the
onSecurityError()hook ofGetAttrNode. Instead of propagatingSecurityNotAllowedMethodErrororSecurityNotAllowedPropertyError, a denied access dispatchesEmailTemplateSecurityPolicyViolationEventand resolves to the value a listener substituted, or tonullwhen no listener did. Logging a violation is left to a listener of that event.
Validating Email Templates
The EmailTemplateSecurityPolicy constraint can be applied programmatically using the Symfony
Validator component. The constraint is a class-level constraint targeting EmailTemplateInterface
objects:
use Oro\Bundle\EmailBundle\Validator\Constraints\EmailTemplateSecurityPolicy;
use Symfony\Component\Validator\Validator\ValidatorInterface;
// $validator and $emailTemplate are injected or retrieved from the container
$violations = $validator->validate($emailTemplate, new EmailTemplateSecurityPolicy());
if (count($violations) > 0) {
foreach ($violations as $violation) {
// $violation->getMessage() - translated error message
// $violation->getCause() - EmailTemplateSecurityPolicyViolationInterface instance
}
}
The validator checks the default template content and, when an EmailTemplate entity is provided,
all non-fallback localized translations as well.
To call the checker directly (bypassing Symfony Validator), inject
oro_email.twig.security_policy.email_template_checker and call checkSecurityPolicy():
use Oro\Bundle\EmailBundle\Twig\SecurityPolicy\EmailTemplateSecurityPolicyCheckerInterface;
// $checker is injected via the DI container
$violations = $checker->checkSecurityPolicy($emailTemplate);
foreach ($violations as $violation) {
// $violation->getName() - disallowed element name
// $violation->getTemplateField() - 'subject' or 'content'
// $violation->getEntityClass() - FQCN (null for tag/filter/function violations)
// $violation->getTemplateLine() - source line (-1 when unavailable)
}
Extending the Security Check
To add extra variable type information for templates that reference variables beyond the root entity,
listen to EmailTemplateSecurityPolicyCheckBefore and update the variable map:
use Oro\Bundle\EmailBundle\Event\EmailTemplateSecurityPolicyCheckBefore;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener]
class MySecurityPolicyListener
{
public function __invoke(EmailTemplateSecurityPolicyCheckBefore $event): void
{
$variableTypes = $event->getVariableTypes();
$variableTypes['customer'] = \Acme\Bundle\Entity\Customer::class;
$event->setVariableTypes($variableTypes);
}
}
The listener receives the email template being validated in $event->getEmailTemplate() and can
inspect it to conditionally add type entries only for relevant templates.
Graceful Rendering of Disallowed Accesses
Even after validation, a template may attempt to access a property or method that is not allowed by
the sandbox policy — for example because the policy changed after the template was saved or because
an edge case was not caught by static analysis. To prevent such accesses from breaking the rendering
pipeline, the sandboxed email template Twig environment registers SafeGetAttributeNodeExtension.
This extension installs SafeGetAttrNodeVisitor and SafeCheckToStringNodeVisitor, which rewrite every
GetAttrNode and every CheckToStringNode in the compiled template AST to their safe variants at compile time.
When the sandbox denies access at runtime, the safe node dispatches
EmailTemplateSecurityPolicyViolationEvent instead of propagating the
SecurityNotAllowedMethodError or SecurityNotAllowedPropertyError exception.
The net effect is that a template containing an access violation renders with an empty value for the offending expression rather than failing entirely. This is intentional: it maintains delivery of the email while producing a diagnostic log entry that operators can monitor.
To substitute a value for a denied access, see Email Templates Rendering Sandbox.
Console Command
The oro:email:template:security-policy-check console command runs the security policy inspection
against templates stored in the database. See CLI Commands (EmailBundle) for the full command reference and usage examples.