<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:atom="http://www.w3.org/2005/Atom"
>
    <channel>
        <title>Symfony Blog</title>
        <atom:link href="https://feeds.feedburner.com/symfony/blog" rel="self" type="application/rss+xml" />
        <link>https://symfony.com/blog/</link>
        <description>Most recent posts published on the Symfony project blog</description>
        <pubDate>Thu, 28 May 2026 13:12:55 +0200</pubDate>
        <lastBuildDate>Wed, 27 May 2026 17:30:00 +0200</lastBuildDate>
        <language>en</language>
                        <item>
            <title><![CDATA[SymfonyOnline June 2026: Custom PHPStan Rules: Guardrails for AI-Assisted Symfony Code]]></title>
            <link>https://symfony.com/blog/symfonyonline-june-2026-custom-phpstan-rules-guardrails-for-ai-assisted-symfony-code?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>
    

SymfonyOnline June 2026 is officially scheduled for June 11 and 12, 2026! Join us online for two tracks of cutting-edge tech talks: one full day dedicated to AI and another full day focus on Symfony Deep Dive.

🎤 Speaker announcement!

We are excited…</description>
            <content:encoded><![CDATA[
                                <p><a class="block text-center" href="https://live.symfony.com/2026-online-june" title="Nl Blog Banner 2026 04 17T163921 336">
    <img src="https://symfony.com/uploads/assets/blog/NL-BLOG-Banner-2026-04-17T163921-336.png" alt="Nl Blog Banner 2026 04 17T163921 336">
</a>
<strong><a href="https://live.symfony.com/2026-online-june">SymfonyOnline June 2026</a></strong> is officially scheduled for June 11 and 12, 2026! Join us online for two tracks of cutting-edge tech talks: one full day dedicated to AI and another full day focus on Symfony Deep Dive.</p>

<h3>🎤 Speaker announcement!</h3>

<p>We are excited to host <strong><a href="https://connect.symfony.com/profile/daveliddament">Dave Liddament</a></strong>, Director, Lamp Bristol, for his talk <strong><a href="https://live.symfony.com/2026-online-june/schedule/custom-phpstan-rules-guardrails-for-ai-assisted-symfony-code">"Custom PHPStan Rules: Guardrails for AI-Assisted Symfony Code"</a></strong>:</p>

<p>"AI assistants speed up coding, but their output is non-deterministic and needs hard guardrails. The most reliable enforcement layer is the one your tooling applies on every run: custom PHPStan rules.</p>

<p>In this talk, we'll start with a coding workflow built around plan mode, voice input, and self-correcting test loops. We'll then reframe the testing pyramid for the AI era: deterministic checks first, AI-driven review with <code>AGENTS.md</code> and skills second, human review last.</p>

<p>The bulk of the session covers PHPStan internals: how <code>nikic/php-parser</code> turns code into an AST, how to inspect nodes with the Rector playground, and how to implement the <code>Rule</code> interface. Two worked examples anchor the talk: banning <code>extends ServiceEntityRepository</code>, and enforcing kebab-case in <code>#[Route]</code> paths whether the argument is positional or named.</p>

<p>By the end, you'll be ready to write your own rules and turn one-off AI corrections into team-wide invariants."</p>

<p>👉 Discover more talks by reading the <strong><a href="https://live.symfony.com/2026-online-june/schedule">full schedule</a></strong></p>

<p>✨ Key Feature: All talks are pre-recorded to ensure the highest technical quality, but speakers will be joining us live to answer your questions in real-time during the dedicated Q&amp;A sessions!</p>

<hr />

<h3>🛠️ Pre-conference Workshops (June 9-10)</h3>

<p>Don't forget that the conference is preceded by two days of hands-on technical workshops. These small-group sessions are the perfect opportunity to master specific Symfony features under the guidance of certified trainers: <strong><a href="https://live.symfony.com/2026-online-june/workshop">Discover the topics!</a></strong></p>

<p>Note: Workshop spots are limited and no replays are available for these sessions to ensure an interactive learning experience.</p>

<h3>🎟️ Register now!</h3>

<p>Join the global PHP &amp; Symfony community from the comfort of your home or office by clicking <strong><a href="https://live.symfony.com/2026-online-june/registration/">here</a></strong>.</p>

<ul>
<li>▶️ <strong>Instant Replays</strong>: Missed a session? All talks are available for replay as soon as they start.</li>
<li>🆒 <strong>Accessibility</strong>: English subtitles are provided for all presentations.</li>
</ul>

<p>We look forward to seeing you online to explore the future of PHP together!</p>

<hr />

<h3>Join us online!</h3>

<p>💡Follow the "conference" blog posts to not miss anything!</p>

<p>Want the latest Symfony updates? Follow us and tune in from wherever you are 🌎</p>

<p><a class="block text-center" href="https://linktr.ee/symfony">
   <img src="https://symfony.com/uploads/assets/blog/Banner-BLOG.png" alt="Banner Blog">
</a></p>

                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/symfonyonline-june-2026-custom-phpstan-rules-guardrails-for-ai-assisted-symfony-code?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Eloïse Charrier ]]></dc:creator>
            <pubDate>Wed, 27 May 2026 17:30:00 +0200</pubDate>
            <comments>https://symfony.com/blog/symfonyonline-june-2026-custom-phpstan-rules-guardrails-for-ai-assisted-symfony-code?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[New in Symfony 8.1: Improved JSON Streaming and Querying]]></title>
            <link>https://symfony.com/blog/new-in-symfony-8-1-improved-json-streaming-and-querying?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>Symfony includes two components dedicated to working with JSON:

    JsonStreamer encodes PHP data into JSON and decodes JSON back into PHP
objects by streaming the contents, which provides high performance and low
memory usage even for large payloads;
JsonPath…</description>
            <content:encoded><![CDATA[
                                <p>Symfony includes two components dedicated to working with JSON:</p>
<ul>
    <li><a href="https://symfony.com/json-streamer" class="reference external">JsonStreamer</a> encodes PHP data into JSON and decodes JSON back into PHP
objects by streaming the contents, which provides high performance and low
memory usage even for large payloads;</li>
<li><a href="https://symfony.com/json-path" class="reference external">JsonPath</a> queries and extracts values from JSON documents using the standard
JSONPath syntax.</li>
</ul>
<p>Symfony 8.1 improves both components in several ways.</p>
<div class="section">
<h2 id="handling-value-objects"><a class="headerlink" href="#handling-value-objects" title="Permalink to this headline">Handling Value Objects</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/mtarld">
                <img src="https://connect.symfony.com/profile/mtarld.picture" alt="Mathias Arlaud">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/mtarld">Mathias Arlaud</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63339">#63339</a>
                                                </span>
            </div>
</div>
<p>By default, JsonStreamer serializes an object by traversing its properties one
by one. However, some objects are better represented as a single scalar value
than as a nested JSON object. Think of a <code translate="no" class="notranslate">Money</code> object encoded as <code translate="no" class="notranslate">"100 EUR"</code>
or a temperature represented as a number.</p>
<p>Symfony 8.1 introduces a generic mechanism for this called <em>value objects</em>.
Implement the new <code translate="no" class="notranslate">ValueObjectTransformerInterface</code> to define how an object
maps to and from a scalar value:</p>
<div translate="no" data-loc="35" class="notranslate codeblock codeblock-length-md codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-comment">// src/Transformer/MoneyValueObjectTransformer.php</span>
<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Transformer</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">ValueObject</span>\<span class="hljs-title">Money</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">JsonStreamer</span>\<span class="hljs-title">Transformer</span>\<span class="hljs-title">ValueObjectTransformerInterface</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">TypeInfo</span>\<span class="hljs-title">Type</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">TypeInfo</span>\<span class="hljs-title">Type</span>\<span class="hljs-title">BuiltinType</span>;

<span class="hljs-comment">/**
 * <span class="hljs-doctag">@implements</span> ValueObjectTransformerInterface&lt;Money, string&gt;
 */</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MoneyValueObjectTransformer</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">ValueObjectTransformerInterface</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transform</span><span class="hljs-params">(<span class="hljs-keyword">object</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>object</span>, <span class="hljs-keyword">array</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>options</span> = [])</span>: <span class="hljs-title">int</span>|<span class="hljs-title">float</span>|<span class="hljs-title">string</span>|<span class="hljs-title">bool</span>|<span class="hljs-title">null</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>object</span>-&gt;amount.<span class="hljs-string">' '</span>.<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>object</span>-&gt;currency;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reverseTransform</span><span class="hljs-params">(<span class="hljs-keyword">int</span>|<span class="hljs-keyword">float</span>|<span class="hljs-keyword">string</span>|<span class="hljs-keyword">bool</span>|<span class="hljs-keyword">null</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>scalar</span>, <span class="hljs-keyword">array</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>options</span> = [])</span>: <span class="hljs-title">object</span>
    </span>{
        [<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>amount</span>, <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>currency</span>] = <span class="hljs-title invoke__">explode</span>(<span class="hljs-string">' '</span>, <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>scalar</span>);

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title invoke__">Money</span>((<span class="hljs-keyword">int</span>) <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>amount</span>, <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>currency</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStreamValueType</span><span class="hljs-params">()</span>: <span class="hljs-title">BuiltinType</span>
    </span>{
        <span class="hljs-keyword">return</span> Type::<span class="hljs-keyword">string</span>();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getValueObjectClassName</span><span class="hljs-params">()</span>: <span class="hljs-title">string</span>
    </span>{
        <span class="hljs-keyword">return</span> Money::<span class="hljs-variable language_">class</span>;
    }
}</code></pre>
    </div>
</div>
<p>Symfony auto-registers these transformers, and the generated code then calls
the transformer instead of traversing the object's properties:</p>
<div translate="no" data-loc="5" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-comment">// write: "100 EUR"</span>
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>json</span> = <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>jsonStreamWriter</span>-&gt;<span class="hljs-title invoke__">write</span>(<span class="hljs-keyword">new</span> <span class="hljs-title invoke__">Money</span>(<span class="hljs-number">100</span>, <span class="hljs-string">'EUR'</span>), Type::object(Money::<span class="hljs-variable language_">class</span>));

<span class="hljs-comment">// read: "100 EUR" -&gt; Money(100, 'EUR')</span>
<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>money</span> = <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>jsonStreamReader</span>-&gt;<span class="hljs-title invoke__">read</span>(<span class="hljs-string">'"100 EUR"'</span>, Type::object(Money::<span class="hljs-variable language_">class</span>));</code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="built-in-dateinterval-and-datetimezone-value-objects"><a class="headerlink" href="#built-in-dateinterval-and-datetimezone-value-objects" title="Permalink to this headline">Built-in DateInterval and DateTimeZone Value Objects</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/mtarld">
                <img src="https://connect.symfony.com/profile/mtarld.picture" alt="Mathias Arlaud">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/mtarld">Mathias Arlaud</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63742">#63742</a>
                     and                                     <a target="_blank" href="https://github.com/symfony/symfony/pull/63879">#63879</a>
                                                </span>
            </div>
</div>
<p>Building on the value object mechanism, Symfony 8.1 now handles <code translate="no" class="notranslate">DateInterval</code>
and <code translate="no" class="notranslate">DateTimeZone</code> objects as value objects out of the box, following the same
approach already used for <code translate="no" class="notranslate">DateTimeInterface</code>.</p>
<p><code translate="no" class="notranslate">DateInterval</code> objects are serialized to ISO 8601 duration strings (e.g.
<code translate="no" class="notranslate">P2Y6M1DT12H30M5S</code>), and <code translate="no" class="notranslate">DateTimeZone</code> objects use their name (e.g.
<code translate="no" class="notranslate">"Europe/Paris"</code> or <code translate="no" class="notranslate">"+02:00"</code>). You can customize the duration format with
the <code translate="no" class="notranslate">date_interval_format</code> option:</p>
<div translate="no" data-loc="5" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">TypeInfo</span>\<span class="hljs-title">Type</span>;

<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>json</span> = <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>jsonStreamWriter</span>-&gt;<span class="hljs-title invoke__">write</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>task</span>, Type::object(Task::<span class="hljs-variable language_">class</span>), [
    <span class="hljs-string">'date_interval_format'</span> =&gt; <span class="hljs-string">'P%yY%mM%dDT%hH%iM%sS'</span>,
]);</code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="configuring-the-datetime-timezone"><a class="headerlink" href="#configuring-the-datetime-timezone" title="Permalink to this headline">Configuring the DateTime Timezone</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/mtarld">
                <img src="https://connect.symfony.com/profile/mtarld.picture" alt="Mathias Arlaud">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/mtarld">Mathias Arlaud</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/63735">#63735</a>
                                                </span>
            </div>
</div>
<p>When encoding or decoding <code translate="no" class="notranslate">DateTimeInterface</code> objects, you can now convert the
timezone by passing the <code translate="no" class="notranslate">date_time_timezone</code> option as a string or a
<code translate="no" class="notranslate">\DateTimeZone</code> instance:</p>
<div translate="no" data-loc="9" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">TypeInfo</span>\<span class="hljs-title">Type</span>;

<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>json</span> = <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>jsonStreamWriter</span>-&gt;<span class="hljs-title invoke__">write</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>event</span>, Type::object(Event::<span class="hljs-variable language_">class</span>), [
    <span class="hljs-string">'date_time_timezone'</span> =&gt; <span class="hljs-string">'Asia/Tokyo'</span>,
]);

<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>event</span> = <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>jsonStreamReader</span>-&gt;<span class="hljs-title invoke__">read</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>json</span>, Type::object(Event::<span class="hljs-variable language_">class</span>), [
    <span class="hljs-string">'date_time_timezone'</span> =&gt; <span class="hljs-keyword">new</span> \<span class="hljs-title invoke__">DateTimeZone</span>(<span class="hljs-string">'America/Mexico_City'</span>),
]);</code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="defining-default-options"><a class="headerlink" href="#defining-default-options" title="Permalink to this headline">Defining Default Options</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/mtarld">
                <img src="https://connect.symfony.com/profile/mtarld.picture" alt="Mathias Arlaud">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/mtarld">Mathias Arlaud</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/62599">#62599</a>
                                                </span>
            </div>
</div>
<p>Repeating the same options on every <code translate="no" class="notranslate">write()</code> and <code translate="no" class="notranslate">read()</code> call is tedious.
Symfony 8.1 lets you define default options for the entire application, so they
are applied automatically to every operation:</p>
<div translate="no" data-loc="6" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-comment"># config/packages/framework.yaml</span>
framework:
    json_streamer:
        default_options:
            <span class="hljs-comment"># encode properties whose value is null</span>
            include_null_properties: <span class="hljs-keyword">true</span></code></pre>
    </div>
</div>
<p>You can also define your own custom options here. Symfony passes them to your
transformers, so you can read them inside <code translate="no" class="notranslate">transform()</code> and <code translate="no" class="notranslate">reverseTransform()</code>:</p>
<div translate="no" data-loc="5" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-comment"># config/packages/framework.yaml</span>
framework:
    json_streamer:
        default_options:
            my_custom_option: <span class="hljs-string">'my_custom_value'</span></code></pre>
    </div>
</div>
</div>
<div class="section">
<h2 id="custom-jsonpath-functions"><a class="headerlink" href="#custom-jsonpath-functions" title="Permalink to this headline">Custom JsonPath Functions</a></h2>
<div class="blog-post-contributor-info">
    <div class="blog-post-contributor-avatar">
                    <a target="_blank" href="https://connect.symfony.com/profile/alexandre-daubois">
                <img src="https://connect.symfony.com/profile/alexandre-daubois.picture" alt="Alexandre Daubois">
            </a>
            </div>
    <div class="blog-post-contributor-contents">
        <span>Contributed by</span>
                    <a target="_blank" class="blog-post-contributor-name" href="https://connect.symfony.com/profile/alexandre-daubois">Alexandre Daubois</a>
                                        <span class="blog-post-contributor-prs"> in
                                    <a target="_blank" href="https://github.com/symfony/symfony/pull/62823">#62823</a>
                                                </span>
            </div>
</div>
<p>The JsonPath component already supports the standard functions defined by
<a href="https://datatracker.ietf.org/doc/html/rfc9535" class="reference external" rel="external noopener noreferrer" target="_blank">RFC 9535</a> (<code translate="no" class="notranslate">length()</code>, <code translate="no" class="notranslate">count()</code>, <code translate="no" class="notranslate">match()</code>, etc.). Symfony 8.1 lets
you register your own functions and use them inside filter expressions.</p>
<p>Create an invokable class and apply the <code translate="no" class="notranslate">#[AsJsonPathFunction]</code> attribute to
it. Symfony registers the function automatically and makes it available in all
JsonPath queries:</p>
<div translate="no" data-loc="10" class="notranslate codeblock codeblock-length-md codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-keyword">use</span> <span class="hljs-title">Symfony</span>\<span class="hljs-title">Component</span>\<span class="hljs-title">JsonPath</span>\<span class="hljs-title">Attribute</span>\<span class="hljs-title">AsJsonPathFunction</span>;

<span class="hljs-meta">#[AsJsonPathFunction(<span class="hljs-string">'upper'</span>)]</span>
<span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UppercaseFunction</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__invoke</span><span class="hljs-params">(<span class="hljs-keyword">mixed</span> <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>value</span>)</span>: ?<span class="hljs-title">string</span>
    </span>{
        <span class="hljs-keyword">return</span> \<span class="hljs-title invoke__">is_string</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>value</span>) ? <span class="hljs-title invoke__">strtoupper</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>value</span>) : <span class="hljs-keyword">null</span>;
    }
}</code></pre>
    </div>
</div>
<p>Inject the <code translate="no" class="notranslate">JsonPathCrawlerInterface</code> to get a crawler pre-configured with
all your custom functions, then use them in any expression:</p>
<div translate="no" data-loc="3" class="notranslate codeblock codeblock-length-sm codeblock-php">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>crawler</span> = <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>crawlerFactory</span>-&gt;<span class="hljs-title invoke__">crawl</span>(<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>json</span>);

<span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>result</span> = <span class="hljs-variable"><span class="hljs-variable-other-marker">$</span>crawler</span>-&gt;<span class="hljs-title invoke__">find</span>(<span class="hljs-string">'$.items[?upper(@.title) == "HELLO"]'</span>);</code></pre>
    </div>
</div>
<p>The number of arguments accepted by the function is inferred automatically from
the <code translate="no" class="notranslate">__invoke()</code> method signature. The optional <code translate="no" class="notranslate">returnType</code> argument
controls where the function can be used: a <code translate="no" class="notranslate">FunctionReturnType::Value</code> function
(the default) works in comparisons like the example above, while a
<code translate="no" class="notranslate">FunctionReturnType::Logical</code> function can be used as a standalone filter test
such as <code translate="no" class="notranslate">$.items[?is_positive(@.value)]</code>.</p>
</div>
                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/new-in-symfony-8-1-improved-json-streaming-and-querying?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Javier Eguiluz ]]></dc:creator>
            <pubDate>Wed, 27 May 2026 16:18:00 +0200</pubDate>
            <comments>https://symfony.com/blog/new-in-symfony-8-1-improved-json-streaming-and-querying?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[CVE-2026-48807: Sandbox `__toString()` policy bypass via `Traversable` in `join`/`replace` and `in`/`not in` operators]]></title>
            <link>https://symfony.com/blog/cve-2026-48807-sandbox-tostring-policy-bypass-via-traversable-in-join-replace-and-in-not-in-operators?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>
Affected versions
Twig versions &amp;lt;=3.26.0 are affected by this security issue.
The issue has been fixed in Twig 3.27.0.


Description
This is a residual bypass of CVE-2026-47732 / GHSA-pr2w-4gpj-cpq4 left
after the initial fix for unguarded __toString()…</description>
            <content:encoded><![CDATA[
                                <div class="section">
<h2 id="affected-versions"><a class="headerlink" href="#affected-versions" title="Permalink to this headline">Affected versions</a></h2>
<p>Twig versions &lt;=3.26.0 are affected by this security issue.</p>
<p>The issue has been fixed in Twig 3.27.0.</p>
</div>
<div class="section">
<h2 id="description"><a class="headerlink" href="#description" title="Permalink to this headline">Description</a></h2>
<p>This is a residual bypass of CVE-2026-47732 / GHSA-pr2w-4gpj-cpq4 left
after the initial fix for unguarded <code translate="no" class="notranslate">__toString()</code> calls. It covers two
related coercion points that were not caught by the original patch.</p>
<p><strong>Traversable in the join and replace filters.</strong>
<code translate="no" class="notranslate">SandboxExtension::ensureToStringAllowed()</code> recurses into PHP arrays so
that a <code translate="no" class="notranslate">Stringable</code> object hidden inside an array argument cannot be
string-coerced without consulting the security policy. The recursion stops
at PHP arrays: a <code translate="no" class="notranslate">Traversable</code> value passed at the same position is not
materialised, so its contents are not policy-checked. <code translate="no" class="notranslate">CoreExtension::join()</code>
and <code translate="no" class="notranslate">CoreExtension::replace()</code> later materialise such <code translate="no" class="notranslate">Traversable</code>
inputs through <code translate="no" class="notranslate">self::toArray()</code> and feed them to <code translate="no" class="notranslate">implode()</code> /
<code translate="no" class="notranslate">strtr()</code>, both of which implicitly call <code translate="no" class="notranslate">__toString()</code> on contained
<code translate="no" class="notranslate">Stringable</code> objects. The bypass also reproduces when the container
implements both <code translate="no" class="notranslate">Stringable</code> and <code translate="no" class="notranslate">Traversable</code>: the container's own
<code translate="no" class="notranslate">__toString()</code> is policy-checked, but the elements yielded by
<code translate="no" class="notranslate">getIterator()</code> are not, and the consuming filters still coerce them to
string.</p>
<p><strong>The in and not in operators.</strong> <code translate="no" class="notranslate">InBinary</code> and <code translate="no" class="notranslate">NotInBinary</code> compile
to <code translate="no" class="notranslate">CoreExtension::inFilter()</code>, which falls through to PHP's <code translate="no" class="notranslate">&lt;=&gt;</code>
operator when comparing a string with a <code translate="no" class="notranslate">Stringable</code> object. PHP coerces
the object to string via <code translate="no" class="notranslate">__toString()</code> without the sandbox policy being
consulted. Beyond the direct side effect, <code translate="no" class="notranslate">in</code> can also be used as a
content-leak oracle: each probe against an attacker-chosen needle leaks one
bit of equality, and chained probes can reconstruct the string returned by
<code translate="no" class="notranslate">__toString()</code> even when every method is denied. The bypass reproduces
with both array and <code translate="no" class="notranslate">Traversable</code> haystacks, and on both operand sides.</p>
<p>A sandboxed template author who is allowed to call <code translate="no" class="notranslate">join</code> / <code translate="no" class="notranslate">replace</code>,
or to use the <code translate="no" class="notranslate">in</code> / <code translate="no" class="notranslate">not in</code> operators, can therefore trigger a
disallowed <code translate="no" class="notranslate">__toString()</code> method on objects reachable from the render
context, even when that method is not on <code translate="no" class="notranslate">SecurityPolicy::$allowedMethods</code>.
The bypass reproduces both under global sandbox mode and when sandboxing is
enabled through <code translate="no" class="notranslate">SourcePolicyInterface</code>.</p>
</div>
<div class="section">
<h2 id="resolution"><a class="headerlink" href="#resolution" title="Permalink to this headline">Resolution</a></h2>
<p><code translate="no" class="notranslate">SandboxExtension::ensureToStringAllowed()</code> now also recurses into
<code translate="no" class="notranslate">Traversable</code> operands when sandboxing is active for the current source:
each value is materialised once and run through the same array-recursion
path, so the policy is consulted before the filter implementation can
coerce contained objects to strings. This applies to plain <code translate="no" class="notranslate">Traversable</code>
operands as well as to containers that implement both <code translate="no" class="notranslate">Stringable</code> and
<code translate="no" class="notranslate">Traversable</code>: the container's own <code translate="no" class="notranslate">__toString()</code> is still
policy-checked, and the yielded elements are additionally checked. The
materialisation is guarded by <code translate="no" class="notranslate">isSandboxed($source)</code> so that
non-sandboxed code paths do not pay the cost or change generator-exhaustion
semantics.</p>
<p><code translate="no" class="notranslate">InBinary</code> and <code translate="no" class="notranslate">NotInBinary</code> now implement
<code translate="no" class="notranslate">Twig\Node\CoercesChildrenToStringInterface</code> and declare both operands as
string-coerced, so <code translate="no" class="notranslate">SandboxNodeVisitor</code> wraps each operand in
<code translate="no" class="notranslate">CheckToStringNode</code>. The policy is consulted before
<code translate="no" class="notranslate">CoreExtension::inFilter()</code> reaches PHP's <code translate="no" class="notranslate">&lt;=&gt;</code> operator, matching the
existing protection on the other comparison binaries (<code translate="no" class="notranslate">Equal</code>, <code translate="no" class="notranslate">Less</code>,
<code translate="no" class="notranslate">Greater</code>, <code translate="no" class="notranslate">Spaceship</code>, ...).</p>
</div>
<div class="section">
<h2 id="credits"><a class="headerlink" href="#credits" title="Permalink to this headline">Credits</a></h2>
<p>We would like to thank Vincent55 Yang and Fabien Potencier for reporting
the issues and Fabien Potencier for providing the fix.</p>
</div>
                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/cve-2026-48807-sandbox-tostring-policy-bypass-via-traversable-in-join-replace-and-in-not-in-operators?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Fabien Potencier ]]></dc:creator>
            <pubDate>Wed, 27 May 2026 15:21:00 +0200</pubDate>
            <comments>https://symfony.com/blog/cve-2026-48807-sandbox-tostring-policy-bypass-via-traversable-in-join-replace-and-in-not-in-operators?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[CVE-2026-46636: Sandbox filter, tag and function allow-list bypass when sandbox state changes between renders]]></title>
            <link>https://symfony.com/blog/cve-2026-46636-sandbox-filter-tag-and-function-allow-list-bypass-when-sandbox-state-changes-between-renders?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>
Affected versions
Twig versions &amp;lt;=3.26.0 are affected by this security issue.
The issue has been fixed in Twig 3.27.0.


Description
The per-template filter, tag and function allow-list check is compiled into
the checkSecurity() method of each Template…</description>
            <content:encoded><![CDATA[
                                <div class="section">
<h2 id="affected-versions"><a class="headerlink" href="#affected-versions" title="Permalink to this headline">Affected versions</a></h2>
<p>Twig versions &lt;=3.26.0 are affected by this security issue.</p>
<p>The issue has been fixed in Twig 3.27.0.</p>
</div>
<div class="section">
<h2 id="description"><a class="headerlink" href="#description" title="Permalink to this headline">Description</a></h2>
<p>The per-template filter, tag and function allow-list check is compiled into
the <code translate="no" class="notranslate">checkSecurity()</code> method of each <code translate="no" class="notranslate">Template</code> subclass and was invoked
once from the constructor, gated by <code translate="no" class="notranslate">SandboxExtension::isSandboxed($source)</code>.
<code translate="no" class="notranslate">Template</code> instances are then cached on the <code translate="no" class="notranslate">Environment</code> in
<code translate="no" class="notranslate">$loadedTemplates</code>, so the verdict computed at construction time was sticky
for the rest of the process.</p>
<p>Any later change of sandbox state on the same <code translate="no" class="notranslate">Environment</code> left that
cached verdict in place: toggling <code translate="no" class="notranslate">SandboxExtension::enableSandbox()</code> /
<code translate="no" class="notranslate">disableSandbox()</code>, swapping the policy via <code translate="no" class="notranslate">setSecurityPolicy()</code>, a
<code translate="no" class="notranslate">SourcePolicyInterface</code> 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 <code translate="no" class="notranslate">SecurityPolicy</code> allow-list.</p>
<p>Method, property and <code translate="no" class="notranslate">__toString</code> allow-lists are not affected: they are
enforced at every call site at runtime through
<code translate="no" class="notranslate">SandboxExtension::checkMethodAllowed()</code>, <code translate="no" class="notranslate">checkPropertyAllowed()</code> and
<code translate="no" class="notranslate">ensureToStringAllowed()</code>, which re-read the current state on every call.</p>
<p>Long-lived workers (FrankenPHP, RoadRunner, Symfony Messenger consumers,
FPM with hot autoloading) that share a single <code translate="no" class="notranslate">Environment</code> between
sandboxed and non-sandboxed renders are the most exposed: a single
non-sandboxed render of a shared layout pre-warms its <code translate="no" class="notranslate">Template</code> 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.</p>
</div>
<div class="section">
<h2 id="resolution"><a class="headerlink" href="#resolution" title="Permalink to this headline">Resolution</a></h2>
<p>The allow-list check is no longer run from the constructor. <code translate="no" class="notranslate">Template</code>
gains a public <code translate="no" class="notranslate">ensureSecurityChecked()</code> method that calls the compiled
<code translate="no" class="notranslate">checkSecurity()</code> only when <code translate="no" class="notranslate">SandboxExtension::isSandboxed($source)</code>
returns true for the current source, and it is invoked at every entry point
that can reach a <code translate="no" class="notranslate">Template</code> instance whose security has not yet been
verified against the current state: <code translate="no" class="notranslate">Template::yield()</code>,
<code translate="no" class="notranslate">Template::yieldBlock()</code> (on the resolved block template, which covers
<code translate="no" class="notranslate">extends</code>, <code translate="no" class="notranslate">use</code>, traits and parent blocks), <code translate="no" class="notranslate">Template::getParent()</code>
(which evaluates user code when the parent name is dynamic) and
<code translate="no" class="notranslate">Template::getTemplateForMacro()</code> (on the resolved macro template).</p>
<p>The explicit <code translate="no" class="notranslate">checkSecurity()</code> calls previously emitted by <code translate="no" class="notranslate">IncludeNode</code>
and <code translate="no" class="notranslate">CoreExtension::include()</code> are removed: the included template's own
<code translate="no" class="notranslate">yield()</code> now re-runs the check against the current sandbox state. The
compiled <code translate="no" class="notranslate">checkSecurity()</code> 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.</p>
</div>
<div class="section">
<h2 id="credits"><a class="headerlink" href="#credits" title="Permalink to this headline">Credits</a></h2>
<p>We would like to thank Fabien Potencier for reporting and fixing the issue.</p>
</div>
                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/cve-2026-46636-sandbox-filter-tag-and-function-allow-list-bypass-when-sandbox-state-changes-between-renders?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Fabien Potencier ]]></dc:creator>
            <pubDate>Wed, 27 May 2026 15:21:00 +0200</pubDate>
            <comments>https://symfony.com/blog/cve-2026-46636-sandbox-filter-tag-and-function-allow-list-bypass-when-sandbox-state-changes-between-renders?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[CVE-2026-48806: Sandbox `__toString()` policy bypass via dynamic mapping keys]]></title>
            <link>https://symfony.com/blog/cve-2026-48806-sandbox-tostring-policy-bypass-via-dynamic-mapping-keys?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>
Affected versions
Twig versions &amp;lt;=3.26.0 are affected by this security issue.
The issue has been fixed in Twig 3.27.0.


Description
This is a residual bypass of CVE-2026-47732 / GHSA-pr2w-4gpj-cpq4 left
after the initial fix for unguarded __toString()…</description>
            <content:encoded><![CDATA[
                                <div class="section">
<h2 id="affected-versions"><a class="headerlink" href="#affected-versions" title="Permalink to this headline">Affected versions</a></h2>
<p>Twig versions &lt;=3.26.0 are affected by this security issue.</p>
<p>The issue has been fixed in Twig 3.27.0.</p>
</div>
<div class="section">
<h2 id="description"><a class="headerlink" href="#description" title="Permalink to this headline">Description</a></h2>
<p>This is a residual bypass of CVE-2026-47732 / GHSA-pr2w-4gpj-cpq4 left
after the initial fix for unguarded <code translate="no" class="notranslate">__toString()</code> calls.</p>
<p>In 3.26.0 the sandbox visitor was extended to wrap every child node that
its parent will string-coerce at runtime with <code translate="no" class="notranslate">CheckToStringNode</code>, gated
by the new <code translate="no" class="notranslate">CoercesChildrenToStringInterface</code>. <code translate="no" class="notranslate">ArrayExpression</code> did
not implement the interface for its mapping keys: when a dynamic key
expression resolves to a <code translate="no" class="notranslate">Stringable</code> object, <code translate="no" class="notranslate">ArrayExpression::compile()</code>
emits a raw <code translate="no" class="notranslate">(string)</code> cast (via <code translate="no" class="notranslate">StringCastUnary</code> for
<code translate="no" class="notranslate">ContextVariable</code> keys, and no cast at all for richer key expressions).
PHP then invokes <code translate="no" class="notranslate">__toString()</code> directly, without ever calling
<code translate="no" class="notranslate">SandboxExtension::ensureToStringAllowed()</code>.</p>
<p>A sandboxed template author can therefore trigger <code translate="no" class="notranslate">__toString()</code> on any
object reachable in the render context by using it as a dynamic mapping
key, for example:</p>
<div translate="no" data-loc="1" class="notranslate codeblock codeblock-length-sm codeblock-twig">
        <div class="codeblock-scroll">
        
        <pre class="codeblock-code"><code><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> arr = {(obj): <span class="hljs-string">"value"</span>} %}</span></code></pre>
    </div>
</div>
<p>Direct output of the same object is correctly blocked, which makes this a
clear policy enforcement gap. The reliable demonstrated impact is
unauthorised disclosure of data returned by <code translate="no" class="notranslate">__toString()</code>.</p>
</div>
<div class="section">
<h2 id="resolution"><a class="headerlink" href="#resolution" title="Permalink to this headline">Resolution</a></h2>
<p><code translate="no" class="notranslate">ArrayExpression</code> now declares its dynamic mapping keys as
string-coercion sites through <code translate="no" class="notranslate">CoercesChildrenToStringInterface</code>, so the
sandbox visitor wraps them with <code translate="no" class="notranslate">CheckToStringNode</code> and the policy is
consulted before PHP coerces the key to a string. The compiler also keeps
an explicit <code translate="no" class="notranslate">(string)</code> cast around the wrapped expression so PHP type
errors on non-string keys are preserved.</p>
<p>As a side effect, any expression is now accepted as a dynamic mapping key
(not only context variables); this is documented as a new feature on the
3.x branch.</p>
</div>
<div class="section">
<h2 id="credits"><a class="headerlink" href="#credits" title="Permalink to this headline">Credits</a></h2>
<p>We would like to thank El Kharoubi Iosif for reporting the issue and Fabien
Potencier for providing the fix.</p>
</div>
                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/cve-2026-48806-sandbox-tostring-policy-bypass-via-dynamic-mapping-keys?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Fabien Potencier ]]></dc:creator>
            <pubDate>Wed, 27 May 2026 15:21:00 +0200</pubDate>
            <comments>https://symfony.com/blog/cve-2026-48806-sandbox-tostring-policy-bypass-via-dynamic-mapping-keys?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[CVE-2026-48805: Sandbox state regression in deprecated internal wrappers in `src/Resources/core.php`]]></title>
            <link>https://symfony.com/blog/cve-2026-48805-sandbox-state-regression-in-deprecated-internal-wrappers-in-src-resources-core-php?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>
Affected versions
Twig versions &amp;lt;=3.26.0 are affected by this security issue.
The issue has been fixed in Twig 3.27.0.


Description
The 3.26.0 source-policy hardening changed the signature of
CoreExtension::checkArrow() to take a boolean $isSandboxed…</description>
            <content:encoded><![CDATA[
                                <div class="section">
<h2 id="affected-versions"><a class="headerlink" href="#affected-versions" title="Permalink to this headline">Affected versions</a></h2>
<p>Twig versions &lt;=3.26.0 are affected by this security issue.</p>
<p>The issue has been fixed in Twig 3.27.0.</p>
</div>
<div class="section">
<h2 id="description"><a class="headerlink" href="#description" title="Permalink to this headline">Description</a></h2>
<p>The 3.26.0 source-policy hardening changed the signature of
<code translate="no" class="notranslate">CoreExtension::checkArrow()</code> to take a boolean <code translate="no" class="notranslate">$isSandboxed</code> instead
of an <code translate="no" class="notranslate">Environment</code>, and added the same <code translate="no" class="notranslate">$isSandboxed</code> argument to
<code translate="no" class="notranslate">CoreExtension::arraySome()</code> and <code translate="no" class="notranslate">CoreExtension::arrayEvery()</code>. Compiled
templates were updated to pass the per-source sandbox state computed at the
call site.</p>
<p>The deprecated internal wrappers exposed in <code translate="no" class="notranslate">src/Resources/core.php</code> for
legacy third-party code (<code translate="no" class="notranslate">twig_check_arrow_in_sandbox()</code>,
<code translate="no" class="notranslate">twig_array_some()</code>, <code translate="no" class="notranslate">twig_array_every()</code>) were not updated:</p>
<ul>
    <li><code translate="no" class="notranslate">twig_array_some()</code> and <code translate="no" class="notranslate">twig_array_every()</code> call
<code translate="no" class="notranslate">CoreExtension::arraySome()</code> / <code translate="no" class="notranslate">arrayEvery()</code> without forwarding the
sandbox state. The underlying methods default <code translate="no" class="notranslate">$isSandboxed</code> to
<code translate="no" class="notranslate">false</code>, so the callable-must-be-a-<code translate="no" class="notranslate">Closure</code> restriction is silently
bypassed in sandbox mode and a string callable such as <code translate="no" class="notranslate">'strcmp'</code> is
accepted.</li>
<li><code translate="no" class="notranslate">twig_check_arrow_in_sandbox()</code> passes the <code translate="no" class="notranslate">Environment</code> object where
<code translate="no" class="notranslate">CoreExtension::checkArrow()</code> now expects a <code translate="no" class="notranslate">bool</code>, which throws a
<code translate="no" class="notranslate">TypeError</code> on PHP 8+.</li>
</ul>
<p>Compiled Twig templates are not affected: they call <code translate="no" class="notranslate">CoreExtension::*</code>
directly with the correct arguments. Applications are only impacted if they
still call the deprecated <code translate="no" class="notranslate">twig_*</code> helpers on top of a sandboxed
<code translate="no" class="notranslate">Environment</code>.</p>
</div>
<div class="section">
<h2 id="resolution"><a class="headerlink" href="#resolution" title="Permalink to this headline">Resolution</a></h2>
<p>The three wrappers now resolve the current sandbox state via
<code translate="no" class="notranslate">twig_resolve_is_sandboxed()</code> (the same helper compiled templates use),
and forward it to the corresponding <code translate="no" class="notranslate">CoreExtension::*</code> method.
<code translate="no" class="notranslate">twig_check_arrow_in_sandbox()</code> no longer triggers a <code translate="no" class="notranslate">TypeError</code>, and
<code translate="no" class="notranslate">twig_array_some()</code> / <code translate="no" class="notranslate">twig_array_every()</code> now enforce the same sandbox
restriction as compiled templates.</p>
</div>
<div class="section">
<h2 id="credits"><a class="headerlink" href="#credits" title="Permalink to this headline">Credits</a></h2>
<p>We would like to thank El Kharoubi Iosif for reporting the issue and Fabien
Potencier for providing the fix.</p>
</div>
                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/cve-2026-48805-sandbox-state-regression-in-deprecated-internal-wrappers-in-src-resources-core-php?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Fabien Potencier ]]></dc:creator>
            <pubDate>Wed, 27 May 2026 15:21:00 +0200</pubDate>
            <comments>https://symfony.com/blog/cve-2026-48805-sandbox-state-regression-in-deprecated-internal-wrappers-in-src-resources-core-php?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
                        <item>
            <title><![CDATA[CVE-2026-48808: Sandbox property allowlist bypass via the `column` filter under `SourcePolicyInterface`]]></title>
            <link>https://symfony.com/blog/cve-2026-48808-sandbox-property-allowlist-bypass-via-the-column-filter-under-sourcepolicyinterface?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</link>
            <description>
Affected versions
Twig versions &amp;lt;=3.26.0 are affected by this security issue.
The issue has been fixed in Twig 3.27.0.


Description
This is a residual bypass of CVE-2026-46635 / GHSA-vcc8-phrv-43wj that
only affects sandboxing enabled through SourcePolicyInterface…</description>
            <content:encoded><![CDATA[
                                <div class="section">
<h2 id="affected-versions"><a class="headerlink" href="#affected-versions" title="Permalink to this headline">Affected versions</a></h2>
<p>Twig versions &lt;=3.26.0 are affected by this security issue.</p>
<p>The issue has been fixed in Twig 3.27.0.</p>
</div>
<div class="section">
<h2 id="description"><a class="headerlink" href="#description" title="Permalink to this headline">Description</a></h2>
<p>This is a residual bypass of CVE-2026-46635 / GHSA-vcc8-phrv-43wj that
only affects sandboxing enabled through <code translate="no" class="notranslate">SourcePolicyInterface</code> (and not
the regular global sandbox mode).</p>
<p><code translate="no" class="notranslate">CoreExtension::column()</code> receives the active sandbox state via the
<code translate="no" class="notranslate">needs_is_sandboxed</code> channel as a boolean <code translate="no" class="notranslate">$isSandboxed</code>, but then
routes the per-element property reads through
<code translate="no" class="notranslate">SandboxExtension::checkPropertyAllowed()</code> without forwarding the current
<code translate="no" class="notranslate">Source</code>. <code translate="no" class="notranslate">SandboxExtension::checkPropertyAllowed()</code> re-evaluates
<code translate="no" class="notranslate">isSandboxed($source)</code> internally; with <code translate="no" class="notranslate">$source = null</code> the
<code translate="no" class="notranslate">SourcePolicyInterface</code>-driven decision is lost, the method
short-circuits to "not sandboxed", and the property allowlist is never
consulted.</p>
<p>A template author whose sandbox is gated by a <code translate="no" class="notranslate">SourcePolicyInterface</code>
and who has <code translate="no" class="notranslate">column</code> on their <code translate="no" class="notranslate">allowedFilters</code> list can therefore read
any public or magic property of any object reachable in the render
context, regardless of <code translate="no" class="notranslate">SecurityPolicy::$allowedProperties</code>. Direct
attribute access to the same property is blocked, and the same payload is
also blocked under global sandbox mode, which makes this a clear policy
enforcement gap rather than a configuration issue.</p>
</div>
<div class="section">
<h2 id="resolution"><a class="headerlink" href="#resolution" title="Permalink to this headline">Resolution</a></h2>
<p><code translate="no" class="notranslate">CoreExtension::column()</code> no longer goes through the <code translate="no" class="notranslate">SandboxExtension</code>
wrapper for the property check. It calls the security policy directly: the
per-source decision is already captured by the <code translate="no" class="notranslate">$isSandboxed</code> boolean
computed at the call site, so the property allowlist is enforced
consistently for both global and source-policy sandboxing.</p>
</div>
<div class="section">
<h2 id="credits"><a class="headerlink" href="#credits" title="Permalink to this headline">Credits</a></h2>
<p>We would like to thank Vincent55 Yang for reporting the issue and Fabien
Potencier for providing the fix.</p>
</div>
                <hr style="margin-bottom: 5px" />
                <div style="font-size: 90%">
                    <a href="https://symfony.com/sponsor">Sponsor</a> the Symfony project.
                </div>
            ]]></content:encoded>
            <guid isPermaLink="false">https://symfony.com/blog/cve-2026-48808-sandbox-property-allowlist-bypass-via-the-column-filter-under-sourcepolicyinterface?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed</guid>
            <dc:creator><![CDATA[ Fabien Potencier ]]></dc:creator>
            <pubDate>Wed, 27 May 2026 15:21:00 +0200</pubDate>
            <comments>https://symfony.com/blog/cve-2026-48808-sandbox-property-allowlist-bypass-via-the-column-filter-under-sourcepolicyinterface?utm_source=Symfony%20Blog%20Feed&amp;utm_medium=feed#comments-list</comments>
        </item>
            </channel>
</rss>
