HTL Tutorial #4: Context-Aware XSS Protection

HTL Tutorial #4: Context-Aware XSS Protection

What is XSS?

Cross-Site Scripting (XSS) is a vulnerability that allows attackers to inject malicious JavaScript code into web pages.

Example of an XSS attack:

// A user enters this in a "name" field:
<script>alert('Hacked!');</script>

// Without protection, it gets executed:
<p>Hello <script>alert('Hacked!');</script></p>DANGEROUS!

HTL's Solution

HTL automatically protects against XSS by analyzing the context where the output is inserted and applying the appropriate escaping.

Automatic Escaping

<!-- Malicious user input -->
${properties.userInput}  <!-- contains: <script>alert('XSS')</script> -->

<!-- HTL safe output: -->
&lt;script&gt;alert('XSS')&lt;/script&gt;

The code is displayed as text, not executed!

HTL's 17 Contexts

HTL automatically recognizes 17 different contexts and applies the correct escaping for each one.

1. context='html' (DEFAULT)

Context: Normal HTML content Behavior: Removes potentially dangerous markup

<!-- Automatic in HTML content -->
<div>${properties.content}</div>

<!-- Explicit -->
<div>${properties.content @ context='html'}</div>

Example:

<!-- Input -->
${properties.text}  <!-- "Hello <b>World</b> <script>alert('xss')</script>" -->

<!-- Output -->
Hello <b>World</b>  <!-- <script> removed! -->

2. context='text'

Context: Plain text (encodes ALL HTML) Use: When you want to display HTML as text

<pre>${properties.codeSnippet @ context='text'}</pre>

Example:

<!-- Input -->
${'<div>HTML Code</div>' @ context='text'}

<!-- Output (visible as text) -->
&lt;div&gt;HTML Code&lt;/div&gt;

3. context='attribute'

Context: Generic HTML attribute values

<div title="${properties.tooltip @ context='attribute'}">
  Hover me
</div>

4. context='uri'

Context: URL/URI (href, src, action) Behavior: Blocks dangerous javascript:, data:, vbscript:

<!-- Safe -->
<a href="${properties.link @ context='uri'}">Link</a>

<!-- If properties.link = "javascript:alert('XSS')" -->
<!-- HTL blocks and outputs: "#" -->

Practical example:

<!-- External links -->
<a href="${properties.externalLink @ context='uri'}" target="_blank">
  Visit the site
</a>

<!-- Download -->
<a href="${properties.downloadUrl @ context='uri'}" download>
  Download PDF
</a>

<!-- Images -->
<img src="${properties.imageUrl @ context='uri'}" alt="${properties.alt}">

5. context='elementName'

Context: Dynamic HTML tag name Behavior: Whitelist of allowed tags

<${properties.tagName @ context='elementName'}>
  Content
</${properties.tagName @ context='elementName'}>

Example:

<!-- properties.tagName = "article" -->
<article>Content</article><!-- properties.tagName = "script" -->
<!-- Blocked! Output: <div> as fallback -->

6. context='attributeName'

Context: Dynamic attribute name

<div ${properties.attrName @ context='attributeName'}="value">
  Content
</div>

7-10. JavaScript Contexts

context='scriptToken'

JavaScript token (variables, functions):

<script>
  var ${properties.varName @ context='scriptToken'} = 'value';
</script>

context='scriptString'

JavaScript strings:

<script>
  var message = '${properties.message @ context='scriptString'}';
</script>

context='scriptComment'

JavaScript comments:

<script>
  /* ${properties.comment @ context='scriptComment'} */
</script>

context='scriptRegExp'

Regular expressions:

<script>
  var pattern = /${properties.pattern @ context='scriptRegExp'}/g;
</script>

11-13. CSS Contexts

context='styleToken'

CSS token (properties, values):

<div style="color: ${properties.color @ context='styleToken'}">
  Text
</div>

context='styleString'

CSS strings:

<style>
  .class::before {
    content: '${properties.content @ context='styleString'}';
  }
</style>

context='styleComment'

CSS comments:

<style>
  /* ${properties.comment @ context='styleComment'} */
</style>

14. context='comment'

Context: HTML comments

<!-- ${properties.comment @ context='comment'} -->

15. context='number'

Context: Only numeric values

<input type="number" value="${properties.age @ context='number'}">

Example:

<!-- Input: "25abc" -->
${properties.value @ context='number'}
<!-- Output: "25" (numbers only) -->

<!-- Input: "not a number" -->
${properties.value @ context='number'}
<!-- Output: "0" (fallback) -->

16. context='unsafe' ⚠️

WARNING: Disables ALL XSS protection!

Use ONLY if:

  • The content comes from an absolutely trusted source
  • The content has already been sanitized
  • You know exactly what you're doing
<!-- ⚠️ DANGEROUS if source is not safe! -->
<div>${properties.richText @ context='unsafe'}</div>

Safe alternative: Use context='html' which removes malicious scripts but keeps safe markup.

Automatic Context Detection

HTL automatically detects the context based on position:

<!-- HTML context (auto) -->
<p>${properties.text}</p>

<!-- ATTRIBUTE context (auto) -->
<div class="${properties.cssClass}"></div>

<!-- URI context (auto) -->
<a href="${properties.url}">Link</a>
<img src="${properties.image}">

<!-- TEXT context in text nodes -->
${properties.plainText}

When to Specify Context Manually

1. Inline JavaScript

<script>
  // You MUST specify the context!
  var data = '${properties.data @ context='scriptString'}';
</script>

2. Inline CSS

<style>
  .custom {
    color: ${properties.color @ context='styleToken'};
  }
</style>

3. Dynamic Attributes

<div ${properties.attrName @ context='attributeName'}="${properties.attrValue @ context='attribute'}">
</div>

Complete Example: User Profile Card

<div class="profile-card" data-sly-use.user="com.example.UserModel">
  <!-- Text content - auto HTML context -->
  <h2>${user.name}</h2>

  <!-- Bio (can contain <b>, <i> etc) - HTML context allows safe markup -->
  <p>${user.bio @ context='html'}</p>

  <!-- Avatar image - auto URI context -->
  <img src="${user.avatarUrl}" alt="${user.name}">

  <!-- Social link - explicit URI context for security -->
  <a href="${user.website @ context='uri'}" target="_blank">
    Website
  </a>

  <!-- Dynamic data attribute -->
  <div data-user-id="${user.id @ context='attribute'}">
    <!-- JavaScript context -->
    <script>
      var userName = '${user.name @ context='scriptString'}';
      var userId = ${user.id @ context='scriptToken'};
      console.log('User: ' + userName);
    </script>
  </div>

  <!-- Badge with safe but limited HTML -->
  <span class="badge">${user.badgeHtml @ context='html'}</span>
</div>

Best Practices

  1. Let HTL choose automatically when possible
  2. Always specify the context in <script> and <style>
  3. Never use context='unsafe' on user input
  4. Use context='html' instead of unsafe for rich text
  5. URI context automatically blocks dangerous links
  6. Number context for numeric inputs - prevents injection
  7. Test your contexts with malicious input during development

Summary Table

Context Use Examples
html HTML content (default) <div>${text}</div>
text Plain encoded text <pre>${code}</pre>
attribute Generic attributes <div title="${tip}">
uri URL/Link <a href="${link}">
elementName Dynamic tags <${tag}>content</${tag}>
attributeName Dynamic attributes <div ${attr}="val">
scriptToken JS tokens var ${name} = value;
scriptString JS strings var s = '${text}';
styleToken CSS token color: ${color}
number Numbers only value="${age}"
unsafe NO escaping ⚠️ Avoid if possible!

Security Testing

Try these malicious inputs in your components:

<script>alert('XSS')</script>
javascript:alert('XSS')
<img src=x onerror="alert('XSS')">
" onload="alert('XSS')
');alert('XSS');//

HTL should block them automatically!

Next Lesson

In the next lesson we'll start with block statements, beginning with data-sly-text for content output.


Lesson #4 of the HTL Tutorial series. ← Previous lesson | Next lesson →