sanitize 7.0.0 → 7.0.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +10 -1
- data/lib/sanitize/css.rb +156 -61
- data/lib/sanitize/transformers/clean_element.rb +0 -1
- data/lib/sanitize/version.rb +1 -1
- metadata +6 -18
- data/test/common.rb +0 -4
- data/test/test_clean_comment.rb +0 -48
- data/test/test_clean_css.rb +0 -68
- data/test/test_clean_doctype.rb +0 -72
- data/test/test_clean_element.rb +0 -537
- data/test/test_config.rb +0 -66
- data/test/test_malicious_css.rb +0 -56
- data/test/test_malicious_html.rb +0 -340
- data/test/test_parser.rb +0 -76
- data/test/test_sanitize.rb +0 -198
- data/test/test_sanitize_css.rb +0 -468
- data/test/test_transformers.rb +0 -228
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 694fdddd1e40857325f7fac61ee5679e6a9fca926413c1b035062ab7449b92c7
|
|
4
|
+
data.tar.gz: 426f1b4ece2fe0514139b661ba77ca7b9a6196481a0759a3ac81a9f446996e3d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e58a303bb8a1285fbc75ab93acfd896444dc125d3b9b13884868366720bc451e5c69409b9a852014394604bc932e0ffbeeb7c1b7192eb3cd0a673ec10a79df2
|
|
7
|
+
data.tar.gz: 8a4b8ca81da4bc4bb070941fab4b08f8e6d36a10487d0d90820e762155fe5717acfbc919c2676650d490cd7d9cac82f7a2f4ec21bc0f373f837938a23a670d6d
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Sanitize are documented in this file. The format (since version 7.0.0) is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
4
|
|
|
5
|
+
## 7.0.1 (2026-10-21)
|
|
6
|
+
|
|
7
|
+
### Security
|
|
8
|
+
|
|
9
|
+
- CSS properties that contain a `{}` block anywhere in their value are now removed. Browsers that support [CSS nesting](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting) reparse an invalid declaration containing a `{}` block as a nested style rule, so a value like `color:hover{position:fixed}` could previously be used to smuggle disallowed properties and URLs past Sanitize in a stylesheet or `<style>` element. (GHSA-wgw9-qhqg-8hv5)
|
|
10
|
+
|
|
11
|
+
- The contents of at-rules such as `@media` and `@supports` that appear in a list of declarations (inside a style rule, inside an at-rule like `@font-face`, or in CSS sanitized with `Sanitize::CSS.properties`) are now sanitized as declarations rather than as rules, matching how browsers that support CSS nesting parse them. Previously, a stylesheet like `p{@media all{position:fixed;a{}}}` could be used to smuggle disallowed properties and URLs past Sanitize, since Sanitize treated `position:fixed;a` as a style rule's selector. The same was true of CSS sanitized with `Sanitize::CSS.properties` if the result was later embedded in a style rule. Style rules nested inside these at-rules are now removed, just as style rules nested directly inside other style rules already were. (GHSA-wgw9-qhqg-8hv5)
|
|
12
|
+
|
|
13
|
+
- CSS properties, style rules, and at-rules that contain blocks or functions nested more deeply than Crass's maximum nesting depth (25 levels by default) are now removed. (GHSA-wgw9-qhqg-8hv5)
|
|
14
|
+
|
|
15
|
+
- Hardened CSS sanitization against tokenizer differentials involving bad URLs. CSS properties that contain a bad URL anywhere inside a simple block (such as `[...]` or `(...)`) are now removed, as are style rules whose selectors contain a bad URL and at-rules whose preludes contain a bad URL. URLs inside simple blocks are now also validated against the configured protocol allowlist. Previously, a bug in Crass (fixed in Crass 1.0.8) could be exploited to smuggle CSS declarations, rules, or at-rules past Sanitize by hiding them in a bad URL in one of these locations.
|
|
16
|
+
|
|
17
|
+
- CSS style rules whose selectors contain a semicolon are now removed, as a defense in depth measure. A semicolon is never valid in a selector, but when a style rule appears where a browser that supports CSS nesting expects declarations, the browser treats everything before the semicolon as a declaration. This could be used to smuggle disallowed properties past `Sanitize::CSS.tree!` when it was given a list of declarations rather than a list of rules (though this isn't something Sanitize itself does).
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Removed an unused `require "cgi"` that caused a warning on Ruby 4.0, since the CGI library is no longer part of Ruby's standard library.
|
|
22
|
+
|
|
5
23
|
## 7.0.0 (2024-12-29)
|
|
6
24
|
|
|
7
25
|
Sanitize has no breaking API changes in this release, but the major version number has been incremented because we've dropped support for end-of-life versions of Ruby. As long as you're using Ruby 3.1.0 or later, this should be a painless upgrade!
|
data/README.md
CHANGED
|
@@ -12,10 +12,16 @@ Sanitize is based on the [Nokogiri HTML5 parser][nokogiri], which parses HTML th
|
|
|
12
12
|
[crass]:https://github.com/rgrove/crass
|
|
13
13
|
[nokogiri]:https://github.com/sparklemotion/nokogiri
|
|
14
14
|
|
|
15
|
+
> [!WARNING]
|
|
16
|
+
>
|
|
17
|
+
> Sanitize cannot fully sanitize the contents of `<math>` or `<svg>` elements. MathML and SVG elements are [foreign elements](https://html.spec.whatwg.org/multipage/syntax.html#foreign-elements) that don't follow normal HTML parsing rules.
|
|
18
|
+
>
|
|
19
|
+
> By default, Sanitize will remove all MathML and SVG elements. If you add MathML or SVG elements to a custom element allowlist, you may create a security vulnerability in your application.
|
|
20
|
+
|
|
15
21
|
## Links
|
|
16
22
|
|
|
17
23
|
* [Home](https://github.com/rgrove/sanitize/)
|
|
18
|
-
* [API Docs](https://rubydoc.info/
|
|
24
|
+
* [API Docs](https://rubydoc.info/gems/sanitize/Sanitize)
|
|
19
25
|
* [Issues](https://github.com/rgrove/sanitize/issues)
|
|
20
26
|
* [Release History](https://github.com/rgrove/sanitize/releases)
|
|
21
27
|
* [Online Demo](https://sanitize-web.fly.dev/)
|
|
@@ -136,6 +142,9 @@ Sanitize.fragment(html,
|
|
|
136
142
|
|
|
137
143
|
Sanitize will happily clean up a standalone CSS stylesheet or property string without needing to invoke the HTML parser.
|
|
138
144
|
|
|
145
|
+
> [!WARNING]
|
|
146
|
+
> This should only be used to sanitize CSS for use in a standalone CSS file. To safely sanitize CSS for use in an HTML context, such as in a `<style>` element or a `style` attribute, you must use `Sanitize.document()` or `Sanitize.fragment()` with an appropriate allowlist as described in the previous sections. Failure to do so may enable HTML injection attacks.
|
|
147
|
+
|
|
139
148
|
```ruby
|
|
140
149
|
css = %[
|
|
141
150
|
@import url(evil.css);
|
data/lib/sanitize/css.rb
CHANGED
|
@@ -66,6 +66,10 @@ class Sanitize
|
|
|
66
66
|
# tree = Crass.parse(css)
|
|
67
67
|
# Sanitize::CSS.tree!(tree, Sanitize::Config::RELAXED)
|
|
68
68
|
#
|
|
69
|
+
# The tree must be a list of rules, such as the tree returned by
|
|
70
|
+
# `Crass.parse`. To sanitize a list of declarations, such as the contents
|
|
71
|
+
# of an HTML `style` attribute, use {.properties}.
|
|
72
|
+
#
|
|
69
73
|
# @return [Array] Sanitized Crass CSS parse tree.
|
|
70
74
|
def self.tree!(tree, config = {})
|
|
71
75
|
new(config).tree!(tree)
|
|
@@ -82,6 +86,11 @@ class Sanitize
|
|
|
82
86
|
@at_rules_with_properties = Set.new(@config[:at_rules_with_properties])
|
|
83
87
|
@at_rules_with_styles = Set.new(@config[:at_rules_with_styles])
|
|
84
88
|
@import_url_validator = @config[:import_url_validator]
|
|
89
|
+
|
|
90
|
+
@parse_options = {
|
|
91
|
+
preserve_comments: @config[:allow_comments],
|
|
92
|
+
preserve_hacks: @config[:allow_hacks]
|
|
93
|
+
}.freeze
|
|
85
94
|
end
|
|
86
95
|
|
|
87
96
|
# Sanitizes inline CSS style properties.
|
|
@@ -96,11 +105,8 @@ class Sanitize
|
|
|
96
105
|
#
|
|
97
106
|
# @return [String] Sanitized CSS properties.
|
|
98
107
|
def properties(css)
|
|
99
|
-
tree = Crass.parse_properties(css,
|
|
100
|
-
|
|
101
|
-
preserve_hacks: @config[:allow_hacks])
|
|
102
|
-
|
|
103
|
-
tree!(tree)
|
|
108
|
+
tree = Crass.parse_properties(css, @parse_options)
|
|
109
|
+
sanitize_tree!(tree, declarations: true)
|
|
104
110
|
Crass::Parser.stringify(tree)
|
|
105
111
|
end
|
|
106
112
|
|
|
@@ -127,10 +133,7 @@ class Sanitize
|
|
|
127
133
|
#
|
|
128
134
|
# @return [String] Sanitized CSS stylesheet.
|
|
129
135
|
def stylesheet(css)
|
|
130
|
-
tree = Crass.parse(css,
|
|
131
|
-
preserve_comments: @config[:allow_comments],
|
|
132
|
-
preserve_hacks: @config[:allow_hacks])
|
|
133
|
-
|
|
136
|
+
tree = Crass.parse(css, @parse_options)
|
|
134
137
|
tree!(tree)
|
|
135
138
|
Crass::Parser.stringify(tree)
|
|
136
139
|
end
|
|
@@ -155,48 +158,13 @@ class Sanitize
|
|
|
155
158
|
#
|
|
156
159
|
# scss.tree!(tree)
|
|
157
160
|
#
|
|
161
|
+
# The tree must be a list of rules, such as the tree returned by
|
|
162
|
+
# `Crass.parse`. To sanitize a list of declarations, such as the contents
|
|
163
|
+
# of an HTML `style` attribute, use {#properties}.
|
|
164
|
+
#
|
|
158
165
|
# @return [Array] Sanitized Crass CSS parse tree.
|
|
159
166
|
def tree!(tree)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
tree.map! do |node|
|
|
163
|
-
next nil if node.nil?
|
|
164
|
-
|
|
165
|
-
case node[:node]
|
|
166
|
-
when :at_rule
|
|
167
|
-
preceded_by_property = false
|
|
168
|
-
next at_rule!(node)
|
|
169
|
-
|
|
170
|
-
when :comment
|
|
171
|
-
next node if @config[:allow_comments]
|
|
172
|
-
|
|
173
|
-
when :property
|
|
174
|
-
prop = property!(node)
|
|
175
|
-
preceded_by_property = !prop.nil?
|
|
176
|
-
next prop
|
|
177
|
-
|
|
178
|
-
when :semicolon
|
|
179
|
-
# Only preserve the semicolon if it was preceded by an allowlisted
|
|
180
|
-
# property. Otherwise, omit it in order to prevent redundant
|
|
181
|
-
# semicolons.
|
|
182
|
-
if preceded_by_property
|
|
183
|
-
preceded_by_property = false
|
|
184
|
-
next node
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
when :style_rule
|
|
188
|
-
preceded_by_property = false
|
|
189
|
-
tree!(node[:children])
|
|
190
|
-
next node
|
|
191
|
-
|
|
192
|
-
when :whitespace
|
|
193
|
-
next node
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
nil
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
tree
|
|
167
|
+
sanitize_tree!(tree, declarations: false)
|
|
200
168
|
end
|
|
201
169
|
|
|
202
170
|
# -- Protected Instance Methods --------------------------------------------
|
|
@@ -204,22 +172,33 @@ class Sanitize
|
|
|
204
172
|
|
|
205
173
|
# Sanitizes a CSS at-rule node. Returns the sanitized node, or `nil` if the
|
|
206
174
|
# current config doesn't allow this at-rule.
|
|
207
|
-
|
|
175
|
+
#
|
|
176
|
+
# If _declarations_ is `true`, the at-rule appears in a list of
|
|
177
|
+
# declarations (such as the contents of a style rule) rather than in a list
|
|
178
|
+
# of rules.
|
|
179
|
+
def at_rule!(rule, declarations:)
|
|
208
180
|
name = rule[:name].downcase
|
|
209
181
|
|
|
182
|
+
return nil if contains_invalid_node?(rule[:prelude])
|
|
183
|
+
|
|
210
184
|
if @at_rules_with_styles.include?(name)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
185
|
+
# Browsers that support CSS nesting parse the block of an at-rule like
|
|
186
|
+
# `@media` that appears in a list of declarations as more declarations
|
|
187
|
+
# rather than as rules, so it must be sanitized as declarations.
|
|
188
|
+
# Otherwise a declaration like `position:fixed;a{}` would be parsed as
|
|
189
|
+
# a style rule with the selector `position:fixed;a` and would bypass
|
|
190
|
+
# the property allowlist.
|
|
191
|
+
styles = if declarations
|
|
192
|
+
Crass::Parser.parse_properties(rule[:block], @parse_options)
|
|
193
|
+
else
|
|
194
|
+
Crass::Parser.parse_rules(rule[:block], @parse_options)
|
|
195
|
+
end
|
|
214
196
|
|
|
215
|
-
rule[:block] =
|
|
197
|
+
rule[:block] = sanitize_tree!(styles, declarations: declarations)
|
|
216
198
|
|
|
217
199
|
elsif @at_rules_with_properties.include?(name)
|
|
218
|
-
props = Crass::Parser.parse_properties(rule[:block],
|
|
219
|
-
|
|
220
|
-
preserve_hacks: @config[:allow_hacks])
|
|
221
|
-
|
|
222
|
-
rule[:block] = tree!(props)
|
|
200
|
+
props = Crass::Parser.parse_properties(rule[:block], @parse_options)
|
|
201
|
+
rule[:block] = sanitize_tree!(props, declarations: true)
|
|
223
202
|
|
|
224
203
|
elsif @at_rules.include?(name)
|
|
225
204
|
return nil if name == "import" && !import_url_allowed?(rule)
|
|
@@ -231,6 +210,31 @@ class Sanitize
|
|
|
231
210
|
rule
|
|
232
211
|
end
|
|
233
212
|
|
|
213
|
+
# Returns `true` if the given array of Crass nodes contains an invalid node
|
|
214
|
+
# (see {#invalid_node?}) at any depth.
|
|
215
|
+
def contains_invalid_node?(nodes)
|
|
216
|
+
contains_node?(nodes) { |node| invalid_node?(node) }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Returns `true` if the given array of Crass nodes contains a node at any
|
|
220
|
+
# depth for which the given block returns a truthy value.
|
|
221
|
+
def contains_node?(nodes)
|
|
222
|
+
return false unless Array === nodes
|
|
223
|
+
|
|
224
|
+
stack = nodes.dup
|
|
225
|
+
|
|
226
|
+
until stack.empty?
|
|
227
|
+
node = stack.pop
|
|
228
|
+
next unless Hash === node
|
|
229
|
+
return true if yield(node)
|
|
230
|
+
|
|
231
|
+
value = node[:value]
|
|
232
|
+
stack.concat(value) if Array === value
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
false
|
|
236
|
+
end
|
|
237
|
+
|
|
234
238
|
# Returns `true` if the given CSS function name is an image-related function
|
|
235
239
|
# that may contain image URLs that need to be validated.
|
|
236
240
|
def image_function?(name)
|
|
@@ -250,6 +254,22 @@ class Sanitize
|
|
|
250
254
|
@import_url_validator.call(import_url)
|
|
251
255
|
end
|
|
252
256
|
|
|
257
|
+
# Returns `true` if the given Crass node is a `:bad_url` or `:error` node.
|
|
258
|
+
#
|
|
259
|
+
# A bad URL is invalid CSS that a browser will discard, but a parser that
|
|
260
|
+
# disagrees with the browser about where a bad URL ends could be tricked
|
|
261
|
+
# into hiding CSS inside it.
|
|
262
|
+
#
|
|
263
|
+
# An `:error` node is CSS that Crass couldn't or wouldn't parse, such as a
|
|
264
|
+
# block nested more deeply than Crass's maximum nesting depth. Its contents
|
|
265
|
+
# are never inspected, but they're preserved verbatim when the tree is
|
|
266
|
+
# stringified, so a browser would still see them.
|
|
267
|
+
#
|
|
268
|
+
# It's safest to reject any construct that contains either one.
|
|
269
|
+
def invalid_node?(node)
|
|
270
|
+
node[:node] == :bad_url || node[:node] == :error
|
|
271
|
+
end
|
|
272
|
+
|
|
253
273
|
# Sanitizes a CSS property node. Returns the sanitized node, or `nil` if the
|
|
254
274
|
# current config doesn't allow this property.
|
|
255
275
|
def property!(prop)
|
|
@@ -266,6 +286,9 @@ class Sanitize
|
|
|
266
286
|
combined_value = +""
|
|
267
287
|
|
|
268
288
|
nodes.each do |child|
|
|
289
|
+
# Reject properties that contain an invalid node at any depth.
|
|
290
|
+
return nil if invalid_node?(child)
|
|
291
|
+
|
|
269
292
|
value = child[:value]
|
|
270
293
|
|
|
271
294
|
case child[:node]
|
|
@@ -299,14 +322,86 @@ class Sanitize
|
|
|
299
322
|
when :url
|
|
300
323
|
return nil unless valid_url?(child)
|
|
301
324
|
|
|
302
|
-
when :
|
|
303
|
-
|
|
325
|
+
when :simple_block
|
|
326
|
+
# Reject properties that contain a `{}` block at any depth.
|
|
327
|
+
#
|
|
328
|
+
# A `{}` block isn't valid in a standard property value, and browsers
|
|
329
|
+
# that support CSS nesting will reparse an invalid declaration that
|
|
330
|
+
# contains one as a nested style rule (e.g.
|
|
331
|
+
# `color:hover{position:fixed}` becomes the rule
|
|
332
|
+
# `color:hover { position: fixed }`), which would allow disallowed
|
|
333
|
+
# properties to be smuggled past the sanitizer.
|
|
334
|
+
return nil if child[:start] == "{"
|
|
335
|
+
|
|
336
|
+
nodes.concat(value) if Array === value
|
|
304
337
|
end
|
|
305
338
|
end
|
|
306
339
|
|
|
307
340
|
prop
|
|
308
341
|
end
|
|
309
342
|
|
|
343
|
+
# Sanitizes the given Crass CSS parse tree and all its children, modifying
|
|
344
|
+
# it in place. See {#tree!}.
|
|
345
|
+
#
|
|
346
|
+
# If _declarations_ is `true`, the tree is a list of declarations (such as
|
|
347
|
+
# the contents of a style rule) rather than a list of rules.
|
|
348
|
+
def sanitize_tree!(tree, declarations:)
|
|
349
|
+
preceded_by_property = false
|
|
350
|
+
|
|
351
|
+
tree.map! do |node|
|
|
352
|
+
next nil if node.nil?
|
|
353
|
+
|
|
354
|
+
case node[:node]
|
|
355
|
+
when :at_rule
|
|
356
|
+
preceded_by_property = false
|
|
357
|
+
next at_rule!(node, declarations: declarations)
|
|
358
|
+
|
|
359
|
+
when :comment
|
|
360
|
+
next node if @config[:allow_comments]
|
|
361
|
+
|
|
362
|
+
when :property
|
|
363
|
+
prop = property!(node)
|
|
364
|
+
preceded_by_property = !prop.nil?
|
|
365
|
+
next prop
|
|
366
|
+
|
|
367
|
+
when :semicolon
|
|
368
|
+
# Only preserve the semicolon if it was preceded by an allowlisted
|
|
369
|
+
# property. Otherwise, omit it in order to prevent redundant
|
|
370
|
+
# semicolons.
|
|
371
|
+
if preceded_by_property
|
|
372
|
+
preceded_by_property = false
|
|
373
|
+
next node
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
when :style_rule
|
|
377
|
+
preceded_by_property = false
|
|
378
|
+
|
|
379
|
+
# Reject style rules whose selectors contain an invalid node (see
|
|
380
|
+
# {#invalid_node?}) or a semicolon at any depth.
|
|
381
|
+
#
|
|
382
|
+
# A semicolon is never valid in a selector, but if a style rule
|
|
383
|
+
# appears where a browser expects declarations (such as the contents
|
|
384
|
+
# of an `@media` rule in a list of declarations), the browser will
|
|
385
|
+
# treat everything before the semicolon as a declaration. For
|
|
386
|
+
# example, `position:fixed;a{}` would be treated as the declaration
|
|
387
|
+
# `position:fixed` followed by the style rule `a{}`.
|
|
388
|
+
next nil if contains_node?(node.dig(:selector, :tokens)) { |token|
|
|
389
|
+
invalid_node?(token) || token[:node] == :semicolon
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
sanitize_tree!(node[:children], declarations: true)
|
|
393
|
+
next node
|
|
394
|
+
|
|
395
|
+
when :whitespace
|
|
396
|
+
next node
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
nil
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
tree
|
|
403
|
+
end
|
|
404
|
+
|
|
310
405
|
# Returns `true` if the given node (which may be of type `:url` or
|
|
311
406
|
# `:function`, since the CSS syntax can produce both) uses an allowlisted
|
|
312
407
|
# protocol.
|
data/lib/sanitize/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sanitize
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.0.
|
|
4
|
+
version: 7.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ryan Grove
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: crass
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 1.0.
|
|
18
|
+
version: 1.0.8
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 1.0.
|
|
25
|
+
version: 1.0.8
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: nokogiri
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -40,7 +40,7 @@ dependencies:
|
|
|
40
40
|
description: |
|
|
41
41
|
Sanitize is an allowlist-based HTML and CSS sanitizer. It removes all HTML
|
|
42
42
|
and/or CSS from a string except the elements, attributes, and properties you
|
|
43
|
-
choose to allow.
|
|
43
|
+
choose to allow.
|
|
44
44
|
email: ryan@wonko.com
|
|
45
45
|
executables: []
|
|
46
46
|
extensions: []
|
|
@@ -62,18 +62,6 @@ files:
|
|
|
62
62
|
- lib/sanitize/transformers/clean_doctype.rb
|
|
63
63
|
- lib/sanitize/transformers/clean_element.rb
|
|
64
64
|
- lib/sanitize/version.rb
|
|
65
|
-
- test/common.rb
|
|
66
|
-
- test/test_clean_comment.rb
|
|
67
|
-
- test/test_clean_css.rb
|
|
68
|
-
- test/test_clean_doctype.rb
|
|
69
|
-
- test/test_clean_element.rb
|
|
70
|
-
- test/test_config.rb
|
|
71
|
-
- test/test_malicious_css.rb
|
|
72
|
-
- test/test_malicious_html.rb
|
|
73
|
-
- test/test_parser.rb
|
|
74
|
-
- test/test_sanitize.rb
|
|
75
|
-
- test/test_sanitize_css.rb
|
|
76
|
-
- test/test_transformers.rb
|
|
77
65
|
homepage: https://github.com/rgrove/sanitize/
|
|
78
66
|
licenses:
|
|
79
67
|
- MIT
|
|
@@ -95,7 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
95
83
|
- !ruby/object:Gem::Version
|
|
96
84
|
version: '0'
|
|
97
85
|
requirements: []
|
|
98
|
-
rubygems_version:
|
|
86
|
+
rubygems_version: 4.0.20
|
|
99
87
|
specification_version: 4
|
|
100
88
|
summary: Allowlist-based HTML and CSS sanitizer.
|
|
101
89
|
test_files: []
|
data/test/common.rb
DELETED
data/test/test_clean_comment.rb
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "common"
|
|
4
|
-
|
|
5
|
-
describe "Sanitize::Transformers::CleanComment" do
|
|
6
|
-
make_my_diffs_pretty!
|
|
7
|
-
parallelize_me!
|
|
8
|
-
|
|
9
|
-
describe "when :allow_comments is false" do
|
|
10
|
-
before do
|
|
11
|
-
@s = Sanitize.new(allow_comments: false, elements: ["div"])
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
it "should remove comments" do
|
|
15
|
-
_(@s.fragment("foo <!-- comment --> bar")).must_equal "foo bar"
|
|
16
|
-
_(@s.fragment("foo <!-- ")).must_equal "foo "
|
|
17
|
-
_(@s.fragment("foo <!-- - -> bar")).must_equal "foo "
|
|
18
|
-
_(@s.fragment("foo <!--\n\n\n\n-->bar")).must_equal "foo bar"
|
|
19
|
-
_(@s.fragment("foo <!-- <!-- <!-- --> --> -->bar")).must_equal "foo --> -->bar"
|
|
20
|
-
_(@s.fragment("foo <div <!-- comment -->>bar</div>")).must_equal "foo <div>>bar</div>"
|
|
21
|
-
|
|
22
|
-
# Special case: the comment markup is inside a <script>, which makes it
|
|
23
|
-
# text content and not an actual HTML comment.
|
|
24
|
-
_(@s.fragment("<script><!-- comment --></script>")).must_equal ""
|
|
25
|
-
|
|
26
|
-
_(Sanitize.fragment("<script><!-- comment --></script>", allow_comments: false, elements: ["script"]))
|
|
27
|
-
.must_equal "<script><!-- comment --></script>"
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
describe "when :allow_comments is true" do
|
|
32
|
-
before do
|
|
33
|
-
@s = Sanitize.new(allow_comments: true, elements: ["div"])
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
it "should allow comments" do
|
|
37
|
-
_(@s.fragment("foo <!-- comment --> bar")).must_equal "foo <!-- comment --> bar"
|
|
38
|
-
_(@s.fragment("foo <!-- ")).must_equal "foo <!-- -->"
|
|
39
|
-
_(@s.fragment("foo <!-- - -> bar")).must_equal "foo <!-- - -> bar-->"
|
|
40
|
-
_(@s.fragment("foo <!--\n\n\n\n-->bar")).must_equal "foo <!--\n\n\n\n-->bar"
|
|
41
|
-
_(@s.fragment("foo <!-- <!-- <!-- --> --> -->bar")).must_equal "foo <!-- <!-- <!-- --> --> -->bar"
|
|
42
|
-
_(@s.fragment("foo <div <!-- comment -->>bar</div>")).must_equal "foo <div>>bar</div>"
|
|
43
|
-
|
|
44
|
-
_(Sanitize.fragment("<script><!-- comment --></script>", allow_comments: true, elements: ["script"]))
|
|
45
|
-
.must_equal "<script><!-- comment --></script>"
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
data/test/test_clean_css.rb
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "common"
|
|
4
|
-
|
|
5
|
-
describe "Sanitize::Transformers::CSS::CleanAttribute" do
|
|
6
|
-
make_my_diffs_pretty!
|
|
7
|
-
parallelize_me!
|
|
8
|
-
|
|
9
|
-
before do
|
|
10
|
-
@s = Sanitize.new(Sanitize::Config::RELAXED)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
it "should sanitize CSS properties in style attributes" do
|
|
14
|
-
_(@s.fragment(%[
|
|
15
|
-
<div style="color: #fff; width: expression(alert(1)); /* <-- evil! */"></div>
|
|
16
|
-
].strip)).must_equal %(
|
|
17
|
-
<div style="color: #fff; /* <-- evil! */"></div>
|
|
18
|
-
).strip
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
it "should remove the style attribute if the sanitized CSS is empty" do
|
|
22
|
-
_(@s.fragment('<div style="width: expression(alert(1))"></div>'))
|
|
23
|
-
.must_equal "<div></div>"
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
describe "Sanitize::Transformers::CSS::CleanElement" do
|
|
28
|
-
make_my_diffs_pretty!
|
|
29
|
-
parallelize_me!
|
|
30
|
-
|
|
31
|
-
before do
|
|
32
|
-
@s = Sanitize.new(Sanitize::Config::RELAXED)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it "should sanitize CSS stylesheets in <style> elements" do
|
|
36
|
-
html = %[
|
|
37
|
-
<style>@import url(evil.css);
|
|
38
|
-
/* Yay CSS! */
|
|
39
|
-
.foo { color: #fff; }
|
|
40
|
-
#bar { background: url(yay.jpg); bogus: wtf; }
|
|
41
|
-
.evil { width: expression(xss()); }
|
|
42
|
-
|
|
43
|
-
@media screen (max-width:480px) {
|
|
44
|
-
.foo { width: 400px; }
|
|
45
|
-
#bar:not(.baz) { height: 100px; }
|
|
46
|
-
}
|
|
47
|
-
</style>
|
|
48
|
-
].strip
|
|
49
|
-
|
|
50
|
-
_(@s.fragment(html)).must_equal %[
|
|
51
|
-
<style>
|
|
52
|
-
/* Yay CSS! */
|
|
53
|
-
.foo { color: #fff; }
|
|
54
|
-
#bar { background: url(yay.jpg); }
|
|
55
|
-
.evil { }
|
|
56
|
-
|
|
57
|
-
@media screen (max-width:480px) {
|
|
58
|
-
.foo { width: 400px; }
|
|
59
|
-
#bar:not(.baz) { height: 100px; }
|
|
60
|
-
}
|
|
61
|
-
</style>
|
|
62
|
-
].strip
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
it "should remove the <style> element if the sanitized CSS is empty" do
|
|
66
|
-
_(@s.fragment("<style></style>")).must_equal ""
|
|
67
|
-
end
|
|
68
|
-
end
|
data/test/test_clean_doctype.rb
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "common"
|
|
4
|
-
|
|
5
|
-
describe "Sanitize::Transformers::CleanDoctype" do
|
|
6
|
-
make_my_diffs_pretty!
|
|
7
|
-
parallelize_me!
|
|
8
|
-
|
|
9
|
-
describe "when :allow_doctype is false" do
|
|
10
|
-
before do
|
|
11
|
-
@s = Sanitize.new(allow_doctype: false, elements: ["html"])
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
it "should remove doctype declarations" do
|
|
15
|
-
_(@s.document("<!DOCTYPE html><html>foo</html>")).must_equal "<html>foo</html>"
|
|
16
|
-
_(@s.fragment("<!DOCTYPE html>foo")).must_equal "foo"
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
it "should not allow doctype definitions in fragments" do
|
|
20
|
-
_(@s.fragment("<!DOCTYPE html><html>foo</html>"))
|
|
21
|
-
.must_equal "foo"
|
|
22
|
-
|
|
23
|
-
_(@s.fragment('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"><html>foo</html>'))
|
|
24
|
-
.must_equal "foo"
|
|
25
|
-
|
|
26
|
-
_(@s.fragment("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"><html>foo</html>"))
|
|
27
|
-
.must_equal "foo"
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
describe "when :allow_doctype is true" do
|
|
32
|
-
before do
|
|
33
|
-
@s = Sanitize.new(allow_doctype: true, elements: ["html"])
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
it "should allow doctype declarations in documents" do
|
|
37
|
-
_(@s.document("<!DOCTYPE html><html>foo</html>"))
|
|
38
|
-
.must_equal "<!DOCTYPE html><html>foo</html>"
|
|
39
|
-
|
|
40
|
-
_(@s.document('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"><html>foo</html>'))
|
|
41
|
-
.must_equal "<!DOCTYPE html><html>foo</html>"
|
|
42
|
-
|
|
43
|
-
_(@s.document("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"><html>foo</html>"))
|
|
44
|
-
.must_equal "<!DOCTYPE html><html>foo</html>"
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
it "should not allow obviously invalid doctype declarations in documents" do
|
|
48
|
-
_(@s.document("<!DOCTYPE blah blah blah><html>foo</html>"))
|
|
49
|
-
.must_equal "<!DOCTYPE html><html>foo</html>"
|
|
50
|
-
|
|
51
|
-
_(@s.document("<!DOCTYPE blah><html>foo</html>"))
|
|
52
|
-
.must_equal "<!DOCTYPE html><html>foo</html>"
|
|
53
|
-
|
|
54
|
-
_(@s.document('<!DOCTYPE html BLAH "-//W3C//DTD HTML 4.01//EN"><html>foo</html>'))
|
|
55
|
-
.must_equal "<!DOCTYPE html><html>foo</html>"
|
|
56
|
-
|
|
57
|
-
_(@s.document("<!whatever><html>foo</html>"))
|
|
58
|
-
.must_equal "<html>foo</html>"
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
it "should not allow doctype definitions in fragments" do
|
|
62
|
-
_(@s.fragment("<!DOCTYPE html><html>foo</html>"))
|
|
63
|
-
.must_equal "foo"
|
|
64
|
-
|
|
65
|
-
_(@s.fragment('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"><html>foo</html>'))
|
|
66
|
-
.must_equal "foo"
|
|
67
|
-
|
|
68
|
-
_(@s.fragment("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"><html>foo</html>"))
|
|
69
|
-
.must_equal "foo"
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|