Lean and configurable whitelist-oriented HTML sanitizer with native Sanitizer API support.
Note: This is a TypeScript rewrite of the original
insanepackage by @bevacqua. The original package is no longer maintained and has known security vulnerabilities (CVE-2020-26303). This fork has been renamed tohtml-saneand published as a new package.
- Whitelist-based - Only allows tags, attributes, and classes you explicitly permit
- URL sanitization - Validates URL schemes in href, src, and other URI attributes
- Lightweight - ~4KB gzipped with zero runtime dependencies (except
html-entities) - TypeScript - Written in TypeScript with full type definitions
- Native API support - Automatically uses the browser's native Sanitizer API when available and compatible
- Universal - Works in Node.js and browsers
- Security hardened - Fixes CVE-2020-26303 (ReDoS vulnerability) and includes 280+ security tests
npm install html-sane
# or
pnpm add html-sane
# or
yarn add html-saneIf you're migrating from the original insane package:
npm uninstall insane
npm install html-saneThen update your imports:
- import insane from 'insane'
+ import insane from 'html-sane'The API is fully compatible - no other code changes required.
This rewrite addresses the following security issues from the original package:
- CVE-2020-26303: Regular Expression Denial of Service (ReDoS) - Fixed by rewriting the HTML parser to use a state machine approach instead of vulnerable regex patterns
- Comprehensive security test suite covering:
- XSS attack vectors (script injection, event handlers, javascript: URLs)
- URL scheme validation (blocks javascript:, data:, vbscript:, etc.)
- HTML entity encoding bypasses
- Malformed HTML handling
- Nested/recursive attack patterns
- ReDoS prevention
- mXSS (mutation XSS) vectors
- DOM clobbering prevention
- Prototype pollution prevention
import insane from 'html-sane'
// Basic usage with defaults
insane('<script>alert(1)</script><p>Hello</p>')
// => '<p>Hello</p>'
// Custom options
insane('<div class="foo bar">text</div>', {
allowedTags: ['div'],
allowedClasses: { div: ['foo'] }
})
// => '<div class="foo">text</div>'Sanitizes an HTML string.
html- The HTML string to sanitizeoptions- Optional sanitization options (merged with defaults)strict- Iftrue, options are used as-is without merging with defaults
Returns the sanitized HTML string.
Array of allowed HTML tag names (lowercase).
insane('<div><script>bad</script></div>', {
allowedTags: ['div']
})
// => '<div></div>'Map of tag names to allowed attribute names. Use '*' for attributes allowed on all tags.
insane('<a href="/foo" onclick="bad()">link</a>', {
allowedTags: ['a'],
allowedAttributes: { a: ['href'] }
})
// => '<a href="/foo">link</a>'Map of tag names to allowed class names. Only applies when 'class' is NOT in allowedAttributes for that tag.
insane('<div class="safe danger">text</div>', {
allowedTags: ['div'],
allowedClasses: { div: ['safe'] }
})
// => '<div class="safe">text</div>'Array of allowed URL schemes for URI attributes.
insane('<a href="javascript:alert(1)">link</a>', {
allowedSchemes: ['http', 'https']
})
// => '<a>link</a>'Default: ['http', 'https', 'mailto']
Custom filter function to accept/reject tags. Return true to keep the tag, false to remove it.
insane('<span data-user="admin">secret</span><span>public</span>', {
allowedTags: ['span'],
filter: (token) => !token.attrs['data-user']
})
// => '<span>public</span>'Transform text content before output.
insane('<p>hello world</p>', {
transformText: (text) => text.toUpperCase()
})
// => '<p>HELLO WORLD</p>'{
allowedTags: [
'a', 'abbr', 'article', 'b', 'blockquote', 'br', 'caption', 'code',
'del', 'details', 'div', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'hr', 'i', 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'ol', 'p',
'pre', 'section', 'span', 'strike', 'strong', 'sub', 'summary',
'sup', 'table', 'tbody', 'td', 'th', 'thead', 'tr', 'u', 'ul'
],
allowedAttributes: {
'*': ['title', 'accesskey'],
a: ['href', 'name', 'target', 'aria-label'],
iframe: ['allowfullscreen', 'frameborder', 'src'],
img: ['src', 'alt', 'title', 'aria-label']
},
allowedClasses: {},
allowedSchemes: ['http', 'https', 'mailto'],
filter: null,
transformText: null
}In supported browsers, html-sane will automatically use the native Sanitizer API when:
- The API is available in the browser
- The options don't use features unsupported by the native API (
allowedClasses,filter,transformText, or customallowedSchemes)
This provides better performance and security in modern browsers while maintaining full compatibility everywhere else.
Full TypeScript support with exported types:
import insane, { type InsaneOptions, type TokenInfo, defaults } from 'html-sane'
const options: InsaneOptions = {
allowedTags: ['p', 'strong'],
filter: (token: TokenInfo) => token.tag !== 'script'
}
console.log(defaults.allowedTags)This package is a TypeScript rewrite of insane by Nicolas Bevacqua.
MIT