CVE-2026-47732: Twig Sandbox: Multiple `__toString()` Policy Bypasses Vulnerability

HIGH
Advisory Identifier: GHSA-pr2w-4gpj-cpq4
CWE-20
Published
Advisory Summary

CVE-2026-47732 is a HIGH sandbox: multiple `__tostring()` policy bypasses vulnerability (CWE-20: Improper Input Validation) affecting Twig libraries in the Packagist ecosystem. The issue involves sandbox: multiple `__tostring()` policy bypasses via unguarded string coercion points. Remediation is available by upgrading affected packages to version 3.26.0.

CVE-2026-47732 at a Glance

CVE Identifier CVE-2026-47732
GHSA Identifier GHSA-pr2w-4gpj-cpq4
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-20: Improper Input Validation
Affected Product Twig
Package Ecosystem Packagist
Published Date
Last Updated

How to Fix CVE-2026-47732

To remediate this vulnerability, upgrade all affected Twig dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

twig/twig fixed: 3.26.0
Upgrade twig/twig to 3.26.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for twig/twig

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-47732, inspect your project dependencies for Twig:

inspect dependencies for twig/twig

Analyze the terminal output to locate any version of Twig in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 3.26.0), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-20: Improper Input Validation, where the product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly. In Twig, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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 `..` range operator. ### Resolution The sandbox now wraps every child node that the parent will string-coerce at runtime, instead of relying on a hardcoded list of node types in `SandboxNodeVisitor`. A new `Twig\Node\CoercesChildrenToStringInterface` lets nodes declare which of their children must be guarded; core nodes (concatenation, comparison and range binaries, filter/function/test expressions, `do`, `include`, `extends`, `use`, ...) implement it. Spread arguments are materialised and policy-checked via the new `SandboxExtension::ensureSpreadAllowed()`, and dynamic attribute names are checked at runtime inside `CoreExtension::getAttribute()`. ### Credits Twig would like to thank Anthropic Glasswing and El Kharoubi Iosif for reporting the issues, and Fabien Potencier for providing the fixes.

CVE-2026-47732 Affected Twig Versions

1 component(s)
Fixed Versions:
3.26.0
Product / Package Ecosystem Affected Versions Fixed Version
twig/twig
Packagist
< 3.26.0
3.26.0

References

Frequently Asked Questions

What is CVE-2026-47732?
CVE-2026-47732 is a security vulnerability in Twig involving Sandbox: multiple `__toString()` policy bypasses via unguarded string coercion points under CWE-20: Improper Input Validation.
Which Twig versions are affected?
Affected versions include releases prior to 3.26.0 across affected packages: twig/twig.
What version fixes CVE-2026-47732?
Upgrading to 3.26.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is twig/twig affected?
Yes, twig/twig is affected in releases prior to 3.26.0. Upgrade your dependency to 3.26.0 or later.
What CWE is associated with CVE-2026-47732?
CVE-2026-47732 is classified under CWE-20: Improper Input Validation (The product receives input or data, but it does not validate or incorrectly validates that the input has the properties that are required to process the data safely and correctly.).
How can I check Packagist dependencies for CVE-2026-47732?
Run the dependency tree command: 'inspect dependencies for twig/twig'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-pr2w-4gpj-cpq4",
  "modified" : "2026-06-05T21:47:11Z",
  "published" : "2026-06-05T21:47:11Z",
  "aliases" : [ "CVE-2026-47732" ],
  "summary" : "Twig: Sandbox: multiple `__toString()` policy bypasses via unguarded string coercion points",
  "details" : "### Description\n\n`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.\n\nConfirmed bypass vectors:\n\n- Conditional expressions (`a ? b : c`, `a ?: b`, `a ?? b`) used as the input of a string-coercing filter or as a filter/function argument.\n- 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).\n- Twig tests in general (which were never policy-gated), in particular `is empty` which casts a `Stringable` value via `(string) $value` in `CoreExtension::testEmpty()`.\n- Null-coalesce expressions nested in concatenation, and the direct output of allowed functions or filters that return a `Stringable` object.\n- 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.\n- The `do` tag and the `..` range operator.\n\n### Resolution\n\nThe sandbox now wraps every child node that the parent will string-coerce at runtime, instead of relying on a hardcoded list of node types in `SandboxNodeVisitor`. A new `Twig\\Node\\CoercesChildrenToStringInterface` lets nodes declare which of their children must be guarded; core nodes (concatenation, comparison and range binaries, filter/function/test expressions, `do`, `include`, `extends`, `use`, ...) implement it. Spread arguments are materialised and policy-checked via the new `SandboxExtension::ensureSpreadAllowed()`, and dynamic attribute names are checked at runtime inside `CoreExtension::getAttribute()`.\n\n### Credits\n\nTwig would like to thank Anthropic Glasswing and El Kharoubi Iosif for reporting the issues, and Fabien Potencier for providing the fixes.",
  "severity" : [ ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "twig/twig"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "3.26.0"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 3.25.0"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/twigphp/Twig/security/advisories/GHSA-pr2w-4gpj-cpq4"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/FriendsOfPHP/security-advisories/blob/master/twig/twig/CVE-2026-47732.yaml"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/twigphp/Twig"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/twigphp/Twig/releases/tag/v3.26.0"
  }, {
    "type" : "WEB",
    "url" : "https://symfony.com/cve-2026-47732"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-20" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-06-05T21:47:11Z",
    "nvd_published_at" : null
  }
}