Functions and rules for sanitizing HTML content to prevent XSS attacks while preserving safe formatting.
/**
* Sanitizes HTML to prevent XSS attacks using configurable rule sets.
* By default, uses HTML_SANITIZE_DEFAULT_RULES which allow safe HTML tags and attributes.
* @param input - HTML string to sanitize
* @param ruleSet - Optional sanitization rules (defaults to HTML_SANITIZE_DEFAULT_RULES)
* @returns Sanitized HTML string
*/
function sanitizeHtml(input: string, ruleSet?: sanitize.IOptions): string;/**
* Default HTML sanitization rules based on sanitize-html defaults.
* Allows most safe HTML tags and includes styled span elements for @mentions.
*/
const HTML_SANITIZE_DEFAULT_RULES: sanitize.IOptions;Allowed in default rules:
/**
* Extended sanitization rules for markdown tiles.
* Allows iframes, images, and extensive styling attributes including:
* - Text styling (font-size, font-weight, color, text-align)
* - Layout (margin, padding, width, height)
* - Borders (border-radius)
* - Background colors
* Used for user-generated markdown content in dashboard tiles.
*/
const HTML_SANITIZE_MARKDOWN_TILE_RULES: sanitize.IOptions;Additional allowed in markdown tile rules:
import {
sanitizeHtml,
HTML_SANITIZE_DEFAULT_RULES,
HTML_SANITIZE_MARKDOWN_TILE_RULES,
} from '@lightdash/common';
// Sanitize with default rules (safe for comments, user input)
const safeHtml = sanitizeHtml('<p>Hello <script>alert("XSS")</script></p>');
// Returns: '<p>Hello </p>' (script tag removed)
// Dangerous content is stripped
const malicious = `
<p>Hello</p>
<script>
// Malicious code
document.cookie = "steal";
</script>
<img src="x" onerror="alert('XSS')">
`;
const cleaned = sanitizeHtml(malicious);
// Returns: '<p>Hello</p>' (script and malicious attributes removed)
// Safe HTML is preserved
const safe = `
<div>
<p>Welcome <strong>user</strong></p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
</div>
`;
const result = sanitizeHtml(safe);
// Returns: HTML is preserved as-isimport {
sanitizeHtml,
HTML_SANITIZE_MARKDOWN_TILE_RULES,
} from '@lightdash/common';
// Sanitize markdown tile content (allows iframes and styling)
const markdownHtml = sanitizeHtml(
'<iframe src="https://example.com/embed"></iframe>',
HTML_SANITIZE_MARKDOWN_TILE_RULES
);
// Returns: iframe is preserved
// Allows extended styling
const styledContent = `
<div style="background-color: #f5f5f5; padding: 20px; border-radius: 8px;">
<h2 style="color: #333; font-size: 24px;">Title</h2>
<p style="margin: 10px 0;">Content with styling</p>
<iframe src="https://charts.example.com/embed/abc123"
width="600"
height="400">
</iframe>
</div>
`;
const sanitized = sanitizeHtml(styledContent, HTML_SANITIZE_MARKDOWN_TILE_RULES);
// Returns: All styling and iframe preservedimport {
sanitizeHtml,
HTML_SANITIZE_DEFAULT_RULES,
} from '@lightdash/common';
// Create custom rules based on defaults
const customRules: sanitize.IOptions = {
...HTML_SANITIZE_DEFAULT_RULES,
allowedTags: [
...(HTML_SANITIZE_DEFAULT_RULES.allowedTags || []),
'custom',
'video'
],
allowedAttributes: {
...HTML_SANITIZE_DEFAULT_RULES.allowedAttributes,
video: ['src', 'controls', 'width', 'height'],
},
};
const htmlWithVideo = `
<div>
<p>Check out this video:</p>
<video src="/videos/demo.mp4" controls width="640" height="360"></video>
</div>
`;
const result = sanitizeHtml(htmlWithVideo, customRules);
// Returns: HTML with video tag preservedimport { sanitizeHtml } from '@lightdash/common';
// Sanitize user-generated comments
function saveComment(rawComment: string) {
const safeComment = sanitizeHtml(rawComment);
return database.comments.insert({
content: safeComment,
createdAt: new Date(),
});
}
// Example usage
const userComment = `
<p>Great dashboard!</p>
<p>Here's a link: <a href="https://example.com">Example</a></p>
<script>alert('XSS')</script>
`;
const saved = saveComment(userComment);
// Saves: '<p>Great dashboard!</p><p>Here's a link: <a href="https://example.com">Example</a></p>'import { sanitizeHtml, HTML_SANITIZE_MARKDOWN_TILE_RULES } from '@lightdash/common';
// Sanitize markdown tile content
function saveMarkdownTile(rawHtml: string) {
const safeHtml = sanitizeHtml(rawHtml, HTML_SANITIZE_MARKDOWN_TILE_RULES);
return database.tiles.insert({
type: 'markdown',
content: safeHtml,
});
}
// Example with embedded chart
const markdownContent = `
<div style="background: white; padding: 20px;">
<h2 style="color: #1a1a1a;">Sales Overview</h2>
<p>Embedded chart below:</p>
<iframe src="https://charts.lightdash.com/embed/sales-chart"
width="100%"
height="400">
</iframe>
</div>
`;
const saved = saveMarkdownTile(markdownContent);
// All content including iframe is preservedimport { sanitizeHtml } from '@lightdash/common';
// Sanitize API response data
app.get('/api/dashboard/:id', async (req, res) => {
const dashboard = await getDashboard(req.params.id);
// Sanitize description before sending
const response = {
...dashboard,
description: sanitizeHtml(dashboard.description),
tiles: dashboard.tiles.map(tile => ({
...tile,
description: sanitizeHtml(tile.description),
})),
};
res.json(response);
});| Feature | Default Rules | Markdown Tile Rules |
|---|---|---|
| Basic HTML tags | ✓ | ✓ |
| Links (a tag) | ✓ | ✓ |
| Images (img tag) | ✓ | ✓ |
| Iframes | ✗ | ✓ |
| Inline styles | Limited | Extensive |
| Script tags | ✗ | ✗ |
| Event handlers | ✗ | ✗ |