security #cve-2026-46639 Fix sandbox bypass in object destructuring assignment (alexandre-daubois)

This PR was merged into the twig-3.x branch.
This commit is contained in:
Fabien Potencier
2026-05-19 21:37:04 +02:00
2 changed files with 36 additions and 1 deletions
@@ -13,6 +13,7 @@ namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Error\SyntaxError;
use Twig\Extension\SandboxExtension;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\Variable\ContextVariable;
@@ -64,7 +65,7 @@ class ObjectDestructuringSetBinary extends AbstractBinary
if ($i) {
$compiler->raw(', ');
}
$compiler->raw('CoreExtension::getAttribute($this->env, $this->source, ')->subcompile($this->getNode('right'))->raw(', ')->repr($mapping['property'])->raw(', [], \\Twig\\Template::ANY_CALL, false, false, false, ')->repr($this->getNode('right')->getTemplateLine())->raw(')');
$compiler->raw('CoreExtension::getAttribute($this->env, $this->source, ')->subcompile($this->getNode('right'))->raw(', ')->repr($mapping['property'])->raw(', [], \\Twig\\Template::ANY_CALL, false, false, ')->repr($compiler->getEnvironment()->hasExtension(SandboxExtension::class))->raw(', ')->repr($this->getNode('right')->getTemplateLine())->raw(')');
}
$compiler->raw(']');
}
+34
View File
@@ -423,6 +423,40 @@ class SandboxTest extends TestCase
$this->assertEquals('bar', $twig->load('1_basic4')->render(self::$params), 'Sandbox allow some properties');
}
public function testSandboxAllowDestructuring()
{
$template = '{% do {bar: x, foo: y} = obj %}{{ x }}-{{ y }}';
$twig = $this->getEnvironment(true, [], ['index' => $template], ['do'], [], ['Twig\Tests\Extension\FooObject' => 'foo'], ['Twig\Tests\Extension\FooObject' => 'bar']);
FooObject::reset();
$this->assertSame('bar-foo', $twig->load('index')->render(self::$params), 'Sandbox allows destructuring when properties and methods are allowed');
}
public function testSandboxUnallowedDestructuringProperty()
{
$template = '{% do {bar: x} = obj %}{{ x }}';
$twig = $this->getEnvironment(true, [], ['index' => $template], ['do']);
try {
$twig->load('index')->render(self::$params);
$this->fail('Sandbox throws a SecurityError exception if an unallowed property is read via destructuring');
} catch (SecurityNotAllowedPropertyError $e) {
$this->assertSame('Twig\Tests\Extension\FooObject', $e->getClassName());
$this->assertSame('bar', $e->getPropertyName());
}
}
public function testSandboxUnallowedDestructuringMethod()
{
$template = '{% do {foo: y} = obj %}{{ y }}';
$twig = $this->getEnvironment(true, [], ['index' => $template], ['do'], [], [], ['Twig\Tests\Extension\FooObject' => 'foo']);
try {
$twig->load('index')->render(self::$params);
$this->fail('Sandbox throws a SecurityError exception if an unallowed method is called via destructuring');
} catch (SecurityNotAllowedMethodError $e) {
$this->assertSame('Twig\Tests\Extension\FooObject', $e->getClassName());
$this->assertSame('foo', $e->getMethodName());
}
}
public function testSandboxAllowFunction()
{
$twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], ['cycle']);