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: -->
<script>alert('XSS')</script>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 }</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 }</pre>Example:
<!-- Input -->
${'<div>HTML Code</div>' }
<!-- Output (visible as text) -->
<div>HTML Code</div>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 }>
Content
</${properties.tagName }>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 }
<!-- Output: "25" (numbers only) -->
<!-- Input: "not a number" -->
${properties.value }
<!-- 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 }</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 }</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 }</span>
</div>Best Practices
- Let HTL choose automatically when possible
- Always specify the context in
<script>and<style> - Never use
context='unsafe'on user input - Use
context='html'instead ofunsafefor rich text - URI context automatically blocks dangerous links
- Number context for numeric inputs - prevents injection
- 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 →