CVE-2026-49981
Twig: Sandbox filter, tag and function allow-list bypass when sandbox state changes between renders for a cached `Template`
Twig is a template language for PHP. Prior to 3.27.0, the per-template filter, tag, and function allow-list verdict is computed when a Template instance is constructed and can remain cached after sandbox state changes between renders, allowing a later sandboxed render to reuse a template that was originally checked with a different or empty policy. This issue is fixed in version 3.27.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- Low
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
6.0
Twig: Sandbox filter, tag and function allow-list bypass when sandbox state changes between renders for a cached `Template`
Twig is a template language for PHP. Prior to 3.27.0, the per-template filter, tag, and function allow-list verdict is computed when a Template instance is constructed and can remain cached after sandbox state changes between renders, allowing a later sandboxed render to reuse a template that was originally checked with a different or empty policy. This issue is fixed in version 3.27.0.
- Record
CVE-2026-49981- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-693, CWE-863
- Updated
- 2026-11-02
EUVDEUVD-2026-44510
6.0
Twig is a template language for PHP. Prior to 3.27.0, the per-template filter, tag, and function allow-list verdict is computed when a Template instance is constructed and can remain cached after sandbox state changes between renders, allowing a later sandboxed render to reuse a template that was originally checked with a different or empty policy. This issue is fixed in version 3.27.0.
- Record
EUVD-2026-44510- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-529h-vh3j-85hq
8.7
Twig: Sandbox filter, tag and function allow-list bypass when sandbox state changes between renders for a cached `Template`
Description
The per-template filter, tag and function allow-list check is compiled into the checkSecurity() method of each Template subclass and was invoked once from the constructor, gated by SandboxExtension::isSandboxed($source). Template instances are then cached on the Environment in $loadedTemplates, so the verdict computed at construction time was sticky for the rest of the process.
Any later change of sandbox state on the same Environment left that cached verdict in place: toggling SandboxExtension::enableSandbox()/disableSandbox(), swapping the policy via setSecurityPolicy(), a SourcePolicyInterface decision flip, or simply having a parent, macro or included template pre-instantiated outside the sandbox before a sandboxed render reached it. In all of these cases, the filters, tags and functions used by the affected template kept running with the original (typically empty) check, bypassing the SecurityPolicy allow-list.
Method, property and __toString allow-lists are not affected: they are enforced at every call site at runtime through SandboxExtension::checkMethodAllowed(), checkPropertyAllowed() and ensureToStringAllowed(), which re-read the current state on every call.
Long-lived workers (FrankenPHP, RoadRunner, Symfony Messenger consumers, FPM with hot autoloading) that share a single Environment between sandboxed and non-sandboxed renders are the most exposed: a single non-sandboxed render of a shared layout pre-warms its Template instance, after which any later sandboxed render that extends, uses, includes or imports from that layout silently skips the filter/tag/function allow-list for the pre-warmed instance.
Resolution
The allow-list check is no longer run from the constructor. Template gains a public ensureSecurityChecked() method that calls the compiled checkSecurity() only when SandboxExtension::isSandboxed($source) returns true for the current source, and it is invoked at every entry point that can reach a Template instance whose security has not yet been verified against the current state: Template::yield(), Template::yieldBlock() (on the resolved block template, which covers extends, use, traits and parent blocks), Template::getParent() (which evaluates user code when the parent name is dynamic) and Template::getTemplateForMacro() (on the resolved macro template).
The explicit checkSecurity() calls previously emitted by IncludeNode and CoreExtension::include() are removed: the included template's own yield() now re-runs the check against the current sandbox state. The compiled checkSecurity() body is a cheap walk over compile-time-static arrays, so the per-render cost is negligible. Old cached compiled PHP files keep working unchanged: the constructor-time call they still contain is idempotent.
Credits
Twig would like to thank Fabien Potencier for reporting and fixing the issue.
- Record
GHSA-529h-vh3j-85hq- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-693, CWE-863
- Updated
- 2026-11-02
NVD
6.0
Twig is a template language for PHP. Prior to 3.27.0, the per-template filter, tag, and function allow-list verdict is computed when a Template instance is constructed and can remain cached after sandbox state changes between renders, allowing a later sandboxed render to reuse a template that was originally checked with a different or empty policy. This issue is fixed in version 3.27.0.
- Record
CVE-2026-49981- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N - Weakness
- CWE-693, CWE-863
- Updated
- 2026-11-02
OSV
6.0
Twig: Sandbox filter, tag and function allow-list bypass when sandbox state changes between renders for a cached `Template`
Twig is a template language for PHP. Prior to 3.27.0, the per-template filter, tag, and function allow-list verdict is computed when a Template instance is constructed and can remain cached after sandbox state changes between renders, allowing a later sandboxed render to reuse a template that was originally checked with a different or empty policy. This issue is fixed in version 3.27.0.
- Record
CVE-2026-49981- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-693, CWE-863
- Updated
- 2026-11-02
OSVDEBIAN-CVE-2026-49981
8.2
Twig is a template language for PHP. Prior to 3.27.0, the per-template filter, tag, and function allow-list verdict is computed when a Template instance is constructed and can remain cached after sandbox state changes between renders, allowing a later sandboxed render to reuse a template that was originally checked with a different or empty policy. This issue is fixed in version 3.27.0.
- Record
DEBIAN-CVE-2026-49981- CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N - Updated
- 2026-11-02
Show 6 moreShow less
OSVGHSA-529h-vh3j-85hq
8.7
Twig: Sandbox filter, tag and function allow-list bypass when sandbox state changes between renders for a cached `Template`
Description
The per-template filter, tag and function allow-list check is compiled into the checkSecurity() method of each Template subclass and was invoked once from the constructor, gated by SandboxExtension::isSandboxed($source). Template instances are then cached on the Environment in $loadedTemplates, so the verdict computed at construction time was sticky for the rest of the process.
Any later change of sandbox state on the same Environment left that cached verdict in place: toggling SandboxExtension::enableSandbox()/disableSandbox(), swapping the policy via setSecurityPolicy(), a SourcePolicyInterface decision flip, or simply having a parent, macro or included template pre-instantiated outside the sandbox before a sandboxed render reached it. In all of these cases, the filters, tags and functions used by the affected template kept running with the original (typically empty) check, bypassing the SecurityPolicy allow-list.
Method, property and __toString allow-lists are not affected: they are enforced at every call site at runtime through SandboxExtension::checkMethodAllowed(), checkPropertyAllowed() and ensureToStringAllowed(), which re-read the current state on every call.
Long-lived workers (FrankenPHP, RoadRunner, Symfony Messenger consumers, FPM with hot autoloading) that share a single Environment between sandboxed and non-sandboxed renders are the most exposed: a single non-sandboxed render of a shared layout pre-warms its Template instance, after which any later sandboxed render that extends, uses, includes or imports from that layout silently skips the filter/tag/function allow-list for the pre-warmed instance.
Resolution
The allow-list check is no longer run from the constructor. Template gains a public ensureSecurityChecked() method that calls the compiled checkSecurity() only when SandboxExtension::isSandboxed($source) returns true for the current source, and it is invoked at every entry point that can reach a Template instance whose security has not yet been verified against the current state: Template::yield(), Template::yieldBlock() (on the resolved block template, which covers extends, use, traits and parent blocks), Template::getParent() (which evaluates user code when the parent name is dynamic) and Template::getTemplateForMacro() (on the resolved macro template).
The explicit checkSecurity() calls previously emitted by IncludeNode and CoreExtension::include() are removed: the included template's own yield() now re-runs the check against the current sandbox state. The compiled checkSecurity() body is a cheap walk over compile-time-static arrays, so the per-render cost is negligible. Old cached compiled PHP files keep working unchanged: the constructor-time call they still contain is idempotent.
Credits
Twig would like to thank Fabien Potencier for reporting and fixing the issue.
- Record
GHSA-529h-vh3j-85hq- CVSS 4.0
- 8.7 High
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-693, CWE-863
- Updated
- 2026-11-02
OSVUBUNTU-CVE-2026-49981
6.0
Twig is a template language for PHP. Prior to 3.27.0, the per-template filter, tag, and function allow-list verdict is computed when a Template instance is constructed and can remain cached after sandbox state changes between renders, allowing a later sandboxed render to reuse a template that was originally checked with a different or empty policy. This issue is fixed in version 3.27.0.
- Record
UBUNTU-CVE-2026-49981- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N - CVSS 3.1
- 8.2 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N - Updated
- 2026-11-02
OSVCLSA-2026-1783991054 No score
TuxCare security update for twig/twig (11 CVEs)
TuxCare rebuilt twig/twig to address 11 CVEs. Fixed in v2.16.1-p1+tuxcare.
- Record
CLSA-2026-1783991054- Updated
- 2026-11-02
OSVCLSA-2026-1784682913 No score
TuxCare security update for twig/twig (10 CVEs)
TuxCare rebuilt twig/twig to address 10 CVEs. Fixed in v2.15.6-p2+tuxcare.
- Record
CLSA-2026-1784682913- Updated
- 2026-11-02
OSVCLSA-2026-1786534048 No score
TuxCare security update for twig/twig (8 CVEs)
TuxCare rebuilt twig/twig to address 8 CVEs. Fixed in v1.44.8-p1+tuxcare.
- Record
CLSA-2026-1786534048- Updated
- 2026-11-02
OSVROOT-APP-COMPOSER-CVE-2026-49981 No score
CVE-2026-49981 in twig/twig - Patched by Root
Root has patched CVE-2026-49981 in the twig/twig package for Root:Composer. Multiple fixed versions available.
- Record
ROOT-APP-COMPOSER-CVE-2026-49981- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
twig/twigPackagist |
through 3.26.0 | 3.27.0 |
twig/twigTuxCare:Packagist From an advisory covering 11 CVEs |
v2.16.1 up to v2.16.1-p1+tuxcare | v2.16.1-p1+tuxcare |
twig/twigTuxCare:Packagist From an advisory covering 10 CVEs |
v2.15.6 up to v2.15.6-p2+tuxcare, plus listed versions v2.15.6-p1+tuxcare |
v2.15.6-p2+tuxcare |
twig/twigTuxCare:Packagist From an advisory covering 8 CVEs |
v1.44.8 up to v1.44.8-p1+tuxcare | v1.44.8-p1+tuxcare |
php-twigDebian:12 |
All versions, plus listed versions 3.10.1-13.10.3-13.11.0-13.13.0-13.14.0-1 Show 45 moreShow less3.14.0-23.14.0-33.14.0-43.14.2-13.14.2-23.14.2-33.15.0-13.15.0-23.17.1-13.17.1-23.18.0-13.18.0-23.18.0-43.18.0-53.18.0-63.18.0-73.19.0-13.19.0-1~bootstrap3.20.0-13.20.0-1~bootstrap3.20.0-23.21.1-13.21.1-23.21.1-33.22.0-13.22.0-23.22.1-13.22.1-23.22.1-33.22.2-13.22.2-23.23.0-13.23.0-23.24.0-13.26.0-13.27.0-13.27.1-13.28.0-13.5.1-13.5.1-1+deb12u13.5.1-1+deb12u23.5.1-1+deb12u33.6.0-13.6.1-13.7.0-1 |
None known |
php-twigDebian:13 |
before 3.27.0-0+deb13u1, plus listed versions 3.20.0-23.21.1-13.21.1-23.21.1-33.22.0-1 Show 11 moreShow less3.22.0-23.22.1-13.22.1-23.22.1-33.22.2-13.22.2-23.23.0-13.23.0-23.24.0-13.26.0-0+deb13u13.26.0-1 |
3.27.0-0+deb13u1 |
php-twigDebian:14 |
before 3.27.0-1, plus listed versions 3.20.0-23.21.1-13.21.1-23.21.1-33.22.0-1 Show 10 moreShow less3.22.0-23.22.1-13.22.1-23.22.1-33.22.2-13.22.2-23.23.0-13.23.0-23.24.0-13.26.0-1 |
3.27.0-1 |
twig/twigPackagist |
before 3.27.0, plus listed versions 1.3.01.4.01.5.01.5.11.6.0 Show 45 moreShow less1.6.11.6.21.6.31.6.41.6.5v1.10.0v1.10.1v1.10.2v1.10.3v1.11.0v1.11.1v1.12.0v1.12.0-RC1v1.12.1v1.12.2v1.12.3v1.13.0v1.13.1v1.13.2v1.14.0v1.14.1v1.14.2v1.15.0v1.15.1v1.16.0v1.16.1v1.16.2v1.16.3v1.17.0v1.18.0v1.18.1v1.18.2v1.19.0v1.20.0v1.21.0v1.21.1v1.21.2v1.22.0v1.22.1v1.22.2v1.22.3v1.23.0v1.23.1v1.23.2v1.23.3 |
3.27.0 |
twig/twigRoot:Composer |
before v3.11.3-p12007004+root.io | v3.11.3-p12007004+root.iov3.11.3-p12007005+root.io |
twigUbuntu:Pro:16.04:LTS |
All versions, plus listed versions 1.20.0-11.23.1-1ubuntu11.23.1-1ubuntu41.23.1-1ubuntu4+esm1 |
None known |
Show 6 moreShow less
| Package | Affected | Fixed |
|---|---|---|
twigUbuntu:Pro:18.04:LTS |
All versions, plus listed versions 1.24.0-2ubuntu12.4.4-2ubuntu12.4.6-12.4.6-1ubuntu0.1~esm1 |
None known |
php-twigUbuntu:Pro:20.04:LTS |
All versions, plus listed versions 2.11.3-22.12.1-12.12.2-12.12.3-12.12.5-12.12.5-1ubuntu0.1~esm12.12.5-1ubuntu0.1~esm2 |
None known |
php-twigUbuntu:Pro:22.04:LTS |
All versions, plus listed versions 3.3.2-1ubuntu33.3.4-13.3.6-13.3.7-13.3.8-2ubuntu43.3.8-2ubuntu4+esm13.3.8-2ubuntu4+esm2 |
None known |
php-twigUbuntu:Pro:24.04:LTS |
All versions, plus listed versions 3.7.0-13.7.1-13.8.0-23.8.0-2ubuntu0.1~esm13.8.0-2ubuntu13.8.0-2ubuntu1+esm1 |
None known |
php-twigUbuntu:26.10 |
All versions, plus listed versions 3.23.0-2build73.27.1-13.28.0-1 |
None known |
php-twigUbuntu:Pro:26.04:LTS |
All versions, plus listed versions 3.20.0-2ubuntu13.23.0-2build53.23.0-2build73.23.0-2ubuntu0.1~esm1 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected, facts | ||
| aliases | EUVD | |
| first indexed | CVE List, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-49981 is a medium vulnerability (CVSS 6.0) in Symfony Twig, published July 1, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.36%, the 30th percentile. Fixed in twig/twig 3.27.0.
References
github.com/twigphp/Twig/security/advisories/GHSA-529h-vh3j-85hq
github.com/twigphp/Twig/commit/23eb6eb1267cb0d303b91eb5cff9b0c559c538a4
github.com/twigphp/Twig/releases/tag/v3.27.0
github.com/advisories/GHSA-529h-vh3j-85hq
github.com/CVEProject/cvelistV5/tree/main/cves/2026/49xxx/CVE-2026-49981.json
nvd.nist.gov/vuln/detail/CVE-2026-49981
Left out here: 202 versions. The JSON record has them.