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