or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

api

features

charts

charts.mdconditional-formatting.mdvisualizations.md
authorization.mdchangesets.mdcharts-as-code.mdcompiler.mddashboards.mddbt.mdee-features.mdformatting.mdparameters.mdpivot.mdprojects-spaces.mdsql-runner.mdtemplating.mdwarehouse.md
index.md
tile.json

html-sanitization.mddocs/api/utilities/specialized/

HTML Sanitization

Functions and rules for sanitizing HTML content to prevent XSS attacks while preserving safe formatting.

Core Function

/**
 * 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;

Sanitization Rule Sets

Default Rules

/**
 * 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:

  • Common safe HTML tags (p, div, span, a, strong, em, ul, ol, li, etc.)
  • Basic formatting attributes
  • Styled span elements for @mentions
  • Safe href and src attributes with protocol restrictions

Markdown Tile 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:

  • iframe elements (for embeds)
  • Extended CSS properties
  • Custom styling attributes
  • Image elements with size attributes

Examples

Basic Sanitization (Default 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-is

Markdown Tile Sanitization

import {
  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 preserved

Custom Rules

import {
  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 preserved

Use Cases

User Comments and Descriptions

import { 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>'

Dashboard Tile Content

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 preserved

API Response Sanitization

import { 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);
});

Security Considerations

  • Always Sanitize User Input: Never trust HTML from user input
  • Choose Appropriate Rules: Use default rules for comments, markdown tile rules for rich content
  • XSS Prevention: Sanitization removes dangerous scripts, event handlers, and unsafe protocols
  • Allowlist Approach: Only explicitly allowed tags and attributes are preserved
  • Context-Specific: Different contexts may require different rule sets

Rule Set Comparison

FeatureDefault RulesMarkdown Tile Rules
Basic HTML tags
Links (a tag)
Images (img tag)
Iframes
Inline stylesLimitedExtensive
Script tags
Event handlers

Related Utilities

  • Use sanitization before storing user content in database
  • Apply sanitization before rendering HTML in UI
  • Combine with validation schemas for comprehensive input security