[RFC Idea] Short echo tag with automatic HTML escaping (<?~ ... ?>)

php.internals

Sergei Issaev

252 days ago
Hi internals, I’d like to propose a small but security-relevant addition to PHP’s templating syntax: a new short echo tag that automatically applies secure HTML escaping. **The problem** Despite PHP being a perfectly capable templating language on its own, developers often reach for external templating engines (like Twig or Blade) primarily to get automatic HTML escaping and avoid XSS. This adds dependencies, complexity, and performance overhead — even when 95% of the project would be simpler with native PHP templates. At the same time, manual escaping is error-prone: ``` <?= $user_input ?>          // ❌ dangerous <?= htmlspecialchars($user_input, ENT_QUOTES | ENT_HTML5, 'UTF-8') ?>  // ✅ but verbose ``` Many security vulnerabilities stem from the sheer verbosity of the safe version. **The proposal** Introduce a new short echo tag: ``` <?~ $expr ?> ``` which compiles to: ``` <?php echo htmlspecialchars($expr, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8'); ?> ``` Key points: - Uses htmlspecialchars() (not htmlentities()) — sufficient and standard for XSS prevention. - Hardcodes secure flags and UTF-8 encoding (aligned with default_charset). - Syntax is currently a parse error → no BC break. - The <?~ tag is available whenever <?= is available. This gives native PHP templates a secure-by-default output mechanism without requiring a full templating engine. **Why not just use Twig/Blade?** There’s nothing wrong with those engines — **but PHP itself is already a great template language**. Requiring a separate dependency just for auto-escaping feels like overkill for many projects (e.g., small apps, WordPress plugins, legacy modernisation). **About implementation** I’m not a C developer and don’t have experience with the Zend Engine internals. However, I’m fully committed to: - Writing and maintaining a complete RFC document - Participating actively in design discussions (syntax, flags, security model) - Testing any implementation or prototype - Collaborating with anyone willing to help with the parser/lexer patch If the idea gains support, I hope a volunteer from the internals team might be interested in guiding or contributing the low-level implementation. **Next steps** I’d appreciate your thoughts — especially on: - Whether this functionality belongs in core - Security assumptions (e.g., hardcoded UTF-8, flag choices) - Syntax alternatives (<?~ vs <?h= vs others) Thank you for your time and consideration. Best regards, Sergei Issaev

Andrey Andreev

252 days ago
Hi Sergei, XSS escaping is unfortunately not as simple as that. Templating engines are context-aware and can know whether to apply escaping for free-form text or an attribute (which can often also be validated by type), specific tag behaviors, and even whether the output is to be executed as HTML, XML, CSS, JS, etc. One-size-fits-all escaping that doesn't take such context into account is not effective and even makes things worse by giving developers a false sense of security. Cheers, Andrey.

Sergei Issaev

252 days ago
Hi Andrey, Thank you for the thoughtful reply — I completely agree that full context-aware escaping (like in Twig or Blade) is essential for complex or mixed-output scenarios. My intention isn’t to replace that kind of intelligence, but rather to offer a simple, safe default for the most common case: outputting plain user-supplied text inside HTML text content or double-quoted attributes, e.g.: ``` <p><?~ $comment ?></p> <input value="<?~ $name ?>"> ``` In these cases, htmlspecialchars(..., ENT_QUOTES | ENT_HTML5, 'UTF-8') is sufficient and widely recommended. The goal isn’t to solve all XSS vectors, but to eliminate the most frequent footgun: forgetting to escape at all. Developers would still be responsible for using proper context-specific escaping (or a full templating engine) when interpolating into <script>, styles, URLs, or unquoted attributes — but for the 80% case of rendering form values or content in standard HTML, <?~ would provide a concise, secure-by-default shortcut. Think of it as similar to how <?= ... ?> made output easier than <?php echo ... ?> — not a security feature per se, but a nudge toward safer habits in everyday templating. Would that narrower scope make the proposal more reasonable for core? Best regards, Sergei On 12/23/25 14:55, Andrey Andreev wrote:

Rob Landers

252 days ago
On Tue, Dec 23, 2025, at 10:55, Andrey Andreev wrote:
> Hi Sergei, > > XSS escaping is unfortunately not as simple as that. Templating engines are context-aware and can know whether to apply escaping for free-form text or an attribute (which can often also be validated by type), specific tag behaviors, and even whether the output is to be executed as HTML, XML, CSS, JS, etc. > > One-size-fits-all escaping that doesn't take such context into account is not effective and even makes things worse by giving developers a false sense of security. > > Cheers, > Andrey.
Hi Andrey, Which template engines are context aware? The only ones I'm aware of is my own and Latte (which take a similar approach but is quite architecturally different). — Rob

Andrey Andreev

252 days ago
Hi Rob, On Tue, Dec 23, 2025 at 12:14 PM Rob Landers <rob@bottled.codes> wrote:
> Which template engines are context aware? The only ones I'm aware of is my > own and Latte (which take a similar approach but is quite architecturally > different). >
Sorry, I don't have a direct answer to your question. I haven't written front-end code for a loong time and haven't needed such tools recently enough to know. I just meant to say that only a templating engine (or something that would parse the full output) can be context-aware. Although in many cases, where a template engine provides helpers such as those that generate lists or forms, those helpers still have sufficient context. Cheers, Andrey.

Anton Smirnov

252 days ago
Hi! On 23/12/2025 11:06, Sergei Issaev wrote:
> which compiles to: > > ``` > <?php echo htmlspecialchars($expr, ENT_QUOTES | ENT_SUBSTITUTE | > ENT_HTML5, 'UTF-8'); ?> > ```
htmlspecialchars is configurable for a reason and your flags are arbitrarily different from the function default
> - Syntax is currently a parse error → no BC break.
It's not, you forgot about short_open_tag=1, in which case it's interpreted as <? (~$expr) ?> I basically see no value over // included earlier function h($s) { return htmlspecialchars($s, ENT_QUOTES | ENT_HTML5, 'UTF-8'); } // template <?= h($user_input) ?> It's short, it does what you want, it's available today
-- Anton

Anton Smirnov

252 days ago
On 23/12/2025 12:07, Anton Smirnov wrote:
>> - Syntax is currently a parse error → no BC break. > > It's not, you forgot about short_open_tag=1, in which case it's > interpreted as <? (~$expr) ?>
Slight self-correction: with short_open_tag=0 it isn't a syntax error either, it just echoes it to the output. This makes me think that introducing a new short tag would be virtually impossible to do without a major release.
-- Anton

Sergei Issaev

252 days ago
Hi, Thank you for the feedback — you’re absolutely right that htmlspecialchars() is configurable for good reasons, and that a userland helper like h() already provides a concise escape hatch today. However, I’d like to gently push back on two points: 1. <?~ vs <? (~$expr) under short_open_tag=On You're correct that with short_open_tag=1, <?~ $x ?> would currently be parsed as <? (~$x) ?>. But in practice: - short_open_tag has been disabled by default since PHP 5.4 (2012). - Most modern frameworks and coding standards explicitly discourage its use. - The <?= short echo tag is always available regardless of short_open_tag, precisely because it’s treated specially. If the RFC were to propose <?~ as a new short echo variant (like <?=), it would follow the same rule: always enabled, independent of short_open_tag. That would eliminate the ambiguity you mentioned. 2. Why not just use h()? Yes, h() works — and many projects already define it. But that’s exactly the problem: everyone reinvents it, often with slightly different flags, encoding assumptions, or error handling. This leads to: - Inconsistent escaping across projects or even within the same codebase. - Junior developers skipping escaping because “it’s not built in”. - Security relying on project-specific conventions rather than language-level defaults. By providing a standard, secure-by-default output tag in core, PHP would: - Reduce boilerplate. - Encourage safer habits out of the box. - Give small projects (e.g., WordPress plugins, scripts, internal tools) a zero-dependency way to escape safely — without requiring them to define or remember h(). Think of it like <?= ... ?>: it didn’t add new capability, but it made the common case easier and more consistent. <?~ ... ?> aims to do the same for secure output. That said, I hear your concern about hardcoded flags. If the community prefers, the escaping behavior could even respect default_charset and a new html_output_flags ini setting — though I’d argue opinionated security defaults are better here. Thanks again for the critique — it’s helping sharpen the idea. Best regards, Sergei On 12/23/25 15:07, Anton Smirnov wrote:

Andrey Andreev

252 days ago
On Tue, Dec 23, 2025 at 12:30 PM Sergei Issaev <sergei_vi@mail.ru> wrote:
> > Yes, h() works — and many projects already define it. But that’s exactly > the problem: everyone reinvents it, often with slightly different flags, > encoding assumptions, or error handling. This leads to: > - Inconsistent escaping across projects or even within the same codebase. > - Junior developers skipping escaping because “it’s not built in”. > - Security relying on project-specific conventions rather than > language-level defaults. > > By providing a standard, secure-by-default output tag in core, PHP would: > - Reduce boilerplate. > - Encourage safer habits out of the box. > - Give small projects (e.g., WordPress plugins, scripts, internal tools) > a zero-dependency way to escape safely — without requiring them to > define or remember h().
I can relate to these pain points, but I also think your conclusions are a bit of wishful thinking. Junior developers would still not use it, because they'd need to know it first - just like h(). Even seasoned developers would avoid it because many will deem the syntax to be ugly, and the solution imperfect. And security gets a band-aid instead of proper protection, which is more dangerous than you'd think - I've seen it time and time again, some feature offers limited security benefits, people only care to remember that "it's secure", use it for everything without critical thought, and you end up with masses of developers believing that they write secure code when it's not even close to that. Then PHP has to take the blame for providing insecure XSS escaping. Cheers, Andrey.

Sergei Issaev

252 days ago
On 12/23/25 16:12, Andrey Andreev wrote:
> On Tue, Dec 23, 2025 at 12:30 PM Sergei Issaev <sergei_vi@mail.ru> wrote: > > > Yes, h() works — and many projects already define it. But that’s > exactly > the problem: everyone reinvents it, often with slightly different > flags, > encoding assumptions, or error handling. This leads to: > - Inconsistent escaping across projects or even within the same > codebase. > - Junior developers skipping escaping because “it’s not built in”. > - Security relying on project-specific conventions rather than > language-level defaults. > > By providing a standard, secure-by-default output tag in core, PHP > would: > - Reduce boilerplate. > - Encourage safer habits out of the box. > - Give small projects (e.g., WordPress plugins, scripts, internal > tools) > a zero-dependency way to escape safely — without requiring them to > define or remember h(). > > > I can relate to these pain points, but I also think your conclusions > are a bit of wishful thinking. > > Junior developers would still not use it, because they'd need to know > it first - just like h(). Even seasoned developers would avoid it > because many will deem the syntax to be ugly, and the solution > imperfect. And security gets a band-aid instead of proper protection, > which is more dangerous than you'd think - I've seen it time and time > again, some feature offers limited security benefits, people only care > to remember that "it's secure", use it for everything without critical > thought, and you end up with masses of developers believing that they > write secure code when it's not even close to that. Then PHP has to > take the blame for providing insecure XSS escaping.
You are correct that the <?~ ?> syntax alone cannot provide comprehensive XSS protection, and it is important not to create a false sense of security. I will remove direct mentions of XSS from the description to avoid misleading anyone. My main goal is not to replace contextual escaping, but to offer a standard, recognizable syntax for a frequent operation — outputting HTML-escaped strings. Yes, developers currently use htmlspecialchars(), wrappers like <?=h(...)?>, etc. But it is precisely the lack of a unified, "native" method that creates inconsistency and inconvenience. Language evolution often follows the path of simplifying routine operations: array() → [], cumbersome isset() checks → the concise ??. Similarly, <?~ ... ?> could become the same standard and expected way for safe output as <?= is for direct output. This will make the default code for embedding dynamic content into HTML structure not only faster to write but also instantly readable for any developer. That is precisely why I am proposing this feature — not as a security panacea, but as a step toward making it as quick and habitual to write structurally correct HTML code as it is to write unsafe code. Best regards, Sergei Issaev

Anton Smirnov

252 days ago
On 23/12/2025 12:26, Sergei Issaev wrote:
> However, I’d like to gently push back on two points: > 1. <?~ vs <? (~$expr) under short_open_tag=On > > You're correct that with short_open_tag=1, <?~ $x ?> would currently be > parsed as <? (~$x) ?>. But in practice: > - short_open_tag has been disabled by default since PHP 5.4 (2012).
But there is no plan to remove them, see https://wiki.php.net/rfc/deprecate_php_short_tags_v2 https://wiki.php.net/rfc/counterargument/deprecate_php_short_tags
> - Most modern frameworks and coding standards explicitly discourage its > use.
Since they are still a valid code, so PHP and various coding standard tools must parse them correctly.
> - The <?= short echo tag is always available regardless of > short_open_tag, precisely because it’s treated specially. > > If the RFC were to propose <?~ as a new short echo variant (like <?=), > it would follow the same rule: always enabled, independent of > short_open_tag. That would eliminate the ambiguity you mentioned.
No it doesn't. The grammar should stay consistent for every possible ini state. I have a feeling that the tokenizer won't even compile with this syntax. (not sure)
> 2. Why not just use h()? > > Yes, h() works — and many projects already define it. But that’s exactly > the problem: everyone reinvents it, often with slightly different flags, > encoding assumptions, or error handling. This leads to: > - Inconsistent escaping across projects or even within the same codebase. > - Junior developers skipping escaping because “it’s not built in”. > - Security relying on project-specific conventions rather than language- > level defaults.
That also leads to a tailored experience suitable for a specific project and context. I also don't see an inconsistent escaping as a huge problem as long as it does its job.
> By providing a standard, secure-by-default output tag in core, PHP would: > - Reduce boilerplate. > - Encourage safer habits out of the box. > - Give small projects (e.g., WordPress plugins, scripts, internal tools) > a zero-dependency way to escape safely — without requiring them to > define or remember h().
Secure today does not mean secure tomorrow. With the existing function you can simply update the default flag set, or users can update their flag sets ahead of PHP. For the syntax approach to be as flexible, it needs to be configurable by some global state (set_escape_flags()? ini config?) which is also a big no-no, that would be magic quotes all over again, just slightly more explicit. Magic quotes btw should be a lesson that you can't make stuff secure by a simple all size fits all solution.
> Think of it like <?= ... ?>: it didn’t add new capability, but it made > the common case easier and more consistent. <?~ ... ?> aims to do the > same for secure output.
<?= exists since forever and "= $expr" is not a valid code. You should at least choose another symbol (like "<?*")
> That said, I hear your concern about hardcoded flags. If the community > prefers, the escaping behavior could even respect default_charset and a > new html_output_flags ini setting — though I’d argue opinionated > security defaults are better here.
See above, also respecting default_charset is a must imho, not everyone uses UTF-8, East Asia specifically. Introducing a core syntax and excluding a huge portion of users is not a good move.
-- Anton

Sergei Issaev

251 days ago
Hi Anton, Thank you for the discussion!
>> - short_open_tag has been disabled by default since PHP 5.4 (2012). > > But there is no plan to remove them, see: > https://wiki.php.net/rfc/deprecate_php_short_tags_v2 > https://wiki.php.net/rfc/counterargument/deprecate_php_short_tags
> <?= exists since forever and "= $expr" is not a valid code. You
should at least choose another symbol (like "<?*"). I agree: since short tags remain valid syntax, the parser and tooling must correctly handle them in all configuration states. Therefore, I propose replacing `<?~ ... ?>` with `<?: ... ?>`.
> See above, also respecting default_charset is a must imho, not
everyone uses UTF-8, East Asia specifically. Introducing a core syntax and excluding a huge portion of users is not a good move. The semantics of `<?: $expr ?>` would be equivalent to: `<?php echo htmlspecialchars($expr, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5); ?>` -- that is, the encoding will be determined automatically via the current default_charset setting, as htmlspecialchars() does by default. Best regards, Sergei Issaev

Rob Landers

251 days ago
On Wed, Dec 24, 2025, at 05:28, Sergei Issaev wrote:
> Hi Anton, > > > See above, also respecting default_charset is a must imho, not > everyone uses UTF-8, East Asia specifically. Introducing a core syntax > and excluding a huge portion of users is not a good move. > > The semantics of `<?: $expr ?>` would be equivalent to: `<?php echo > htmlspecialchars($expr, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5); ?>` -- > that is, the encoding will be determined automatically via the current > default_charset setting, as htmlspecialchars() does by default. > > Best regards, > Sergei Issaev >
I know this is not your intent, but this will give people a false sense of security. Much like Twig and Blade do today. Here's some examples where this breaks, written in the context of an html doc: <script> const name = "<?: $name ?>"; </script> breaks with ";alert(1); // <a href="/search?q=<?: $query ?>"... double encodes window.config = <?: json_encode($config ?> doesn't technically break ... but that would be a fun bug which would encourage devs to just turn off escaping, which defeats the purpose. .... Maybe, you should consider something like this: <?:html: or <?:h: <?:attr: <?:url: <?:js: <?:css: Then you only choose the one for your context: const name = <?:js: $name ?> <a href="/search?q=<?:attr: $query ?>"... window.config=<?:js: $config ?> .size { width=<?:css: $width ?>; } //prevents ;}.size:after { content="<a..." Its a bit more to type, but compared to googling the right way to do these in all the right places and vetting/hoping that it is right... it seems worth it. — Rob

Christian Schneider

251 days ago
Am 24.12.2025 um 09:14 schrieb Rob Landers <rob@bottled.codes>:
> I know this is not your intent, but this will give people a false sense of security. Much like Twig and Blade do today.
I think this is an important point as many on this list will object to such a feature because of this. What I mean by this: The main obstacle is not the specific syntax but the feature in general. I mean this as a heads-up for the original poster to be not too disappointed when (not "if" - in my personal opinion) it gets shot down.
> Here's some examples where this breaks, written in the context of an html doc:
This is just the tip of the iceberg.
> Maybe, you should consider something like this: > > <?:html: or <?:h: > <?:attr: > <?:url: > <?:js: > <?:css:
I'm pretty sure for at least some of them one can find issues with a generic quoting, e.g. character encoding other than UTF-8. Or what schemes are allowed for a URL, e.g. you might want to disallow javascript:. All in all I much prefer this to be part of a framework which knows about edge cases than to be a language feature. Or maybe something like the template strings t"foo" in Python but that's a different approach to Twig-like templates. Regards, - Chris

Paul M. Jones

251 days ago
Hi all,
> On Dec 24, 2025, at 02:14, Rob Landers <rob@bottled.codes> wrote: > > Maybe, you should consider something like this: > > <?:html: or <?:h: > <?:attr: > <?:url: > <?:js: > <?:css: > > Then you only choose the one for your context: > > const name = <?:js: $name ?> > > <a href="/search?q=<?:attr: $query ?>"... > > window.config=<?:js: $config ?> > > .size { width=<?:css: $width ?>; } //prevents ;}.size:after { content="<a..." > > Its a bit more to type, but compared to googling the right way to do these in all the right places and vetting/hoping that it is right... it seems worth it.
For what it's worth, that is much how Qiq works: - {{ ... }} will not echo at all by itself - {{= ... }} will echo raw unescaped output - {{h ... }} will echo escaped for HTML content - {{a ... }} will echo escaped for HTML attributes - {{u ... }} will echo escaped for URLs - {{c ... }} will echo escaped for CSS - {{j ... }} will echo escaped for JavaScript <https://qiqphp.com/3.x/syntax.html> -- pmj

Sergei Issaev

250 days ago
Hi! On 12/24/25 13:14, Rob Landers wrote:
> Maybe, you should consider something like this: > > <?:html: or <?:h: > <?:attr: > <?:url: > <?:js: > <?:css:
That’s an interesting idea! However, it goes beyond the scope of this RFC, which aims only to introduce a concise shorthand for the frequently used <?= htmlspecialchars(...) ?> pattern. A more comprehensive solution with context-specific escaping—for example, for HTML, attributes, URLs, JavaScript, or CSS—would indeed be better implemented at the templating engine level or as a dedicated module. Moreover, as other participants in the discussion rightly pointed out, relying solely on automatic escaping at the rendering layer may create a false sense of security if developers don’t fully consider the output context in which data is used. In fact, such syntax doesn’t inherently improve security—it primarily increases coding convenience. True safety still depends on an explicit and context-aware choice of encoding method. Summary of the proposal: Introduce a new short echo tag: ``` <?: $expr ?> ``` which compiles to: ``` <?php echo htmlspecialchars($expr); ?> ``` The encoding will be determined automatically via the current `default_charset` setting, as `htmlspecialchars()` does by default. The behavior flags can be configured via a new INI directive: `short_echo_specialchars_flags`. By default, it matches the flag values used by `htmlspecialchars()`. Thank you to everyone who contributed to the discussion. Best regards, Sergei Issaev

Sara Golemon

251 days ago
On Tue, Dec 23, 2025 at 4:11 AM Anton Smirnov <sandfox@sandfox.me> wrote:
> On 23/12/2025 11:06, Sergei Issaev wrote: > > <?php echo htmlspecialchars($expr, ENT_QUOTES | ENT_SUBSTITUTE | > > ENT_HTML5, 'UTF-8'); ?> > > htmlspecialchars is configurable for a reason and your flags are > arbitrarily different from the function default >
This is the only necessary argument to reject this feature request out of hand. Hard pass. -Sara