sanitize-html-rs

A Rust library for sanitizing untrusted HTML using rule-based allowlists of elements, attributes, and URL patterns.

Library
Cargo
v0.10.0
16stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
21/100Needs Attention
Development Activity0
Maintenance0
Community12
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
63/100Good
Architecture78
Code Quality82
Innovation58
Learning Curve35

sanitize_html is a Rust library for sanitizing HTML fragments using an explicit, rule-based allowlist model. Rather than trying to blocklist dangerous markup, it defines exactly which elements and attributes survive sanitization, so anything not explicitly permitted, scripts, event handlers, iframes, dangerous URL schemes, is stripped, converted to plain text, or replaced with a space. The library ships five predefined rule sets (DEFAULT, BASIC, RESTRICTED, RELAXED, UNTRUSTED) modeled after the popular Ruby “sanitize” gem, and exposes a builder API for constructing custom rules from scratch.

Under the hood, input is parsed into a DOM using html5ever (the same HTML5 parser used by Servo), walked recursively, and each element is either kept (with its attributes filtered through per-attribute value patterns), deleted along with its children, replaced by its children only, replaced with whitespace, or renamed. Patterns for validating attribute values, such as restricting href or src to safe URL schemes, are expressed as composable predicates supporting &, |, and ! operators, making it straightforward to build precise validation rules without hand-rolled string parsing.

What You Get

  • Five predefined rule sets, DEFAULT, BASIC, RESTRICTED, RELAXED, and UNTRUSTED, covering common sanitization strictness levels out of the box
  • A builder-style Rules/Element API for defining fully custom allowlists of elements, attributes, and mandatory attributes
  • Composable Pattern predicates (&, |, !) for validating attribute values, including built-in href/src URL scheme checks
  • HTML5-spec-compliant parsing via html5ever, so sanitization behaves like a real browser parser rather than a naive regex or string scan

Common Use Cases

  • User-generated comments - a forum or blog stores raw HTML from commenters and sanitizes it with the DEFAULT or RESTRICTED rule set before rendering, stripping scripts and dangerous markup while keeping plain text intact.
  • Rich-text editor output - an application accepts HTML produced by a WYSIWYG editor and applies the RELAXED or BASIC rule set to keep formatting, links, and images while rejecting inline event handlers and unsafe URL schemes.
  • Untrusted third-party content - a service embeds HTML snippets sourced from external feeds or APIs and applies the UNTRUSTED rule set, which strips everything except simple emphasis and links, forcing every link to open safely with rel=“noreferrer noopener” target=“_blank”.
  • Custom domain-specific markup - a team building a CMS defines its own Rules set with a bespoke allowlist of elements and attribute patterns tailored to their editor’s exact output.

Under The Hood

Architecture The crate is organized as a small three-stage pipeline: parse::parse_dom builds an rcdom::RcDom tree from raw HTML via html5ever’s tree-builder trait implementation, sanitize::sanitize_dom walks that tree recursively applying rules::Rules to decide what to keep, and parse::unparse_document serializes the surviving tree back to HTML/text, the whole flow is orchestrated by two thin public entry points (sanitize_bytes/sanitize_str in lib.rs) that compose these stages and map errors into a single SanitizeError. The core abstraction is the ElementAction enum in sanitize.rs (Keep/Delete/Space/Elide/Rename), computed once per element against the Rules struct’s four collections (allowed_elements, delete_elements, space_elements, rename_elements), which keeps the traversal logic (clean_node/clean_nodes) a pure function of node plus rules with no external state or dependency injection. Data flows one-way from bytes to DOM to filtered DOM to bytes, and rcdom.rs is a self-contained, vendored DOM implementation rather than something with churn risk from the rest of the crate; if the core ElementAction decision model changed, the change stays localized to element_action and the corresponding match arm in clean_node, which is evidence of clean separation of concerns for a project this size.

Tech Stack This is pure Rust on the 2024 edition with exactly two runtime dependencies declared in Cargo.toml: regex (for Pattern-based href/src scheme validation) and html5ever (Mozilla/Servo’s spec-compliant HTML5 parser and tokenizer), used both for parsing input and for its serialization traits when unparsing. There is no web framework, ORM, or database involved, this is a leaf library rather than an application, and no async runtime is used since sanitization is a synchronous, in-memory transformation. CI runs across three GitHub Actions workflows: a build/test matrix across Linux, macOS, and Windows on stable Rust, a coverage workflow using cargo-tarpaulin uploaded to Codecov, and a dedicated style/formatting check, with the crate published to crates.io and documented on docs.rs.

Code Quality Tests live in a dedicated tests module exercising all five predefined rule sets plus custom Rules/Element construction against shared HTML fixtures, and the predefined-rules module additionally carries an inline test module asserting each rule set’s element/space/delete-list cardinality doesn’t silently drift; runnable doc examples embedded in the public API documentation are also exercised as part of the test suite. Error handling is explicit and typed: the public sanitize functions return a Result wrapping a dedicated SanitizeError with proper Display and Error::source implementations rather than panicking or silently swallowing failures, though internal helpers lean on a boxed dynamic error trait object rather than a purpose-built enum, which is a reasonable tradeoff at this scope. Naming is consistent and idiomatic, the public API is small and documented with missing-docs enforcement at compile time, and CI runs a dedicated style-check workflow alongside the multi-OS test matrix and coverage reporting.

What Makes It Unique The library’s distinguishing technical choice is building sanitization on a real HTML5 parser with a full DOM walk rather than string or regex scrubbing, which sidesteps a well-known class of sanitizer bugs where a browser and a naive sanitizer disagree on how malformed markup nests. Its Pattern type turns attribute-value validation into composable boolean predicates with overloaded AND/OR/NOT operators instead of a fixed enum of validators, a modest but genuinely elegant API detail that lets consumers compose scheme and format checks without extending the crate itself. That said, the overall approach, porting allowlist rules from Ruby’s well-known sanitize gem, is a proven, established pattern rather than something new to the ecosystem; other DOM-based sanitizers take a similar parse-then-filter approach.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search