npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

unsane

v0.1.0

Published

A tiny, zero-dependency, run-anywhere HTML sanitization library written in TypeScript.

Readme

Unsane logo

Unsane

A tiny, zero-dependency, run-anywhere HTML sanitization library written in TypeScript.

Features

  • Lightweight: Runtime and published-package size budgets are enforced in CI
  • Zero dependencies: Includes internal HTML entity encoder/decoder and state machine tokenizer
  • Run anywhere: Doesn't rely on DOM APIs, JSDOM, or Node APIs, so you can use in any environment

Installation

npm install unsane

Requirements

Unsane requires a supported release of Node.js 22 or later.

Usage

Basic Usage

// ES Modules
import { sanitize } from "unsane";

// Input: potentially malicious HTML
const dirty =
  '<script>alert("xss")</script><div onclick="alert(`pwned`)">Hello</div>';

// Output: clean HTML with dangerous elements/attributes removed
const clean = sanitize(dirty);
// -> '<div>Hello</div>'

Configuration Options

You can customize the sanitizer behavior with options:

import { sanitize } from "unsane";

const options = {
  // Custom list of allowed tags
  allowedTags: ["p", "span", "strong", "em", "a", "img"],

  // Custom list of allowed attributes for each tag
  allowedAttributes: {
    a: ["href", "target"],
    img: ["src", "alt", "width", "height"],
    "*": ["class"], // Attributes allowed on all elements
  },
};

const dirty =
  '<script>alert("xss")</script><a href="https://example.com" onclick="hack()" style="color:red">Link</a>';
const clean = sanitize(dirty, options);
// -> '<a href="https://example.com">Link</a>'

When the same policy is used repeatedly, compile it once to avoid rebuilding its lookup tables for every input:

import { createSanitizer } from "unsane";

const comments = createSanitizer({
  allowedTags: ["p", "strong", "em", "a"],
  allowedAttributes: { a: ["href"] },
});

comments('<p><a href="/docs">Read the docs</a></p>');

Available options:

  • allowedTags – array of tag names that are kept in the sanitized output.
  • allowedAttributes – object mapping tag names to allowed attributes. Use "*" for attributes allowed on all tags.
  • maxInputLength – maximum input string length accepted by sanitize(). Defaults to 1_000_000 characters. Set to Infinity only for trusted, already-bounded inputs.

Custom allowlists use a fixed capability model. Unsane strips document-state elements (html, head, body, base, link, meta, and form), upgradeable custom-element names containing -, event/CSS attributes, URL-list attributes, form-association attributes, browsing-state attributes, namespace transitions, and other specialized active grammars even when they are explicitly listed. Unknown non-upgradeable elements, data-*, aria-*, and other explicitly allowed inert attributes remain supported.

Single-URL attributes, including legacy attributes such as background, dynsrc, and lowsrc, all use the fixed protocol validator. Browsing-context targets are limited to _self and _blank; _blank removes opener and adds noopener noreferrer. Multi-URL and embedded-document grammars remain denied instead of receiving incomplete parsing.

URL-bearing attributes use a fixed conservative protocol allowlist: http:, https:, mailto:, tel:, ftp:, and sms:. Custom protocol allowlists are intentionally not part of the public API. Relative URLs and fragments are allowed, while protocol-relative URLs (//example.com) are removed.

Links with target="_blank" are emitted with rel="noopener noreferrer" even when rel is omitted from a custom allowlist.

Security Notes

  • Unsane sanitizes HTML fragments, not full document policies. Keep Content Security Policy, Trusted Types, and framework escaping in place.
  • URL attributes are checked after entity decoding and protocol normalization, but URL rewriting and link reputation checks remain the caller's job.
  • Inputs longer than the configured maxInputLength throw a RangeError; keep upstream request-body limits in place for untrusted traffic.
  • CSS is not sanitized. style attributes and <style> elements are dropped instead of parsed.
  • SVG and MathML are outside the supported safe subset and are removed rather than partially sanitized.
  • id is not allowed by default because named DOM properties can collide with application globals. If a custom policy enables id or name, namespace their values before inserting the result into a document.
  • If you expand the tag or attribute allowlists, add app-specific tests for the markup you now accept.

HTML Entity Functions

import { encode, decode, escape } from "unsane";

// Encode special characters into entities
const encoded = encode('<div>"text"</div>');
// -> '&#x3C;div&#x3E;&#x22;text&#x22;&#x3C;/div&#x3E;'

// Decode HTML entities
const decoded = decode("&lt;div&gt;&quot;text&quot;&lt;/div&gt;");
// -> '<div>"text"</div>'

// Escape HTML special characters
const escaped = escape('<script>"alert"</script>');
// -> '&lt;script&gt;&quot;alert&quot;&lt;/script&gt;'

Numeric references follow the HTML replacement rules for nulls, surrogates, out-of-range values, and the Windows-1252 compatibility range. During sanitization, named references outside Unsane's compact built-in decoder are preserved for the browser to interpret, so their visible value is not changed and the runtime does not need to ship the full multi-thousand-entry entity table. URL and other active attributes remain subject to conservative capability-specific validation.

CLI Usage

You can also sanitize input directly from the command line:

echo '<script>alert("xss")</script>' | npx unsane

This reads HTML from stdin and prints the sanitized result to stdout.

Runtime Size

This library is designed to stay lightweight while providing conservative HTML sanitization. The size gate builds the actual tree-shaken consumer entry point and checks the package that npm would publish. It enforces these ceilings:

| Metric | Budget | | ---------------------------- | ------------ | | Minified consumer ESM bundle | 10 KiB | | Minified + gzip | 4 KiB | | Minified + Brotli | 3.75 KiB | | npm tarball / unpacked size | 20 / 100 KiB | | Published file count | 32 files |

You can check the package size yourself with:

npm run analyze-size

This command enforces conservative bundle and published-package budgets in CI so accidental growth fails before release. Runtime throughput can be measured against representative plain-text, safe-fragment, attribute-heavy, raw-content, and hostile-nesting workloads with:

npm run benchmark

Threat Model

  • Supported contexts: Designed for server-side rendering pipelines and JavaScript runtimes (Node.js ≥22, Cloudflare Workers, Deno) where DOM APIs are unavailable. Browser usage is possible, but the sanitizer never mutates DOM nodes directly; it only returns sanitized HTML strings.
  • Supported inputs: Operates on HTML fragments (snippets destined for innerHTML/text interpolation). Full documents (<!DOCTYPE>, <html>, <head>) are normalized but not guaranteed to preserve structure.
  • Guarantees: Removes elements outside a conservative allowlist, strips disallowed attributes (especially event handlers, protocol-relative URLs, and URL-bearing attributes with non-HTTP(S)/mailto/tel/ftp/sms protocols), normalizes and escapes inline text, and self-closes void tags.
  • Non-goals / exclusions: Does not sanitize or interpret CSS (style attributes are dropped), JavaScript, MathML, or SVG namespaces—content in those namespaces is removed rather than partially sanitized. It does not attempt to sanitize inline <style> blocks or external resources (<link>, <script>, <iframe>, etc.) and should be paired with CSPs.
  • Consumer responsibilities: Validate that customized allowedTags/allowedAttributes meet your application’s needs, run application-specific allowlist tests, and apply additional sanitization for CSS/URL rewriting if end users can supply styles or alternate protocols.
  • Intended use: Defense-in-depth for semi-trusted markup (e.g., Markdown already filtered elsewhere). Do not treat Unsane as a drop-in replacement for battle-tested libraries like DOMPurify without additional auditing, fuzzing, and monitoring.

Security Features

Unsane is designed to protect against common XSS vectors:

  • Removes dangerous tags like <script>, <style>, <iframe>, etc.
  • Strips event handler attributes (onclick, onerror, etc.)
  • Removes javascript: URLs and other dangerous protocols
  • Handles unicode escape sequences in URLs
  • Properly encodes HTML entities
  • Maintains HTML structure to prevent invalid nesting exploits
  • Properly handles HTML edge cases with state machine-based parsing
  • Robust handling of attribute values with proper quote parsing

Browser Compatibility

Works in all modern browsers as well as Node.js environments. No DOM or browser APIs are required.

Contributing

Please see CONTRIBUTING.md for instructions on setting up the project and running tests. The dist directory is generated and should not be committed.

License

MIT