rails-html-sanitizer 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.
|