1

In my code I'm using the _e() function to echo static text onto the page:

_e( 'Plugin name not found.', 'opn_td' );

Is this safe, or do I need to escape this output?

  • As I understand (from WordPress documentation here and here), escaping needs to occur only if there is a chance the output could be malicious or undesirable. Is that the case here? It seems to me like 'no.' But PHP Code Sniffer (in VSCode) has flagged this as a violation of WordPress coding standards, indicating:

All output should be run through an escaping function (like esc_html_e() or esc_attr_e())...

screenshot here.

  • I agree that 'escape everything' is the safest option, and not difficult to implement--I have no problem doing so in this case. But I'm more interested in learning how much of a security risk this particular line of code actually introduces.

  • I understand the value of esc_html_e() when outputting dynamic content, and will certainly use it in those cases.

Thanks.

8
  • 3
    This text is dynamic. _() and _e() allow the text to be replaced by translations or filters. They need to be escaped, as the code sniffer is telling you. – Jacob Peattie Oct 3 '19 at 12:08
  • @JacobPeattie OK got it. So in light of that, when would _e() ever be used? – cag8f Oct 3 '19 at 12:08
  • 1
  • Either function could be used if you were capturing the text/value and escaping it later (you should escape as late as possible). – Jacob Peattie Oct 3 '19 at 12:10
  • Maybe _e() is for cases in which you actually do NOT want to escape output? For example, maybe you WANT to output specific HTML to a page? – cag8f Oct 3 '19 at 12:11
2

The _e() function displays a translated string; so 1) You're actually echoing a dynamic text; and 2) Yes, you should escape a translated string.

Relevant excerpt taken from the internationalization security guide in the Plugin Handbook:

Escape Internationalized Strings

You can’t trust that a translator will only add benign text to their localization; if they want to, they could add malicious JavaScript or other code instead. To protect against that, it’s important to treat internationalized strings like you would any other untrusted input.

If you’re outputting the strings, then they should be escaped.

Insecure:

<?php _e( 'The REST API content endpoints were added in WordPress 4.7.', 'your-text-domain' ); ?>

Secure:

<?php esc_html_e( 'The REST API content endpoints were added in WordPress 4.7.', 'your-text-domain' ); ?>

In response to your comment:

example of when I might use _e() instead of esc_html_e()

So based on the excerpt from the internationalization security guide, I believe we should just use esc_html_e() and avoid using _e() unless we are 100% certain that a translation is completely clean from malicious code and special characters (apart from the basic ones like dots/., hypens/- and spaces).

And one may want to use _e() because he/she wants HTML code (e.g. b, a, etc.) in the translation to be interpreted (e.g. <b>hey</b> would visually show hey in bold like so: hey):

// if the translation was '<b>Enviar</b>' (in Spanish), then 'Enviar' would
// visually be in bold
_e( 'Submit' );
// equivalent to echo __( 'Submit' );

// but here, the text would remain as-is ('<b>Enviar</b>')
esc_html_e( 'Submit' );
// equivalent to echo esc_html( __( 'Submit' ) );

But then, instead of using _e(), one should use __() and with functions like wp_kses_data(), wp_kses_post() or wp_kses() which allows us to control the list of allowed HTML tags and attributes (e.g. we can allow/disallow href, onclick, etc.). And despite these functions do not guarantee that the output is actually secure, using them is at least better compared to simply echoing the raw HTML:

// what if the translation was 'Enviar <script>some bad JS code</script>' ?
_e( 'Submit' );

// wp_kses_data() by default disallows/removes <script> and </script> tags.
// sample output: 'Enviar some bad JS code' - doesn't look good.. but better
// than the browser executing the bad JS script.
echo wp_kses_data( __( 'Submit' ) );

Nevertheless, if one can ensure that a translation is secure (e.g. by moderating a translation), then using _e() would not be a problem — and in fact, it's simpler (just one single function call)...

Resources

Notes

  • The WordPress core also calls _e() without escaping the HTML output... e.g. _e( 'Enter your password to view comments.' ); and that distracted my focus in writing the previous versions of this answer. Nonetheless, I'm not going to comment further on that possibly insecure _e() calls..

  • And just so you know, I'm not a security expert. :)

7
  • 1
    OK thanks for that, it all indeed makes sense, and I've marked it as the answer. But you mentioned, "Use _e() when you want HTML tags in the translated string to be interpreted," and you gave an example. I understand how using _e() in this situation would be required in order to display the <b> tag. But technically, isn't this also insecure? If a translator adds undesirable code, wouldn't that code be executed in this example? – cag8f Oct 7 '19 at 13:07
  • Good catch. :) And I hope the updated answer helps you with that.. – Sally CJ Oct 7 '19 at 14:15
  • OK thanks for all that! – cag8f Oct 8 '19 at 4:36
  • 1
    "So based on the excerpt from the internationalization security guide, I believe we should just use esc_html_e() and avoid using _e() unless we are 100% certain that a translation is completely clean from malicious code and special characters (apart from the basic ones like dots/., hypens/- and spaces)." That's my understanding as well. "The WordPress core also calls _e() without escaping the HTML output... e.g. _e( 'Enter your password to view comments.' );" Curious. It's difficult to believe they made a mistake there. More likely is that we're overlooking something. – cag8f Oct 10 '19 at 12:40
  • 1
    Anyway, for now I'm fine with escaping everything. Thanks for your time and insight. – cag8f Oct 10 '19 at 12:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.