Loofah

Loofah is a Ruby library for manipulating and transforming HTML and XML documents and fragments, built on top of Nokogiri. It gives you a small, sharp tool, the scrubber, that walks a parsed document node by node and changes it, plus a set of ready-made scrubbers that sanitize untrusted HTML using an allowlist derived from the html5lib project.

Loofah is the sanitization engine underneath rails-html-sanitizer, which means it quietly cleans user-generated HTML in a very large number of Ruby on Rails applications. You can also use it directly in any Ruby program.

The Problem It Solves

Accepting HTML from users (comments, rich-text editors, imported emails, pasted Word documents) is dangerous: a single <script> tag, onerror= attribute, or javascript: link turns stored content into a cross-site scripting (XSS) attack. Regular expressions cannot reliably remove these, because HTML is not a regular language and browsers are extremely forgiving parsers.

Loofah takes the robust approach:

  1. Parse the input with a real HTML parser (Nokogiri's libxml2-based HTML4 parser, or its Gumbo-based HTML5 parser).
  2. Walk the resulting tree and apply a scrubber to every node.
  3. Serialize the cleaned tree back to HTML or plain text.

Because the scrubber sees the same tree a browser would build, tricks that confuse string-based filters (unclosed tags, odd casing, encoded characters) do not get through.

Key Features

A Short Example

require "loofah"

unsafe_html = "ohai! <div>div is safe</div> <script>but script is not</script>"

doc = Loofah.html5_fragment(unsafe_html).scrub!(:prune)
doc.to_s    # => "ohai! <div>div is safe</div> "
doc.text    # => "ohai! div is safe "

And a custom transformation:

span2div = Loofah::Scrubber.new do |node|
  node.name = "div" if node.name == "span"
end

Loofah.html5_fragment("<span>foo</span><p>bar</p>").scrub!(span2div).to_s
# => "<div>foo</div><p>bar</p>"

How It Works

Loofah's document and fragment classes (Loofah::HTML5::Document, Loofah::HTML4::DocumentFragment, Loofah::XML::Document, and so on) are subclasses of the matching Nokogiri classes, so everything you know about Nokogiri (css, xpath, at_css, node manipulation) still works. Loofah adds:

  • a scrub! method on documents, fragments, nodes, and node sets, which resolves a symbol like :prune to a built-in Loofah::Scrubber and traverses the tree with it;
  • HTML-aware to_s, text, and to_text methods for HTML documents and fragments;
  • removal of comments that sit outside the <html> element when an HTML document is parsed.

The built-in sanitizers check each element name against Loofah::HTML5::SafeList, clean the attributes of allowed elements (removing unknown attributes, unsafe URIs, and unsafe CSS), and then strip, prune, escape, or whitewash everything else. The Internals page walks through this step by step.

When to Use Loofah

Use Loofah when you need to:

  • sanitize untrusted HTML with sensible defaults and little configuration;
  • write your own document transformations (rename tags, rewrite links, remove nodes) with a simple visitor API on top of Nokogiri;
  • turn HTML into readable plain text.

Consider alternatives when:

  • you need fine-grained, declarative control over exactly which tags and attributes are allowed per element. The README points to rgrove/sanitize, another Nokogiri-based library with more configuration options, and to rails/rails-html-sanitizer, which builds on Loofah and adds configurable allowed tags and attributes;
  • you only need to parse or query HTML, in which case plain Nokogiri is enough.

For Active Record integration, the README mentions the separate loofah-activerecord gem.

Project Status

Loofah is actively maintained by Mike Dalessio (the Nokogiri maintainer) and Bryan Helmkamp. The current version in the repository is 2.25.2. It is released under the MIT License. Security issues are handled through GitHub security advisories; recent releases have tightened URI checking against encoded javascript: schemes.

Release notes are in the CHANGELOG. The library was originally called "Dryopteris".

Where to Go Next