rails-html-sanitizer 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rails-html-sanitizer might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +7 -6
- data/lib/rails/html/sanitizer.rb +14 -7
- data/lib/rails/html/sanitizer/version.rb +1 -1
- data/lib/rails/html/scrubbers.rb +53 -3
- data/test/sanitizer_test.rb +43 -14
- data/test/scrubbers_test.rb +15 -1
- metadata +4 -5
- data/LICENSE.txt +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44e7ba72869ce5a5b6aa4f202dced7073ef94b72
|
4
|
+
data.tar.gz: b5410baf4f05cc97449852a7b3b4e36774a9942d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ea541f36dbc6de129d6bd889a8b198bf5e4805a578e204dc21dfc01c29551f869e064b7c315a9cd7e2732cef58ad820851684df55568922135dd4866f5d8ff7
|
7
|
+
data.tar.gz: ff206594a72e31e5504f935b437ea105327f7540d5d1a8530f202d35419c278f9d78a5e7f413e86c519bf7bf12b54341aadb7591cfa719f35ea1693d4d4998b2
|
data/README.md
CHANGED
@@ -132,10 +132,11 @@ The `node` argument passed to some methods in a custom scrubber is an instance o
|
|
132
132
|
- [`Nokogiri::XML::Node`](http://nokogiri.org/Nokogiri/XML/Node.html)
|
133
133
|
- [Nokogiri](http://nokogiri.org)
|
134
134
|
|
135
|
-
## Contributing
|
135
|
+
## Contributing to Rails Html Sanitizers
|
136
136
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
137
|
+
Rails Html Sanitizers is work of many contributors. You're encouraged to submit pull requests, propose features and discuss issues.
|
138
|
+
|
139
|
+
See [CONTRIBUTING](CONTRIBUTING.md).
|
140
|
+
|
141
|
+
## License
|
142
|
+
Rails Html Sanitizers is released under the [MIT License](MIT-LICENSE).
|
data/lib/rails/html/sanitizer.rb
CHANGED
@@ -13,6 +13,10 @@ module Rails
|
|
13
13
|
node.xpath(*xpaths).remove
|
14
14
|
node
|
15
15
|
end
|
16
|
+
|
17
|
+
def properly_encode(fragment, options)
|
18
|
+
fragment.xml? ? fragment.to_xml(options) : fragment.to_html(options)
|
19
|
+
end
|
16
20
|
end
|
17
21
|
|
18
22
|
# === Rails::Html::FullSanitizer
|
@@ -26,9 +30,12 @@ module Rails
|
|
26
30
|
return unless html
|
27
31
|
return html if html.empty?
|
28
32
|
|
29
|
-
Loofah.fragment(html)
|
30
|
-
|
31
|
-
|
33
|
+
loofah_fragment = Loofah.fragment(html)
|
34
|
+
|
35
|
+
remove_xpaths(loofah_fragment, XPATHS_TO_REMOVE)
|
36
|
+
loofah_fragment.scrub!(TextOnlyScrubber.new)
|
37
|
+
|
38
|
+
properly_encode(loofah_fragment, encoding: 'UTF-8')
|
32
39
|
end
|
33
40
|
end
|
34
41
|
|
@@ -97,6 +104,10 @@ module Rails
|
|
97
104
|
attr_accessor :allowed_tags
|
98
105
|
attr_accessor :allowed_attributes
|
99
106
|
end
|
107
|
+
self.allowed_tags = Set.new(%w(strong em b i p code pre tt samp kbd var sub
|
108
|
+
sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dl dt dd abbr
|
109
|
+
acronym a img blockquote del ins))
|
110
|
+
self.allowed_attributes = Set.new(%w(href src width height alt cite datetime title class name xml:lang abbr))
|
100
111
|
|
101
112
|
def initialize
|
102
113
|
@permit_scrubber = PermitScrubber.new
|
@@ -136,10 +147,6 @@ module Rails
|
|
136
147
|
def allowed_attributes(options)
|
137
148
|
options[:attributes] || self.class.allowed_attributes
|
138
149
|
end
|
139
|
-
|
140
|
-
def properly_encode(fragment, options)
|
141
|
-
fragment.xml? ? fragment.to_xml(options) : fragment.to_html(options)
|
142
|
-
end
|
143
150
|
end
|
144
151
|
end
|
145
152
|
end
|
data/lib/rails/html/scrubbers.rb
CHANGED
@@ -60,6 +60,11 @@ module Rails
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def scrub(node)
|
63
|
+
if node.cdata?
|
64
|
+
text = node.document.create_text_node node.text
|
65
|
+
node.replace text
|
66
|
+
return CONTINUE
|
67
|
+
end
|
63
68
|
return CONTINUE if skip_node?(node)
|
64
69
|
|
65
70
|
unless keep_node?(node)
|
@@ -76,7 +81,7 @@ module Rails
|
|
76
81
|
end
|
77
82
|
|
78
83
|
def skip_node?(node)
|
79
|
-
node.text?
|
84
|
+
node.text?
|
80
85
|
end
|
81
86
|
|
82
87
|
def scrub_attribute?(name)
|
@@ -100,6 +105,7 @@ module Rails
|
|
100
105
|
if @attributes
|
101
106
|
node.attribute_nodes.each do |attr|
|
102
107
|
attr.remove if scrub_attribute?(attr.name)
|
108
|
+
scrub_attribute(node, attr)
|
103
109
|
end
|
104
110
|
|
105
111
|
scrub_css_attribute(node)
|
@@ -123,6 +129,30 @@ module Rails
|
|
123
129
|
end
|
124
130
|
var
|
125
131
|
end
|
132
|
+
|
133
|
+
def scrub_attribute(node, attr_node)
|
134
|
+
attr_name = if attr_node.namespace
|
135
|
+
"#{attr_node.namespace.prefix}:#{attr_node.node_name}"
|
136
|
+
else
|
137
|
+
attr_node.node_name
|
138
|
+
end
|
139
|
+
|
140
|
+
if Loofah::HTML5::WhiteList::ATTR_VAL_IS_URI.include?(attr_name)
|
141
|
+
# this block lifted nearly verbatim from HTML5 sanitization
|
142
|
+
val_unescaped = CGI.unescapeHTML(attr_node.value).gsub(Loofah::HTML5::Scrub::CONTROL_CHARACTERS,'').downcase
|
143
|
+
if val_unescaped =~ /^[a-z0-9][-+.a-z0-9]*:/ && ! Loofah::HTML5::WhiteList::ALLOWED_PROTOCOLS.include?(val_unescaped.split(Loofah::HTML5::WhiteList::PROTOCOL_SEPARATOR)[0])
|
144
|
+
attr_node.remove
|
145
|
+
end
|
146
|
+
end
|
147
|
+
if Loofah::HTML5::WhiteList::SVG_ATTR_VAL_ALLOWS_REF.include?(attr_name)
|
148
|
+
attr_node.value = attr_node.value.gsub(/url\s*\(\s*[^#\s][^)]+?\)/m, ' ') if attr_node.value
|
149
|
+
end
|
150
|
+
if Loofah::HTML5::WhiteList::SVG_ALLOW_LOCAL_HREF.include?(node.name) && attr_name == 'xlink:href' && attr_node.value =~ /^\s*[^#\s].*/m
|
151
|
+
attr_node.remove
|
152
|
+
end
|
153
|
+
|
154
|
+
node.remove_attribute(attr_node.name) if attr_name == 'src' && attr_node.value !~ /[^[:space:]]/
|
155
|
+
end
|
126
156
|
end
|
127
157
|
|
128
158
|
# === Rails::Html::TargetScrubber
|
@@ -137,11 +167,31 @@ module Rails
|
|
137
167
|
# If set, attributes included will be removed.
|
138
168
|
class TargetScrubber < PermitScrubber
|
139
169
|
def allowed_node?(node)
|
140
|
-
|
170
|
+
!super
|
141
171
|
end
|
142
172
|
|
143
173
|
def scrub_attribute?(name)
|
144
|
-
|
174
|
+
!super
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# === Rails::Html::TextOnlyScrubber
|
179
|
+
#
|
180
|
+
# Rails::Html::TextOnlyScrubber allows you to permit text nodes.
|
181
|
+
#
|
182
|
+
# Unallowed elements will be stripped, i.e. element is removed but its subtree kept.
|
183
|
+
class TextOnlyScrubber < Loofah::Scrubber
|
184
|
+
def initialize
|
185
|
+
@direction = :bottom_up
|
186
|
+
end
|
187
|
+
|
188
|
+
def scrub(node)
|
189
|
+
if node.text?
|
190
|
+
CONTINUE
|
191
|
+
else
|
192
|
+
node.before node.children
|
193
|
+
node.remove
|
194
|
+
end
|
145
195
|
end
|
146
196
|
end
|
147
197
|
end
|
data/test/sanitizer_test.rb
CHANGED
@@ -11,6 +11,16 @@ class SanitizersTest < Minitest::Test
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
def test_sanitize_nested_script
|
15
|
+
sanitizer = Rails::Html::WhiteListSanitizer.new
|
16
|
+
assert_equal '<script>alert("XSS");</script>', sanitizer.sanitize('<script><script></script>alert("XSS");<script><</script>/</script><script>script></script>', tags: %w(em))
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_sanitize_nested_script_in_style
|
20
|
+
sanitizer = Rails::Html::WhiteListSanitizer.new
|
21
|
+
assert_equal '<script>alert("XSS");</script>', sanitizer.sanitize('<style><script></style>alert("XSS");<style><</style>/</style><style>script></style>', tags: %w(em))
|
22
|
+
end
|
23
|
+
|
14
24
|
class XpathRemovalTestSanitizer < Rails::Html::Sanitizer
|
15
25
|
def sanitize(html, options = {})
|
16
26
|
fragment = Loofah.fragment(html)
|
@@ -104,9 +114,12 @@ class SanitizersTest < Minitest::Test
|
|
104
114
|
assert_equal "Frozen string with no tags", full_sanitize("Frozen string with no tags".freeze)
|
105
115
|
end
|
106
116
|
|
107
|
-
def
|
117
|
+
def test_full_sanitize_respect_html_escaping_of_the_given_string
|
118
|
+
assert_equal 'test\r\nstring', full_sanitize('test\r\nstring')
|
108
119
|
assert_equal '&', full_sanitize('&')
|
109
|
-
assert_equal '&', full_sanitize('&'
|
120
|
+
assert_equal '&', full_sanitize('&')
|
121
|
+
assert_equal '&amp;', full_sanitize('&amp;')
|
122
|
+
assert_equal 'omg <script>BOM</script>', full_sanitize('omg <script>BOM</script>')
|
110
123
|
end
|
111
124
|
|
112
125
|
def test_strip_links_with_tags_in_tags
|
@@ -152,7 +165,7 @@ class SanitizersTest < Minitest::Test
|
|
152
165
|
end
|
153
166
|
|
154
167
|
def test_sanitize_script
|
155
|
-
assert_sanitized "a b c<script language=\"Javascript\">blah blah blah</script>d e f", "a b
|
168
|
+
assert_sanitized "a b c<script language=\"Javascript\">blah blah blah</script>d e f", "a b cblah blah blahd e f"
|
156
169
|
end
|
157
170
|
|
158
171
|
def test_sanitize_js_handlers
|
@@ -173,17 +186,23 @@ class SanitizersTest < Minitest::Test
|
|
173
186
|
tags = Loofah::HTML5::WhiteList::ALLOWED_ELEMENTS - %w(script form)
|
174
187
|
tags.each do |tag_name|
|
175
188
|
define_method "test_should_allow_#{tag_name}_tag" do
|
176
|
-
|
189
|
+
scope_allowed_tags(tags) do
|
190
|
+
assert_sanitized "start <#{tag_name} title=\"1\" onclick=\"foo\">foo <bad>bar</bad> baz</#{tag_name}> end", %(start <#{tag_name} title="1">foo bar baz</#{tag_name}> end)
|
191
|
+
end
|
177
192
|
end
|
178
193
|
end
|
179
194
|
|
180
195
|
def test_should_allow_anchors
|
181
|
-
assert_sanitized %(<a href="foo" onclick="bar"><script>baz</script></a>), %(<a href=\"foo\"
|
196
|
+
assert_sanitized %(<a href="foo" onclick="bar"><script>baz</script></a>), %(<a href=\"foo\">baz</a>)
|
182
197
|
end
|
183
198
|
|
184
199
|
def test_video_poster_sanitization
|
185
|
-
|
186
|
-
|
200
|
+
scope_allowed_tags(%w(video)) do
|
201
|
+
scope_allowed_attributes %w(src poster) do
|
202
|
+
assert_sanitized %(<video src="videofile.ogg" autoplay poster="posterimage.jpg"></video>), %(<video src="videofile.ogg" poster="posterimage.jpg"></video>)
|
203
|
+
assert_sanitized %(<video src="videofile.ogg" poster=javascript:alert(1)></video>), %(<video src="videofile.ogg"></video>)
|
204
|
+
end
|
205
|
+
end
|
187
206
|
end
|
188
207
|
|
189
208
|
# RFC 3986, sec 4.2
|
@@ -309,7 +328,7 @@ class SanitizersTest < Minitest::Test
|
|
309
328
|
end
|
310
329
|
|
311
330
|
def test_should_not_fall_for_xss_image_hack_with_uppercase_tags
|
312
|
-
assert_sanitized %(<IMG """><SCRIPT>alert("XSS")</SCRIPT>">),
|
331
|
+
assert_sanitized %(<IMG """><SCRIPT>alert("XSS")</SCRIPT>">), %(<img>alert("XSS")">)
|
313
332
|
end
|
314
333
|
|
315
334
|
[%(<IMG SRC="javascript:alert('XSS');">),
|
@@ -326,8 +345,8 @@ class SanitizersTest < Minitest::Test
|
|
326
345
|
%(<IMG SRC="jav
ascript:alert('XSS');">),
|
327
346
|
%(<IMG SRC="  javascript:alert('XSS');">),
|
328
347
|
%(<IMG SRC="javascript:alert('XSS');">),
|
329
|
-
%(<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>)].
|
330
|
-
define_method "test_should_not_fall_for_xss_image_hack_#{
|
348
|
+
%(<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>)].each do |img_hack|
|
349
|
+
define_method "test_should_not_fall_for_xss_image_hack_#{img_hack}" do
|
331
350
|
assert_sanitized img_hack, "<img>"
|
332
351
|
end
|
333
352
|
end
|
@@ -453,6 +472,16 @@ class SanitizersTest < Minitest::Test
|
|
453
472
|
end
|
454
473
|
end
|
455
474
|
|
475
|
+
def test_sanitize_data_attributes
|
476
|
+
assert_sanitized %(<a href="/blah" data-method="post">foo</a>), %(<a href="/blah">foo</a>)
|
477
|
+
assert_sanitized %(<a data-remote="true" data-type="script" data-method="get" data-cross-domain="true" href="attack.js">Launch the missiles</a>), %(<a href="attack.js">Launch the missiles</a>)
|
478
|
+
end
|
479
|
+
|
480
|
+
def test_allow_data_attribute_if_requested
|
481
|
+
text = %(<a data-foo="foo">foo</a>)
|
482
|
+
assert_equal %(<a data-foo="foo">foo</a>), white_list_sanitize(text, attributes: ['data-foo'])
|
483
|
+
end
|
484
|
+
|
456
485
|
protected
|
457
486
|
|
458
487
|
def xpath_sanitize(input, options = {})
|
@@ -484,18 +513,18 @@ protected
|
|
484
513
|
end
|
485
514
|
|
486
515
|
def scope_allowed_tags(tags)
|
516
|
+
old_tags = Rails::Html::WhiteListSanitizer.allowed_tags
|
487
517
|
Rails::Html::WhiteListSanitizer.allowed_tags = tags
|
488
518
|
yield Rails::Html::WhiteListSanitizer.new
|
489
|
-
|
490
519
|
ensure
|
491
|
-
Rails::Html::WhiteListSanitizer.allowed_tags =
|
520
|
+
Rails::Html::WhiteListSanitizer.allowed_tags = old_tags
|
492
521
|
end
|
493
522
|
|
494
523
|
def scope_allowed_attributes(attributes)
|
524
|
+
old_attributes = Rails::Html::WhiteListSanitizer.allowed_attributes
|
495
525
|
Rails::Html::WhiteListSanitizer.allowed_attributes = attributes
|
496
526
|
yield Rails::Html::WhiteListSanitizer.new
|
497
|
-
|
498
527
|
ensure
|
499
|
-
Rails::Html::WhiteListSanitizer.allowed_attributes =
|
528
|
+
Rails::Html::WhiteListSanitizer.allowed_attributes = old_attributes
|
500
529
|
end
|
501
530
|
end
|
data/test/scrubbers_test.rb
CHANGED
@@ -143,6 +143,20 @@ class TargetScrubberTest < ScrubberTest
|
|
143
143
|
end
|
144
144
|
end
|
145
145
|
|
146
|
+
class TextOnlyScrubberTest < ScrubberTest
|
147
|
+
def setup
|
148
|
+
@scrubber = Rails::Html::TextOnlyScrubber.new
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_removes_all_tags_and_keep_the_content
|
152
|
+
assert_scrubbed '<tag>hello</tag>', 'hello'
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_skips_text_nodes
|
156
|
+
assert_node_skipped('some text')
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
146
160
|
class ReturningStopFromScrubNodeTest < ScrubberTest
|
147
161
|
class ScrubStopper < Rails::Html::PermitScrubber
|
148
162
|
def scrub_node(node)
|
@@ -157,4 +171,4 @@ class ReturningStopFromScrubNodeTest < ScrubberTest
|
|
157
171
|
def test_returns_stop_from_scrub_if_scrub_node_does
|
158
172
|
assert_scrub_stopped '<script>remove me</script>'
|
159
173
|
end
|
160
|
-
end
|
174
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails-html-sanitizer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rafael Mendonça França
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2016-01-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: loofah
|
@@ -90,7 +90,6 @@ extensions: []
|
|
90
90
|
extra_rdoc_files: []
|
91
91
|
files:
|
92
92
|
- CHANGELOG.md
|
93
|
-
- LICENSE.txt
|
94
93
|
- README.md
|
95
94
|
- lib/rails-html-sanitizer.rb
|
96
95
|
- lib/rails/html/sanitizer.rb
|
@@ -98,7 +97,7 @@ files:
|
|
98
97
|
- lib/rails/html/scrubbers.rb
|
99
98
|
- test/sanitizer_test.rb
|
100
99
|
- test/scrubbers_test.rb
|
101
|
-
homepage: https://github.com/
|
100
|
+
homepage: https://github.com/rails/rails-html-sanitizer
|
102
101
|
licenses:
|
103
102
|
- MIT
|
104
103
|
metadata: {}
|
@@ -118,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
118
117
|
version: '0'
|
119
118
|
requirements: []
|
120
119
|
rubyforge_project:
|
121
|
-
rubygems_version: 2.
|
120
|
+
rubygems_version: 2.5.1
|
122
121
|
signing_key:
|
123
122
|
specification_version: 4
|
124
123
|
summary: This gem is responsible to sanitize HTML fragments in Rails applications.
|
data/LICENSE.txt
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
Copyright (c) 2013 Rafael Mendonça França, Kasper Timm Hansen
|
2
|
-
|
3
|
-
MIT License
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
-
a copy of this software and associated documentation files (the
|
7
|
-
"Software"), to deal in the Software without restriction, including
|
8
|
-
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
-
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
-
permit persons to whom the Software is furnished to do so, subject to
|
11
|
-
the following conditions:
|
12
|
-
|
13
|
-
The above copyright notice and this permission notice shall be
|
14
|
-
included in all copies or substantial portions of the Software.
|
15
|
-
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
-
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
-
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
-
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|