The problem
Every time you render user-generated content — comments, rich text, profile bios — you open a door. Sanitization is the standard fix, but preserving safe HTML and emitting plain text are different jobs with different tradeoffs.
Purifai handles the second job: a zero-dependency, TypeScript-native sanitizer that strips markup and runs in Node.js, browsers, edge runtimes, and workers without a DOM shim.
Verification
The benchmark inserts each sanitizer's output into a real DOM (jsdom), then serializes and re-parses it — the round trip where mutation XSS lives — and scores security and content fidelity separately:
| Library | Category | Security | Text kept | Markup kept |
|---|---|---|---|---|
| Purifai | strip-to-text | 100% | 100% | 0% (by design) |
| DOMPurify | preserve-html | 100% | 100% | 100% |
| sanitize-html | preserve-html | 100% | 100% | 100% |
| xss | preserve-html | 100% | 100% | 100% |
84 attack vectors (OWASP, PortSwigger, cure53 mXSS/namespace-confusion corpora).
All four libraries block the full suite. Purifai's distinction is its strip-to-text model: it keeps the text, discards markup by design, ships at 4.1 KB gzipped, and does not need a DOM at runtime.
Using it
npm install purifai
import { Purifai } from 'purifai'; const clean = Purifai.sanitize('<script>alert("xss")</script>Hello World'); // -> "Hello World" const safe = Purifai.sanitize(userInput, { maxLength: 10000, allowBasicHtml: false, aggressiveMode: true, });
No DOM shim, no external dependencies — a function that takes dirty HTML and returns safe output.
Design and testing
Purifai normalizes encoded input, removes dangerous protocols and executable markup, then iterates its filters to a fixpoint. The test suite combines curated attack vectors with fuzzing against six DOM-based safety invariants, including a serialize-and-reparse check for mutation XSS.
Idempotence matters for a delete-based sanitizer: running the output through the pipeline again must not reveal a new match or change the result. That invariant is tested alongside security and content retention.
Choosing strip-to-text
Purifai is useful when markup is not part of the product requirement. Search indexes, notification previews, profile summaries, audit logs, AI inputs, CSV exports, and compact cards usually need the words—not the original tags and attributes.
Removing markup makes the output contract easy to explain: text should survive; executable structure should not. That is different from a rich-text editor, where headings, links, tables, and inline emphasis must remain. For those surfaces, a mature preserve-HTML sanitizer is the better tool.
The distinction is important because the two categories should not be compared only by how many attack strings produce an empty result. A sanitizer that keeps safe formatting is doing more work than one that intentionally emits text.
Security boundaries
Sanitization is one layer, not an application security policy. Purifai does not decide who may create content, where that content may be stored, or which users may retrieve it. It also does not replace output encoding when a value moves into JavaScript, CSS, a URL, SQL, or another interpreter.
A strong deployment still combines validation, contextual encoding, a Content Security Policy where applicable, dependency review, and tests at the actual render boundary. Purifai narrows one specific problem: converting untrusted markup into text without requiring a DOM.
Operational use
The companion analyze() and isDangerous() APIs support logging and review
flows, while batch helpers cover imports and pipelines. Detection should inform
an operational decision rather than silently becoming one: a product may
sanitize ordinary content, quarantine repeated hostile input, or retain decoded
evidence for an abuse team.
Inputs should still have a practical size limit, and changes to sanitization rules should be rolled out against representative content. Security tests prove the dangerous structure is gone; fidelity tests prove the useful text did not disappear with it.
