The name passed to MacroReferenceExpression is emitted as raw PHP in
compile() via "->{$name}(...)". Callers were expected to validate
the name, but a missing check led to CVE-2026-XXXXX (PHP code injection
via _self / import macro reference): defense-in-depth, validate the
name in the constructor so the class is safe by construction.
This is a defense-in-depth measure: callers must always concatenate the
result into a double-quoted PHP context, but if one ever (mistakenly)
embeds it inside a single-quoted PHP literal, an attacker-controlled
single quote in the source value could break out of that context. The
previous commit fixed exactly such a bug in ModuleNode for the {% use %}
template name.
Encoding ' as the hex escape \x27 guarantees that the emitted PHP source
never contains a literal single quote derived from user input, while the
decoded runtime value is unchanged. \' is not used because it is not a
recognized escape sequence in PHP double-quoted strings (the backslash
would be kept literally).
The HtmlDumper output is intended to be rendered in a browser, and the
template and macro/block names it interpolates are loader-controlled
(e.g. the key for ArrayLoader or a database row id), so they can carry
arbitrary HTML when an application stores templates under user-supplied
identifiers.
This PR was merged into the 3.x branch.
Discussion
----------
Lazy load `EscaperRuntime` in `EscaperExtension`
This allows overriding `EscaperRuntime` via a custom runtime loader.
Previously, `EscaperExtension::setEnvironment()` was calling `$environment->getRuntime(EscaperRuntime::class)` eagerly. Since this method is called from `Environment::__construct()`, the runtime was resolved before any custom runtime loader could be injected, making it impossible to override `EscaperRuntime`.
- Required by https://github.com/symfony/symfony/pull/63929
Commits
-------
b73ab8cfd9 Lazy load EscaperRuntime in EscaperExtension
This PR was merged into the 3.x branch.
Discussion
----------
Add a `needs_is_sandboxed` option for filters, functions, and tests
This PR introduces a new `needs_is_sandboxed` option for Twig callables.
When set to `true`, Twig passes the current sandbox state (enabled/disabled) as a boolean to the callable. This lets filters, functions, and tests adapt their behavior depending on whether the sandbox is enabled or not.
Commits
-------
5462817da0 Add a `needs_is_sandboxed` option for filters, functions, and tests
Both filters consume HTML on the input side. Adding `pre_escape => 'html'`
makes the autoescaper escape attacker-controlled inputs before the filters
process them, so they are no longer reachable via `{{ user_input|inline_css }}`
or `{{ user_input|inky_to_html }}` without an explicit `|raw`.
The `html_to_markdown` filter emits plain Markdown text, so the
`is_safe` annotation is dropped entirely and autoescape now handles
its output according to the surrounding context.
The `markdown_to_html` and `inline_css` filters emit HTML, not text
safe in every escaping context, so `is_safe => ['all']` produced
unescaped HTML when their output was interpolated into a JS, CSS or
URL context. The annotation is now `is_safe => ['html']`.
This PR was merged into the 3.x branch.
Discussion
----------
[Doc] Document loose comparison in the `in` operator
Clarifies the comparison semantics of the `in` operator in the *Containment Operators* section of `doc/templates.rst`:
- `in` uses a loose comparison (similar to `==`) for sequences, mappings, and `Traversable` objects, like PHP's `in_array()`.
- Pointer to `same as` for strict comparisons.
- Warning about boolean left operands (e.g. `true in ['foo', 'bar']` returns `true`), which surprises users.
- Note that string containment only accepts `string`, `int`, `float` on the left.
Closes#4650 (or at least the documentation half of it — the `Investigate before 4.x` label suggests the maintainers may want to revisit the actual semantics in 4.x; this PR addresses `@gorenstein`'s explicit request to "at least improve the documentation").
Commits
-------
8af8e93970 [Doc] Document loose comparison in the `in` operator
This PR was merged into the 3.x branch.
Discussion
----------
[Doc] Reword whitespace control note about first-newline removal
Fixes#4720.
The wording on line 1224 of `doc/templates.rst` ("removal of the first newline **inherited from PHP**") was misleading — Twig does not inherit anything from PHP, it deliberately implements the same behavior (just like Jinja does, which uses *"like in PHP"* in its own documentation).
The note already exists a few lines above (line 1210: *"The first newline after a template tag is removed automatically (like in PHP)."*), so the second mention can just point back to it instead of duplicating the (incorrect) attribution.
Commits
-------
6904b23fb6 [Doc] Reword whitespace control note about first-newline removal
Clarifies that `in` performs a loose comparison on sequences, mappings,
and `Traversable` objects (similar to `==`), points to `same as` for
strict comparisons, and warns about boolean left operands.
Refs #4650
The previous wording ("inherited from PHP") was misleading: Twig does
not inherit anything from PHP, it deliberately implements the same
behavior, like Jinja does. The behavior was already explained earlier
in the same section, so just point back to it instead.
This PR was merged into the 3.x branch.
Discussion
----------
Stop publishing extra package minor versions with no changes
Commits
-------
48733e0f09 Stop publishing extra package minor versions with no changes
Previously, setEnvironment() called getRuntime(EscaperRuntime::class) eagerly,
which prevented overriding EscaperRuntime via a custom runtime loader since
Environment::__construct() calls setEnvironment() before any loader can be injected.