xss-safe-display
v0.1.11
Published
A TypeScript library for safe display and sanitization to prevent XSS attacks.
Maintainers
Readme
xss-safe-display
TypeScript utilities for safe rendering of user content:
- sanitize plain text
- sanitize controlled HTML allowlists
- sanitize HTML by built-in presets (
strict,content,relaxed) - sanitize URLs
- recursively sanitize object trees
Installation
pnpm add xss-safe-displaynpm install xss-safe-displayQuick Start
import { safeDisplay, sanitizeObject, sanitizeUrl } from 'xss-safe-display';
const userInput = `<script>alert("xss")</script><b>Hello</b>`;
const safeText = safeDisplay.text(userInput);
// "<script>alert("xss")</script><b>Hello</b>"
const richHtml = `<p class="lead">Hi</p><script>alert(1)</script>`;
const safeHtml = safeDisplay.html(richHtml, {
preset: 'content',
allowedTags: ['p'],
allowedAttributes: ['class']
});
// { __html: '<p class="lead">Hi</p>' }
const safeHref = sanitizeUrl('//evil.example');
// "#"
const payload = {
title: '<b>News</b>',
body: '<p>Hello <b>world</b></p><script>steal()</script>'
};
const sanitized = sanitizeObject(payload, {
htmlPaths: ['body'],
htmlAllowedTags: ['p', 'b'],
limits: { maxDepth: 20, maxNodes: 10000, maxStringLength: 50000 }
});
// { title: 'News', body: '<p>Hello <b>world</b></p>' }Usage Guide
1. Plain text output (default safe UI text)
import { sanitizeString, escapeHTML } from 'xss-safe-display';
const input = `<img src=x onerror=alert(1)>Hello`;
const plain = sanitizeString(input);
// "Hello"
const escaped = escapeHTML(input);
// "<img src=x onerror=alert(1)>Hello"2. Rich HTML output with preset policy
import { sanitizeHTML, sanitizeHTMLWithPreset } from 'xss-safe-display';
const html = `<p class="lead">Hello <b>world</b></p><script>alert(1)</script>`;
const strict = sanitizeHTMLWithPreset(html, 'strict');
// "<b>world</b>"
const content = sanitizeHTML(html, {
preset: 'content',
allowedTags: ['p', 'b'],
allowedAttributes: ['class']
});
// '<p class="lead">Hello <b>world</b></p>'3. Deep object sanitization (API payloads / DB output)
import { sanitizeObject } from 'xss-safe-display';
const payload = {
title: '<b>News</b>',
content: {
blocks: [{ html: '<p>Safe</p><script>bad()</script>' }]
}
};
const result = sanitizeObject(payload, {
htmlPaths: ['content.blocks[].html'],
htmlAllowedTags: ['p'],
limits: {
maxDepth: 20,
maxNodes: 10000,
maxStringLength: 50000
}
});4. URL policy (protocol + host filtering)
import { sanitizeUrl } from 'xss-safe-display';
const profileUrl = sanitizeUrl('https://api.example.com/users/1', {
allowedHosts: ['example.com'],
blockedHosts: ['admin.example.com']
});
// "https://api.example.com/users/1"
const blocked = sanitizeUrl('https://evil.test/phishing', {
allowedHosts: ['example.com']
});
// "#"5. Frontend convenience wrapper (safeDisplay)
import { safeDisplay } from 'xss-safe-display';
const text = safeDisplay.text('<b>Hello</b>');
const html = safeDisplay.htmlWithPreset('<p>Hi</p><script>x</script>', 'strict');
const href = safeDisplay.url('example.com');Recommended Defaults
- Use
safeDisplay.text(...)for any plain user-provided text. - Use
sanitizeHTMLWithPreset(..., 'strict')unless you need richer formatting. - Use
sanitizeObject(..., { limits: ... })for large external payloads. - Use
sanitizeUrl(..., { allowedHosts: [...] })for external links in apps.
API
sanitizeString(value: string): string
Removes all HTML tags and unsafe markup from a string.
import { sanitizeString } from 'xss-safe-display';
sanitizeString('<script>alert(1)</script>Hello');
// "Hello"sanitizeHTML(html: string, allowedTags?: string[]): string
sanitizeHTML(html: string, options?: SanitizeHTMLOptions): string
Sanitizes HTML with an allowlist. Supports legacy string[] tags argument and an options object.
import { sanitizeHTML } from 'xss-safe-display';
sanitizeHTML('<p class="x">Hi</p><img src=x onerror=1>', {
preset: 'content',
allowedTags: ['p'],
allowedAttributes: ['class']
});
// '<p class="x">Hi</p>'sanitizeHTMLWithPreset(html: string, preset?: SanitizeHTMLPreset): string
Applies built-in preset policy:
strictcontentrelaxed
import { sanitizeHTMLWithPreset } from 'xss-safe-display';
sanitizeHTMLWithPreset('<p><b>Hello</b></p><script>alert(1)</script>', 'strict');
// '<b>Hello</b>'sanitizeObject<T>(obj: T, options?: SanitizeObjectOptions): T
Recursively sanitizes strings inside arrays and plain objects.
- preserves object cycles (no stack overflow)
- preserves non-plain objects that are not collections (for example
Date) unchanged - sanitizes
MapandSetvalues recursively - supports selected keys as rich HTML via
htmlFields - supports path-based rich HTML targeting via
htmlPaths(example:content.blocks[].html) - supports optional DoS limits:
limits.maxDepth,limits.maxNodes,limits.maxStringLength
import { sanitizeObject } from 'xss-safe-display';
const data = {
title: '<b>Hello</b>',
description: '<p>Safe <b>HTML</b></p><script>alert(1)</script>'
};
const result = sanitizeObject(data, {
htmlPaths: ['description'],
htmlAllowedTags: ['p', 'b'],
limits: {
maxDepth: 16,
maxNodes: 5000,
maxStringLength: 10000
}
});escapeHTML(text: string): string
Escapes special characters for safe text rendering.
import { escapeHTML } from 'xss-safe-display';
escapeHTML(`<div>"'&</div>`);
// '<div>"'&</div>'sanitizeUrl(url: string | null | undefined, options?: SanitizeUrlOptions): string
URL sanitizer with protocol allowlist and safe fallback.
- blocks
javascript:,data:,vbscript: - blocks protocol-relative URLs like
//evil.example - allows
http:,https:,mailto:,tel:by default - supports host policy via
allowedHosts/blockedHosts - auto-prefixes plain domains with
https://
import { sanitizeUrl } from 'xss-safe-display';
sanitizeUrl('example.com');
// 'https://example.com'
sanitizeUrl('/admin', { allowRelative: false, fallbackUrl: '/home' });
// '/home'
sanitizeUrl('tel:+37060000000');
// 'tel:+37060000000'
sanitizeUrl('https://api.example.com', { allowedHosts: ['example.com'] });
// 'https://api.example.com'
sanitizeUrl('https://evil.test', { allowedHosts: ['example.com'] });
// '#'safeDisplay
Convenience wrapper:
import { safeDisplay } from 'xss-safe-display';
safeDisplay.text('<b>Hello</b>');
safeDisplay.html('<p>Hi</p><script>x</script>', ['p']);
safeDisplay.htmlWithPreset('<p>Hi</p><script>x</script>', 'strict');
safeDisplay.url('https://example.com');Exported Types
import type {
SanitizeHTMLPreset,
SanitizeHTMLOptions,
SanitizeObjectLimits,
SanitizeObjectOptions,
SanitizeUrlOptions
} from 'xss-safe-display';Local Development
Install dependencies:
pnpm install --store-dir .pnpm-storeRun checks and tests:
pnpm run type-check
pnpm run lint
pnpm test
pnpm run test:coverageBuild:
pnpm run buildIf build fails with spawn EPERM after install, approve build scripts for esbuild:
pnpm approve-buildsSecurity Notes
- Always keep allowlists minimal (
allowedTags,allowedAttributes). - Sanitize on output even if input was sanitized earlier.
- For rich content in front-end frameworks, combine with framework-specific safety practices.
- URL sanitization here is allowlist-based and intentionally strict.
License
MIT (License.txt)
