{"_id":"@darylcecile/sanitizer","_rev":"2-1ca78e9323448609db3e06dee82fe88a","name":"@darylcecile/sanitizer","dist-tags":{"latest":"0.0.2"},"versions":{"0.0.1":{"name":"@darylcecile/sanitizer","version":"0.0.1","keywords":["sanitizer","html","xss","sanitize","w3c","ssr","parser","security","zero-dependency"],"author":{"name":"Daryl Cecile"},"license":"MIT","_id":"@darylcecile/sanitizer@0.0.1","maintainers":[{"name":"darcectech","email":"darylcecile@gmail.com"}],"homepage":"https://github.com/darylcecile/sanitizer#readme","bugs":{"url":"https://github.com/darylcecile/sanitizer/issues"},"dist":{"shasum":"47db4169a829d5240231a784f226395cf256c3d4","tarball":"https://registry.npmjs.org/@darylcecile/sanitizer/-/sanitizer-0.0.1.tgz","fileCount":13,"integrity":"sha512-VvtrUspQZiYiyh03BO/ubICPwA1vZu0IJEgawsMNtiLLn51vhZN68zGNHvS8WM3oV0bddzWfNxZHi5gGIqXHxQ==","signatures":[{"sig":"MEQCIHMNEMqIp2/QydfpreroDUIEz/IwGNGl3c2MdoX4d51TAiBjYAx3TDcdRrEBl9BoXuZusc08aHM7QSs2LTiLIL+YRg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":73689},"main":"dist/index.js","type":"module","_from":"file:darylcecile-sanitizer-0.0.1.tgz","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"test":"bun test","build":"rm -rf dist && bun build src/index.ts --outdir dist --format esm --splitting --target browser && bunx tsc --project tsconfig.build.json","docs:lint":"bunx gh-doccy lint","typecheck":"bunx tsc --noEmit","docs:review":"bunx gh-doccy review"},"_npmUser":{"name":"darcectech","email":"darylcecile@gmail.com"},"_resolved":"/private/var/folders/zh/l70grz9n3ll9j9c_f1ghp7gr0000gn/T/fc80b61acb2e5552ccf08776ab67d8cf/darylcecile-sanitizer-0.0.1.tgz","_integrity":"sha512-VvtrUspQZiYiyh03BO/ubICPwA1vZu0IJEgawsMNtiLLn51vhZN68zGNHvS8WM3oV0bddzWfNxZHi5gGIqXHxQ==","repository":{"url":"git+https://github.com/darylcecile/sanitizer.git","type":"git"},"_npmVersion":"10.9.4","description":"A zero-dependency, SSR-safe HTML sanitizer following the W3C Sanitizer API spec","directories":{},"_nodeVersion":"22.21.1","_hasShrinkwrap":false,"devDependencies":{"gh-doccy":"latest","@types/bun":"latest"},"peerDependencies":{"typescript":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/sanitizer_0.0.1_1774384629984_0.6478947665031509","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@darylcecile/sanitizer","version":"0.0.2","description":"A zero-dependency, SSR-safe HTML sanitizer following the W3C Sanitizer API spec","module":"dist/index.js","main":"dist/index.js","types":"dist/index.d.ts","type":"module","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"}},"scripts":{"build":"rm -rf dist && bun build src/index.ts --outdir dist --format esm --splitting --target browser && bunx tsc --project tsconfig.build.json","test":"bun test","typecheck":"bunx tsc --noEmit","prepublishOnly":"bun run build","docs:lint":"bunx gh-doccy lint","docs:review":"bunx gh-doccy review"},"keywords":["sanitizer","html","xss","sanitize","w3c","ssr","parser","security","zero-dependency"],"author":{"name":"Daryl Cecile"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/darylcecile/sanitizer.git"},"homepage":"https://github.com/darylcecile/sanitizer#readme","bugs":{"url":"https://github.com/darylcecile/sanitizer/issues"},"devDependencies":{"@types/bun":"latest","gh-doccy":"latest"},"peerDependencies":{"typescript":"^5.0.0"},"gitHead":"f59fe6c3958e3f305fd56b5383f79fedff9cf86f","_id":"@darylcecile/sanitizer@0.0.2","_nodeVersion":"24.14.0","_npmVersion":"11.9.0","dist":{"integrity":"sha512-SIw6oH0BWIw1R0FCcZ7RZk0BnLzqtVRaMtVID+DvLHWKPJochLECwwAkj0hvWlpvEmmtBZCWPhIhiKSc3Uy4Eg==","shasum":"52746c49883d5742683146557f2dc38a99654cce","tarball":"https://registry.npmjs.org/@darylcecile/sanitizer/-/sanitizer-0.0.2.tgz","fileCount":13,"unpackedSize":73729,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@darylcecile%2fsanitizer@0.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHGmusHHdFfXMGM8UzyDaFTmO1HaqyPOD5ybPZwj3yN3AiBDYRU35FSCF+Zae3IpmAmEotsC/hL3JqVOQAxAd6RbpA=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:d4c590d0-0ff2-4573-8471-c03b8be895e9"}},"directories":{},"maintainers":[{"name":"darcectech","email":"darylcecile@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sanitizer_0.0.2_1774384804492_0.24792593886837455"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-24T20:37:09.861Z","modified":"2026-03-24T20:40:04.904Z","0.0.1":"2026-03-24T20:37:10.139Z","0.0.2":"2026-03-24T20:40:04.634Z"},"bugs":{"url":"https://github.com/darylcecile/sanitizer/issues"},"author":{"name":"Daryl Cecile"},"license":"MIT","homepage":"https://github.com/darylcecile/sanitizer#readme","keywords":["sanitizer","html","xss","sanitize","w3c","ssr","parser","security","zero-dependency"],"repository":{"type":"git","url":"git+https://github.com/darylcecile/sanitizer.git"},"description":"A zero-dependency, SSR-safe HTML sanitizer following the W3C Sanitizer API spec","maintainers":[{"name":"darcectech","email":"darylcecile@gmail.com"}],"readme":"# @darylcecile/sanitizer\n\nA **zero-dependency**, **SSR-safe** HTML Sanitizer following the [W3C Sanitizer API spec](https://wicg.github.io/sanitizer-api). Works with Bun, Node, Deno, or any JavaScript runtime — no browser DOM required.\n\n## Features\n\n- 🔒 **XSS protection** — strips `<script>`, event handlers, `javascript:` URLs, and more\n- 📐 **W3C spec-compliant** — follows the [Sanitizer API](https://wicg.github.io/sanitizer-api) configuration model\n- 🌐 **SSR-safe** — custom HTML parser & serializer, no browser APIs needed\n- 📦 **Zero dependencies** — lightweight, self-contained\n- 🧩 **Configurable** — element/attribute allow-lists, remove-lists, `replaceWithChildrenElements`\n- 🔧 **Exposed parser** — parse HTML to AST, walk/modify the tree, serialize back to HTML\n- ✅ **TypeScript-first** — full type definitions\n- 🧪 **Battle-tested** — 188 tests including adapted `sanitize-html` test vectors\n\n## Install\n\n```bash\nbun add @darylcecile/sanitizer\n```\n\n## Quick Start\n\n```typescript\nimport { sanitize } from \"@darylcecile/sanitizer\";\n\n// Safe by default — strips scripts, event handlers, dangerous URLs\nsanitize('<div onclick=\"alert(1)\">Hello</div>');\n// → '<div>Hello</div>'\n\nsanitize('<script>alert(\"xss\")</script><p>Safe content</p>');\n// → '<p>Safe content</p>'\n\nsanitize('<a href=\"javascript:alert(1)\">click</a>');\n// → '<a>click</a>'\n```\n\n## What Gets Stripped (Safe Mode)\n\nBy default, `sanitize()` runs in **safe mode** using the W3C built-in safe default configuration. This:\n\n| Category | Behavior |\n|----------|----------|\n| **Script elements** | `<script>`, SVG `<script>` — removed with content |\n| **Embedding elements** | `<iframe>`, `<embed>`, `<object>`, `<frame>` — removed |\n| **SVG `<use>`** | Removed (can reference external XSS payloads) |\n| **Event handlers** | All `on*` attributes (`onclick`, `onerror`, `onload`, etc.) — stripped |\n| **`javascript:` URLs** | Stripped from `href`, `action`, `formaction` and SVG animation attributes |\n| **Comments** | Stripped by default |\n| **Unknown elements** | Any element not in the W3C safe default allow-list — removed |\n| **Unsafe attributes** | Any attribute not in the W3C safe default — stripped |\n\n### What's Preserved\n\nThe default config allows ~100 safe HTML, SVG, and MathML elements with their appropriate attributes. This includes:\n\n- **Structure**: `<div>`, `<span>`, `<p>`, `<article>`, `<section>`, `<nav>`, `<header>`, `<footer>`, etc.\n- **Text formatting**: `<b>`, `<i>`, `<em>`, `<strong>`, `<code>`, `<pre>`, `<mark>`, `<sub>`, `<sup>`, etc.\n- **Lists**: `<ul>`, `<ol>`, `<li>`, `<dl>`, `<dt>`, `<dd>`\n- **Tables**: `<table>`, `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>` (with `colspan`, `rowspan`, etc.)\n- **Links**: `<a>` (with `href`, `hreflang`, `type`)\n- **Headings**: `<h1>` through `<h6>`\n- **Quotes**: `<blockquote>` (with `cite`), `<q>`\n- **Time/edits**: `<time>` (with `datetime`), `<del>`, `<ins>` (with `cite`, `datetime`)\n- **SVG**: `<svg>`, `<circle>`, `<rect>`, `<path>`, `<text>`, `<g>`, etc.\n- **MathML**: `<math>`, `<mrow>`, `<mi>`, `<mo>`, `<mfrac>`, etc.\n- **Global attributes**: `dir`, `lang`, `title`, `color`, `fill`, `stroke`, `transform`, and more presentation/styling attributes\n\n## Configuration\n\n### Element Allow-List\n\nOnly allow specific elements. **Elements not in the list — and their entire subtree — are removed.**\n\n```typescript\nsanitize(\"<div><p>text</p><span>more</span></div>\", {\n  sanitizer: { elements: [\"div\", \"p\"], attributes: [] },\n});\n// → '<div><p>text</p></div>'   (span + subtree removed)\n```\n\n### Element Remove-List\n\nBlock specific elements while allowing everything else:\n\n```typescript\nsanitize(\"<div><span>text</span><b>bold</b></div>\", {\n  sanitizer: { removeElements: [\"span\"] },\n  safe: false,\n});\n// → '<div><b>bold</b></div>'\n```\n\n### Replace Elements With Children (Unwrap)\n\nRemove the tag but keep its content — useful when you want the text but not the element:\n\n```typescript\nsanitize(\"<div><b>bold</b> and <i>italic</i></div>\", {\n  sanitizer: {\n    removeElements: [],\n    removeAttributes: [],\n    replaceWithChildrenElements: [\"b\", \"i\"],\n  },\n  safe: false,\n});\n// → '<div>bold and italic</div>'\n```\n\n### Attribute Allow-List\n\nOnly allow specific attributes globally:\n\n```typescript\nsanitize('<div class=\"x\" id=\"y\" title=\"z\">text</div>', {\n  sanitizer: { elements: [\"div\"], attributes: [\"class\"] },\n});\n// → '<div class=\"x\">text</div>'\n```\n\n### Attribute Remove-List\n\nBlock specific attributes:\n\n```typescript\nsanitize('<div class=\"x\" id=\"y\">text</div>', {\n  sanitizer: { removeElements: [], removeAttributes: [\"id\"] },\n  safe: false,\n});\n// → '<div class=\"x\">text</div>'\n```\n\n### Per-Element Attributes\n\nAllow different attributes on different elements:\n\n```typescript\nsanitize(\n  '<a href=\"test.html\" class=\"link\">link</a><div class=\"box\">box</div>',\n  {\n    sanitizer: {\n      elements: [\n        { name: \"a\", namespace: \"http://www.w3.org/1999/xhtml\",\n          attributes: [{ name: \"href\", namespace: null }] },\n        { name: \"div\", namespace: \"http://www.w3.org/1999/xhtml\",\n          attributes: [{ name: \"class\", namespace: null }] },\n      ],\n      attributes: [],\n    },\n  },\n);\n// → '<a href=\"test.html\">link</a><div class=\"box\">box</div>'\n```\n\n### Data Attributes\n\nAllow all `data-*` attributes:\n\n```typescript\nsanitize('<div data-id=\"42\" data-role=\"main\">text</div>', {\n  sanitizer: { elements: [\"div\"], attributes: [], dataAttributes: true },\n});\n// → '<div data-id=\"42\" data-role=\"main\">text</div>'\n```\n\n### Comments\n\nPreserve HTML comments:\n\n```typescript\nsanitize(\"<!-- note --><p>text</p>\", {\n  sanitizer: { elements: [\"p\"], attributes: [], comments: true },\n});\n// → '<!-- note --><p>text</p>'\n```\n\n## Unsafe Mode\n\nSkip the safety checks (allows scripts, event handlers, etc.):\n\n```typescript\nimport { sanitizeUnsafe } from \"@darylcecile/sanitizer\";\n\n// Everything is allowed\nsanitizeUnsafe('<div onclick=\"handler()\">text</div>');\n// → '<div onclick=\"handler()\">text</div>'\n\nsanitizeUnsafe('<script>console.log(\"hi\")</script>');\n// → '<script>console.log(\"hi\")</script>'\n```\n\nYou can also use `sanitize()` with `safe: false`:\n\n```typescript\nsanitize(html, { sanitizer: myConfig, safe: false });\n```\n\n## Sanitizer Class\n\nCreate reusable, mutable sanitizer instances:\n\n```typescript\nimport { Sanitizer, sanitize } from \"@darylcecile/sanitizer\";\n\n// Built-in safe default (W3C spec default allowlist)\nconst safe = new Sanitizer(\"default\");\n// or: new Sanitizer()  — same thing\n\n// Custom config\nconst custom = new Sanitizer({\n  elements: [\"p\", \"b\", \"i\", \"a\"],\n  attributes: [\"href\", \"class\"],\n});\n\n// Modify programmatically\ncustom.allowElement(\"div\");        // add to allow-list → true\ncustom.removeElement(\"i\");         // remove from allow-list → true\ncustom.replaceElementWithChildren(\"b\"); // unwrap <b> → true\ncustom.allowAttribute(\"id\");       // add to allow-list → true\ncustom.removeAttribute(\"class\");   // remove from allow-list → true\ncustom.setComments(true);          // allow comments → true\ncustom.setDataAttributes(true);    // allow data-* attrs → true\ncustom.removeUnsafe();             // strip script-capable config → true\n\n// Use with sanitize()\nconst result = sanitize(html, { sanitizer: custom });\n\n// Inspect current config\nconsole.log(custom.get());\n```\n\n### Modifier Methods\n\nAll modifier methods return `boolean` — `true` if the config was changed, `false` if no change was needed.\n\n| Method | Description |\n|--------|-------------|\n| `allowElement(element)` | Add element to allow-list (or remove from remove-list) |\n| `removeElement(element)` | Remove/block an element |\n| `replaceElementWithChildren(element)` | Unwrap an element (keep children, remove tag) |\n| `allowAttribute(attribute)` | Allow an attribute globally |\n| `removeAttribute(attribute)` | Remove an attribute globally |\n| `setComments(allow)` | Allow/disallow HTML comments |\n| `setDataAttributes(allow)` | Allow/disallow `data-*` attributes |\n| `removeUnsafe()` | Strip all script-executing elements and attributes |\n| `get()` | Returns the current configuration dictionary (sorted) |\n\n## HTML Parser\n\nThe library exposes its HTML parser and serializer for direct AST manipulation — useful for custom transformations, analysis, or any use case beyond sanitization.\n\n```typescript\nimport { parseHTML, serialize, NodeType } from \"@darylcecile/sanitizer\";\n\n// Parse HTML into an AST\nconst doc = parseHTML(\"<div><p>Hello <b>world</b></p></div>\");\n\n// Walk the tree\nfor (const child of doc.children) {\n  if (child.type === NodeType.Element) {\n    console.log(child.tagName); // 'div'\n  }\n}\n\n// Serialize back to HTML\nconsole.log(serialize(doc));\n// → '<div><p>Hello <b>world</b></p></div>'\n```\n\n### Modifying the AST\n\n```typescript\nimport {\n  parseHTML, serialize, createElement, createText,\n  appendChild, removeChild,\n} from \"@darylcecile/sanitizer\";\n\nconst doc = parseHTML(\"<ul><li>First</li></ul>\");\nconst ul = doc.children[0]; // the <ul>\n\nif (ul.type === 1) {\n  const li = createElement(\"li\", \"http://www.w3.org/1999/xhtml\");\n  appendChild(li, createText(\"Second\"));\n  appendChild(ul, li);\n}\n\nconsole.log(serialize(doc));\n// → '<ul><li>First</li><li>Second</li></ul>'\n```\n\nThe parser handles void elements, raw text (`<script>`, `<style>`), auto-closing siblings, comments, doctypes, SVG/MathML namespace switching, and malformed HTML.\n\nSee the [Parser Guide](docs/parser.md) for full documentation.\n\n## API Reference\n\n### `sanitize(html, options?)`\n\nSanitize an HTML string. **Safe by default.**\n\n```typescript\nfunction sanitize(html: string, options?: SanitizeOptions): string;\n```\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `sanitizer` | `Sanitizer \\| SanitizerConfig \\| \"default\"` | `\"default\"` | Sanitizer configuration |\n| `safe` | `boolean` | `true` | Apply safety defaults (strip scripts, event handlers, `javascript:` URLs) |\n\n### `sanitizeUnsafe(html, options?)`\n\nSanitize without safety guarantees. Equivalent to `sanitize(html, { ...options, safe: false })`.\n\n### `new Sanitizer(config?)`\n\nCreate a reusable Sanitizer instance. Accepts a `SanitizerConfig` dictionary or `\"default\"`.\n\n### `SanitizerConfig`\n\n```typescript\ninterface SanitizerConfig {\n  // Element lists (use ONE of elements/removeElements, not both)\n  elements?: SanitizerElementWithAttributes[];  // allow-list\n  removeElements?: SanitizerElement[];           // block-list\n  replaceWithChildrenElements?: SanitizerElement[]; // unwrap list\n\n  // Attribute lists (use ONE of attributes/removeAttributes, not both)\n  attributes?: SanitizerAttribute[];             // allow-list\n  removeAttributes?: SanitizerAttribute[];       // block-list\n\n  comments?: boolean;        // allow HTML comments\n  dataAttributes?: boolean;  // allow data-* attributes (only with attributes allow-list)\n}\n\n// Elements/attributes can be strings (shorthand) or objects (with namespace)\ntype SanitizerElement = string | { name: string; namespace?: string | null };\ntype SanitizerAttribute = string | { name: string; namespace?: string | null };\n```\n\n### Built-in Configs\n\n```typescript\nimport {\n  BUILT_IN_SAFE_DEFAULT_CONFIG,   // W3C allowlist (~100 elements)\n  BUILT_IN_SAFE_BASELINE_CONFIG,  // blocks only script-executing content\n} from \"@darylcecile/sanitizer\";\n```\n\n### `parseHTML(html)`\n\nParse an HTML string into a `DocumentNode` AST.\n\n```typescript\nfunction parseHTML(html: string): DocumentNode;\n```\n\n### `serialize(node)`\n\nSerialize a `DocumentNode` or `ElementNode` back to an HTML string.\n\n```typescript\nfunction serialize(node: DocumentNode | ElementNode): string;\n```\n\nSee the full [API Reference](docs/api-reference.md) and [Parser Guide](docs/parser.md) for all exports including AST types, factory functions, and tree manipulation utilities.\n\n## When Should I Use This vs `sanitize-html`?\n\n[`sanitize-html`](https://github.com/apostrophecms/apostrophe/tree/main/packages/sanitize-html) is a mature, widely-used library with a rich feature set. Choose the right tool for your needs:\n\n| Feature | This library | `sanitize-html` |\n|---------|-------------|-----------------|\n| **Spec** | W3C Sanitizer API | Custom API |\n| **Dependencies** | Zero | htmlparser2, postcss, etc. |\n| **SSR-safe** | ✅ Custom parser | ✅ htmlparser2 |\n| **Disallowed elements** | Removed with subtree (use `replaceWithChildrenElements` to unwrap) | Text preserved by default (discard mode) |\n| **Tag transforms** | Not supported | `transformTags` |\n| **Style filtering** | Not supported | `allowedStyles`, `allowedClasses` |\n| **Scheme filtering** | `javascript:` only (per spec) | `allowedSchemes`, per-tag schemes |\n| **Hostname filtering** | Not supported | `allowedIframeHostnames`, `allowedScriptHostnames` |\n| **Escape mode** | Not supported | `disallowedTagsMode: 'escape'` |\n\n**Use this library** if you want W3C spec compliance, zero dependencies, or a lightweight sanitizer for typical use cases.\n\n**Use `sanitize-html`** if you need advanced features like tag transforms, CSS style filtering, per-tag scheme allow-lists, or iframe hostname restrictions.\n\n## Running Tests\n\n```bash\nbun test\n```\n\nThe test suite includes 188 tests:\n- **73 core tests** — sanitization, config, parser, serializer\n- **115 adapted tests** — ported from `sanitize-html` covering XSS vectors, entity handling, real-world patterns, and edge cases\n","readmeFilename":"README.md"}