Twig: Sandbox: multiple `__toString()` policy bypasses via unguarded string coercion points
Date: 2026-06
Affected: composer/twig/twig
Attack vector: xss
Category: vulnerability-disclosure
Severity: High
CVE: CVE-2026-47730
CVE-2026-47732
CWE: CWE-20
CWE-79

### Description `SandboxNodeVisitor` enforces `SecurityPolicy::checkMethodAllowed()` for implicit `__toString()` calls by wrapping selected AST nodes in `CheckToStringNode`. The set of wrapped nodes is incomplete, and several Twig language constructs still trigger PHP string coercion on a `Stringable` operand without first consulting the policy. A sandboxed template author can therefore invoke `__toString()` on any object reachable in the render context, even when `__toString` on its class is not allowlisted. Confirmed bypass vectors: - Conditional expressions (`a ? b : c`, `a ?: b`, `a ?? b`) used as the input of a string-coercing filter or as a filter/function argument. - The `matches` operator and the loose comparison operators (`==`, `!=`, `<`, `>`, `<=`, `>=`, `<=>`), which coerce a `Stringable` operand to string and can be used as an oracle to recover the value byte by byte (no tag, filter or function needs to be allowlisted). - Twig tests in general (which were never policy-gated), in particular `is empty` which casts a `Stringable` value via `(string) $value` in `CoreExtension::testEmpty()`. - Null-coalesce expressions nested in concatenation, and the direct output of allowed functions or filters that return a `Stringable` object. - Arguments passed to allowed object methods, template-name expressions of template-loading tags (`include`, `extends`, `use`, ...), dynamic attribute/property names, and spread arguments from `Traversable` objects. - The `do` tag and the


Mitigations:

Architectural layers (MAESTRO):