Troubleshooting
This page collects surprising behaviors you may run into with Loofah, why they happen (traced to the source code), and what to do about them. Each entry follows the pattern symptom → cause → fix.
Setup and Parsing
NotImplementedError: Loofah::HTML5 is not supported by your version of Nokogiri
Cause: Loofah.html5_support? is false: Nokogiri is 1.14.0 or older, or you are on JRuby, where Nokogiri has no Gumbo parser. Note that exactly 1.14.0 counts as unsupported.
Fix: Upgrade Nokogiri on CRuby. On JRuby, use the html4_* methods. For code that must run on both, branch on Loofah.html5_support?.
My output is wrapped in <html><head></head><body>
Cause: You parsed a snippet with html5_document / html4_document, which always builds a full document, and then called to_s on the document.
Fix: Use html5_fragment for snippets. If you really need a document, serialize just the body: doc.at_css("body").inner_html.
ArgumentError when passing options to a fragment method
Cause: Loofah replaces the fragment classes' parse with parse(tags, encoding = nil) for HTML and parse(tags) for XML. Nokogiri's extra fragment arguments (context, parse options, block) are not accepted.
Fix: Pass only the markup and, for HTML, an encoding name. If you need Nokogiri's fragment options, parse with Nokogiri and copy the nodes into a Loofah document.
ArgumentError: unexpected class: MyFragment
Cause: You subclassed Loofah::HTML5::DocumentFragment or Loofah::HTML4::DocumentFragment. The fragment parse method picks the matching document class by comparing self with exactly those two classes, and raises for anything else.
Fix: Do not subclass the fragment classes; wrap them or add behavior with modules instead.
undefined method 'scrub!' on a node
Cause: The node came from a plain Nokogiri document. scrub! is added to nodes only through the decorators of a Loofah document.
Fix: Parse with a Loofah.*_document or Loofah.*_fragment method.
Sanitizer Output
Script source shows up as text after sanitizing
Cause: :strip (and Loofah::Helpers.sanitize, which uses it) removes unsafe tags but keeps their content. <script>alert('evil')</script> becomes the text alert('evil').
Fix: Use :prune, which removes unsafe elements together with their content.
Links lost their href
There are three possible causes:
- The URI was rejected.
allowed_uri?removeshrefvalues with schemes outside the protocol list (javascript:,vbscript:,file:, custom app schemes,feed:since 2.25.2) anddata:URIs with disallowed media types. Check withLoofah::HTML5::Scrub.allowed_uri?(url). - You used
:whitewash, which removes every attribute. Use:pruneor:stripinstead. - SVG references. On SVG elements such as
use,hrefandxlink:hrefmust point inside the document (#id).
style attributes disappear or are shorter than expected
Cause: scrub_css drops declarations with url(...), disallowed properties (position, z-index, transform, opacity, ...), disallowed functions, and, for background*, border*, margin*, padding*, keywords that are not on the keyword or color list. If nothing is left, the empty style attribute is removed.
Fix: Test the declaration with Loofah::HTML5::Scrub.scrub_css(...) to see what survives. If you need more CSS, sanitize styles yourself in a custom scrubber, or use a sanitizer that lets you configure allowed properties. See URI and CSS Sanitization.
CSS output has no spaces: color:red;font-size:12px;
Cause: scrub_css re-serializes each declaration as name:value; with no space after the colon or between declarations. Whitespace inside values is preserved (collapsed to single spaces).
Fix: None needed; browsers parse it identically. Do not compare sanitized CSS against hand-formatted strings in tests.
Boolean attributes like disabled or checked vanish
Cause: After attribute checks, any remaining attribute (except data-*) whose value is empty or whitespace-only is removed. A boolean attribute written without a value is reported by the parser with an empty value, so it is dropped. This follows from the implementation of scrub_attributes.
Fix: If you need these attributes, write them with a value (disabled="disabled"), or restore them with a custom scrubber.
Forms survive Loofah::Helpers.sanitize
Cause: form, input, button, select, and textarea are on the safelist. The helper removes only <form> elements at the top level of the fragment (XPath ./form), so a form nested in a <div> is kept.
Fix: Remove form elements with a custom pruning scrubber (recipe).
HTML comments become visible text
Cause: With :escape, any node that is not an allowed element, text, or CDATA is replaced by its serialized markup as text. Comments therefore show up as <!-- ... --> text on the page. (With :strip, :prune, and :whitewash, comments are removed.)
Fix: Run :prune first or remove comments with a custom scrubber before :escape.
data-* attributes are kept with dangerous-looking values
Cause: Attributes matching /\Adata-[\w-]+\z/ are kept with no value check, by design.
Fix: Treat data-* values as untrusted in your JavaScript, or remove them with a custom scrubber.
Special characters inside <style> are escaped
Cause: With the HTML4 parser on CRuby, <style> and <script> content becomes CDATA, and Loofah escapes <, >, and & in CDATA to prevent HTML4/HTML5 parser-mismatch attacks. The project's decision record accepts this side effect.
Fix: Use the HTML5 parser, where the issue does not arise in the same way.
Results differ between machines
Cause: Parsing depends on Nokogiri's parser, which differs between CRuby (libxml2 or Gumbo) and JRuby, and between libxml2 versions. The project's tests contain several version-specific expectations, for example for namespaced tags under :whitewash and for comments inside attributes.
Fix: Prefer the HTML5 parser and pin Nokogiri in your lockfile. In tests, compare parsed structures rather than exact strings when possible.
href values have %20 and %22 in them
Cause: On CRuby, after scrubbing, spaces and double quotes in href, action, src, and name (on <a>) are percent-encoded to work around a libxml2 serialization issue (CVE-2018-8048).
Fix: This is intentional. Decode the URL if you need the original form for display.
Built-in Transformations
scrub!("prune") raises Loofah::ScrubberNotFound
Cause: Built-in scrubbers are looked up by symbol in Loofah::Scrubbers::MAP.
Fix: Use scrub!(:prune), or convert with name.to_sym after checking Loofah::Scrubbers.scrubber_symbols.include?(name.to_sym).
:unprintable did not remove a control character
Cause: It removes only U+2028 and U+2029, and only from text and CDATA nodes.
Fix: Write a custom scrubber with your own character class for other characters.
:targetblank skipped some links
Cause: Links without href, and links whose href starts with #, are intentionally skipped (since 2.24.0). Existing target values on other links are replaced.
:double_breakpoint did nothing on my fragment
Cause: In 2.25.2 and earlier, :double_breakpoint left document fragments unchanged. The fix is recorded in the CHANGELOG's unreleased section.
Fix: Upgrade to a release that includes the fix, or parse a document and scrub it.
:double_breakpoint removed my paragraph's class
Cause: New paragraphs are created without attributes. This also happens to paragraphs that are only rebuilt (two non-adjacent <br> children) without being split.
Fix: Copy attributes in your own scrubber, or run :double_breakpoint before adding classes.
Text Output
text returns & and <
Cause: text escapes special characters by default so the result is HTML-safe.
Fix: For non-HTML destinations, use text(encode_special_chars: false) or to_text(encode_special_chars: false). Loofah::Helpers.strip_tags always escapes.
"prefix " + fragment gives text instead of HTML
Cause: Loofah HTML documents and fragments define to_str as an alias of text, so Ruby converts them implicitly to their escaped text.
Fix: Call to_s explicitly: "prefix " + fragment.to_s.
text on a document ignores the <title>
Cause: Document text reads from /html/body only.
Fix: Read doc.at_css("title")&.text separately.
to_text puts table cells on separate lines
Cause: td, th, and tr are in Loofah::Elements::LOOSE_BLOCK_LEVEL, so each becomes its own line.
Fix: For tabular text, walk the table with Nokogiri yourself.
Getting Help
- Issues: github.com/flavorjones/loofah/issues
- Mailing list:
[email protected] - Security reports: through GitHub security advisories, as described in SECURITY.md. Do not discuss vulnerabilities on the public mailing list.