Labs ICT
Pro Login

Security

XSS prevention and safe output.

Understanding XSS Attacks

Cross-Site Scripting (XSS) happens when attackers inject malicious scripts into your web pages. If you display user input without proper escaping, attackers can steal cookies, session tokens, or redirect users to malicious sites.


<%-- DANGEROUS: Never do this --%>
<%- userComment %>
    

If userComment contains <script>stealCookies()</script>, it will execute in the user's browser.

Try it Yourself →

Escaped vs Unescaped Output

EJS provides two output tags: <%= %> for escaped output and <%- %> for unescaped output. Understanding the difference is critical for security.


<%= user.name %>
    

This converts special characters to HTML entities. < becomes &lt;, > becomes &gt;, and so on.


<%- user.name %>
    

This outputs the raw HTML. Only use this when you trust the source completely.


<%= '<script>alert("xss")</script>' %>
<%-- Renders as: &lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt; --%>

<%- '<script>alert("xss")</script>' %>
<!-- Renders as an actual script tag! -->
    

User Input Sanitization

Always sanitize user input before displaying it. Use a sanitization library to strip dangerous content.


const sanitizeHtml = require('sanitize-html');

app.post('/comment', (req, res) => {
  const clean = sanitizeHtml(req.body.comment, {
    allowedTags: ['b', 'i', 'em', 'strong'],
    allowedAttributes: {}
  });
  
  res.render('comment', { comment: clean });
});
    

This allows basic formatting while removing potentially dangerous tags and attributes.


<div class="comment">
  <%= comment %>
</div>
    
Try it Yourself →

Content Security Policy

Content Security Policy (CSP) headers add an extra layer of protection by restricting which resources can load on your pages.


const helmet = require('helmet');
app.use(helmet());

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'"],
    styleSrc: ["'self'", "'unsafe-inline'"]
  }
}));
    

Even if an XSS attack succeeds, CSP can prevent the injected script from executing or sending data to external servers.


<head>
  <meta http-equiv="Content-Security-Policy" 
        content="default-src 'self'; script-src 'self'">
</head>
    

Safe Rendering Patterns

Combine multiple safety measures for defense in depth. Never rely on just one protection method.


<%-- Always escape by default --%>
<p><%= userInput %></p>

<%-- Only use unescaped output for trusted content --%>
<%- trustedHtmlContent %>

<%-- Validate before displaying --%>
<% if (typeof userInput === 'string' && userInput.length < 1000) { %>
  <p><%= userInput %></p>
<% } %>

<%-- Use attributes carefully --%>
<div data-content="<%= userInput %>"></div>
    

Never use user input directly in JavaScript, event handlers, or href attributes without proper encoding.


<%-- DANGEROUS --%>
<a href="<%= userUrl %>">Click here</a>

<%-- SAFER --%>
<a href="/redirect?url=<%= encodeURIComponent(userUrl) %>">Click here</a>
    
Try it Yourself →