index.md (3564B)
1 --- 2 title: "HTML Sanitization In Rails That Actually Works" 3 date: 2009-11-23T00:00:00+00:00 4 draft: false 5 canonical_url: https://www.viget.com/articles/html-sanitization-in-rails-that-actually-works/ 6 --- 7 8 Assuming you don't want to simply escape everything, sanitizing user 9 input is one of the relative weak points of the Rails framework. On 10 [SpeakerRate](http://speakerrate.com/), where users can use 11 [Markdown](http://daringfireball.net/projects/markdown/) to format 12 comments and descriptions, we've run up against some of the limitations 13 of Rails' built-in sanitization features, so we decided to dig in and 14 fix it ourselves. 15 16 In creating our own sanitizer, our goals were threefold: we want to 17 **let a subset of HTML in**. As the [Markdown 18 documentation](http://daringfireball.net/projects/markdown/syntax#html) 19 clearly states, "for any markup that is not covered by Markdown's 20 syntax, you simply use HTML itself." In keeping with the Markdown 21 philosophy, we can't simply strip all HTML from incoming comments, so 22 the included 23 [HTML::WhiteListSanitizer](https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb#LID60) 24 is the obvious starting point. 25 26 Additionally, we want to **escape, rather than remove, non-approved 27 tags**, since some commenters want to discuss the merits of, say, 28 [`<h2 class="h2">`](http://speakerrate.com/talks/1698-object-oriented-css#c797). 29 Contrary to its documentation, WhiteListSanitizer simply removes all 30 non-whitelisted tags. Someone opened a 31 [ticket](https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/916) 32 about this issue in August of 2008 with an included patch, but the 33 ticket was marked as resolved without ever applying it. Probably for the 34 best, as the patch introduces a new bug. 35 36 Finally, we want to **escape unclosed tags even if they belong to the 37 whitelist**. An unclosed `<strong>` tag can wreak havoc on the rest of a 38 page, not to mention what a `<div>` can do. Self-closing tags are okay. 39 40 With these requirements in mind, we subclassed HTML::WhiteListSanitizer 41 and fixed it up. Introducing, then: 42 43 {{<dither jason_statham.jpg "" "inline">}}Jason Statham stands in a sharp suit, glaring intently under the midday sun.{{</dither>}} 44 45 [**HTML::StathamSanitizer**](https://gist.github.com/241114). 46 User-generated markup, you're on notice: this sanitizer will take its 47 shirt off and use it to kick your ass. At this point, I've written more 48 about the code than code itself, so without further ado: 49 50 ```ruby 51 module HTML 52 class StathamSanitizer < WhiteListSanitizer 53 54 protected 55 56 def tokenize(text, options) 57 super.map do |token| 58 if token.is_a?(HTML::Tag) && options[:parent].include?(token.name) 59 token.to_s.gsub(/</, "<") 60 else 61 token 62 end 63 end 64 end 65 66 def process_node(node, result, options) 67 result << case node 68 when HTML::Tag 69 if node.closing == :close && options[:parent].first == node.name 70 options[:parent].shift 71 elsif node.closing != :self 72 options[:parent].unshift node.name 73 end 74 75 process_attributes_for node, options 76 77 if options[:tags].include?(node.name) 78 node 79 else 80 bad_tags.include?(node.name) ? nil : node.to_s.gsub(/</, "<") 81 end 82 else 83 bad_tags.include?(options[:parent].first) ? nil : node.to_s.gsub(/</, "<") 84 end 85 end 86 end 87 end 88 ``` 89 90 As always, download and fork [at the 91 'hub](https://gist.github.com/241114).