sanitize 6.1.3 → 7.0.0
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/{HISTORY.md → CHANGELOG.md} +32 -14
- data/LICENSE +3 -1
- data/README.md +120 -238
- data/lib/sanitize/config/basic.rb +15 -15
- data/lib/sanitize/config/default.rb +45 -45
- data/lib/sanitize/config/relaxed.rb +136 -32
- data/lib/sanitize/config/restricted.rb +2 -2
- data/lib/sanitize/config.rb +12 -14
- data/lib/sanitize/css.rb +308 -308
- data/lib/sanitize/transformers/clean_cdata.rb +9 -9
- data/lib/sanitize/transformers/clean_comment.rb +9 -9
- data/lib/sanitize/transformers/clean_css.rb +59 -55
- data/lib/sanitize/transformers/clean_doctype.rb +15 -15
- data/lib/sanitize/transformers/clean_element.rb +220 -237
- data/lib/sanitize/version.rb +3 -1
- data/lib/sanitize.rb +38 -38
- data/test/common.rb +4 -3
- data/test/test_clean_comment.rb +26 -25
- data/test/test_clean_css.rb +14 -13
- data/test/test_clean_doctype.rb +21 -20
- data/test/test_clean_element.rb +258 -273
- data/test/test_config.rb +22 -21
- data/test/test_malicious_css.rb +20 -19
- data/test/test_malicious_html.rb +100 -99
- data/test/test_parser.rb +26 -25
- data/test/test_sanitize.rb +70 -69
- data/test/test_sanitize_css.rb +149 -114
- data/test/test_transformers.rb +81 -83
- metadata +14 -43
data/test/test_sanitize_css.rb
CHANGED
@@ -1,27 +1,28 @@
|
|
1
|
-
#
|
2
|
-
require_relative 'common'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
3
|
+
require_relative "common"
|
4
|
+
|
5
|
+
describe "Sanitize::CSS" do
|
5
6
|
make_my_diffs_pretty!
|
6
7
|
parallelize_me!
|
7
8
|
|
8
|
-
describe
|
9
|
+
describe "instance methods" do
|
9
10
|
before do
|
10
11
|
@default = Sanitize::CSS.new
|
11
12
|
@relaxed = Sanitize::CSS.new(Sanitize::Config::RELAXED[:css])
|
12
|
-
@custom
|
13
|
+
@custom = Sanitize::CSS.new(properties: %w[background color width])
|
13
14
|
end
|
14
15
|
|
15
|
-
describe
|
16
|
-
it
|
16
|
+
describe "#properties" do
|
17
|
+
it "should sanitize CSS properties" do
|
17
18
|
css = 'background: #fff; width: expression(alert("hi"));'
|
18
19
|
|
19
|
-
_(@default.properties(css)).must_equal
|
20
|
-
_(@relaxed.properties(css)).must_equal
|
21
|
-
_(@custom.properties(css)).must_equal
|
20
|
+
_(@default.properties(css)).must_equal " "
|
21
|
+
_(@relaxed.properties(css)).must_equal "background: #fff; "
|
22
|
+
_(@custom.properties(css)).must_equal "background: #fff; "
|
22
23
|
end
|
23
24
|
|
24
|
-
it
|
25
|
+
it "should allow allowlisted URL protocols" do
|
25
26
|
[
|
26
27
|
"background: url(relative.jpg)",
|
27
28
|
"background: url('relative.jpg')",
|
@@ -39,13 +40,13 @@ describe 'Sanitize::CSS' do
|
|
39
40
|
"background: image('https://example.com/https.jpg');",
|
40
41
|
"background: image(rtl 'https://example.com/https.jpg');"
|
41
42
|
].each do |css|
|
42
|
-
_(@default.properties(css)).must_equal
|
43
|
+
_(@default.properties(css)).must_equal ""
|
43
44
|
_(@relaxed.properties(css)).must_equal css
|
44
|
-
_(@custom.properties(css)).must_equal
|
45
|
+
_(@custom.properties(css)).must_equal ""
|
45
46
|
end
|
46
47
|
end
|
47
48
|
|
48
|
-
it
|
49
|
+
it "should not allow non-allowlisted URL protocols" do
|
49
50
|
[
|
50
51
|
"background: url(javascript:alert(0))",
|
51
52
|
"background: url(ja\\56 ascript:alert(0))",
|
@@ -55,21 +56,21 @@ describe 'Sanitize::CSS' do
|
|
55
56
|
"background: url('javas\\\ncript:alert(0)')",
|
56
57
|
"background: url('java\\0script:foo')"
|
57
58
|
].each do |css|
|
58
|
-
_(@default.properties(css)).must_equal
|
59
|
-
_(@relaxed.properties(css)).must_equal
|
60
|
-
_(@custom.properties(css)).must_equal
|
59
|
+
_(@default.properties(css)).must_equal ""
|
60
|
+
_(@relaxed.properties(css)).must_equal ""
|
61
|
+
_(@custom.properties(css)).must_equal ""
|
61
62
|
end
|
62
63
|
end
|
63
64
|
|
64
|
-
it
|
65
|
+
it "should not allow -moz-binding" do
|
65
66
|
css = "-moz-binding:url('http://ha.ckers.org/xssmoz.xml#xss')"
|
66
67
|
|
67
|
-
_(@default.properties(css)).must_equal
|
68
|
-
_(@relaxed.properties(css)).must_equal
|
69
|
-
_(@custom.properties(css)).must_equal
|
68
|
+
_(@default.properties(css)).must_equal ""
|
69
|
+
_(@relaxed.properties(css)).must_equal ""
|
70
|
+
_(@custom.properties(css)).must_equal ""
|
70
71
|
end
|
71
72
|
|
72
|
-
it
|
73
|
+
it "should not allow expressions" do
|
73
74
|
[
|
74
75
|
"width:expression(alert(1))",
|
75
76
|
"width: /**/expression(alert(1)",
|
@@ -78,57 +79,57 @@ describe 'Sanitize::CSS' do
|
|
78
79
|
"xss:expression(alert(1))",
|
79
80
|
"height: foo(expression(alert(1)));"
|
80
81
|
].each do |css|
|
81
|
-
_(@default.properties(css)).must_equal
|
82
|
-
_(@relaxed.properties(css)).must_equal
|
83
|
-
_(@custom.properties(css)).must_equal
|
82
|
+
_(@default.properties(css)).must_equal ""
|
83
|
+
_(@relaxed.properties(css)).must_equal ""
|
84
|
+
_(@custom.properties(css)).must_equal ""
|
84
85
|
end
|
85
86
|
end
|
86
87
|
|
87
|
-
it
|
88
|
+
it "should not allow behaviors" do
|
88
89
|
css = "behavior: url(xss.htc);"
|
89
90
|
|
90
|
-
_(@default.properties(css)).must_equal
|
91
|
-
_(@relaxed.properties(css)).must_equal
|
92
|
-
_(@custom.properties(css)).must_equal
|
91
|
+
_(@default.properties(css)).must_equal ""
|
92
|
+
_(@relaxed.properties(css)).must_equal ""
|
93
|
+
_(@custom.properties(css)).must_equal ""
|
93
94
|
end
|
94
95
|
|
95
|
-
describe
|
96
|
-
it
|
97
|
-
_(@relaxed.properties(
|
98
|
-
.must_equal
|
96
|
+
describe "when :allow_comments is true" do
|
97
|
+
it "should preserve comments" do
|
98
|
+
_(@relaxed.properties("color: #fff; /* comment */ width: 100px;"))
|
99
|
+
.must_equal "color: #fff; /* comment */ width: 100px;"
|
99
100
|
|
100
101
|
_(@relaxed.properties("color: #fff; /* \n\ncomment */ width: 100px;"))
|
101
102
|
.must_equal "color: #fff; /* \n\ncomment */ width: 100px;"
|
102
103
|
end
|
103
104
|
end
|
104
105
|
|
105
|
-
describe
|
106
|
-
it
|
107
|
-
_(@custom.properties(
|
108
|
-
.must_equal
|
106
|
+
describe "when :allow_comments is false" do
|
107
|
+
it "should strip comments" do
|
108
|
+
_(@custom.properties("color: #fff; /* comment */ width: 100px;"))
|
109
|
+
.must_equal "color: #fff; width: 100px;"
|
109
110
|
|
110
111
|
_(@custom.properties("color: #fff; /* \n\ncomment */ width: 100px;"))
|
111
|
-
.must_equal
|
112
|
+
.must_equal "color: #fff; width: 100px;"
|
112
113
|
end
|
113
114
|
end
|
114
115
|
|
115
|
-
describe
|
116
|
-
it
|
117
|
-
_(@relaxed.properties(
|
118
|
-
.must_equal
|
116
|
+
describe "when :allow_hacks is true" do
|
117
|
+
it "should allow common CSS hacks" do
|
118
|
+
_(@relaxed.properties("_border: 1px solid #fff; *width: 10px"))
|
119
|
+
.must_equal "_border: 1px solid #fff; *width: 10px"
|
119
120
|
end
|
120
121
|
end
|
121
122
|
|
122
|
-
describe
|
123
|
-
it
|
124
|
-
_(@custom.properties(
|
125
|
-
.must_equal
|
123
|
+
describe "when :allow_hacks is false" do
|
124
|
+
it "should not allow common CSS hacks" do
|
125
|
+
_(@custom.properties("_border: 1px solid #fff; *width: 10px"))
|
126
|
+
.must_equal " "
|
126
127
|
end
|
127
128
|
end
|
128
129
|
end
|
129
130
|
|
130
|
-
describe
|
131
|
-
it
|
131
|
+
describe "#stylesheet" do
|
132
|
+
it "should sanitize a CSS stylesheet" do
|
132
133
|
css = %[
|
133
134
|
/* Yay CSS! */
|
134
135
|
.foo { color: #fff; }
|
@@ -140,82 +141,82 @@ describe 'Sanitize::CSS' do
|
|
140
141
|
}
|
141
142
|
].strip
|
142
143
|
|
143
|
-
_(@default.stylesheet(css).strip).must_equal %
|
144
|
+
_(@default.stylesheet(css).strip).must_equal %(
|
144
145
|
.foo { }
|
145
146
|
#bar { }
|
146
|
-
|
147
|
+
).strip
|
147
148
|
|
148
149
|
_(@relaxed.stylesheet(css)).must_equal css
|
149
150
|
|
150
|
-
_(@custom.stylesheet(css).strip).must_equal %
|
151
|
+
_(@custom.stylesheet(css).strip).must_equal %(
|
151
152
|
.foo { color: #fff; }
|
152
153
|
#bar { }
|
153
|
-
|
154
|
+
).strip
|
154
155
|
end
|
155
156
|
|
156
|
-
describe
|
157
|
-
it
|
158
|
-
_(@relaxed.stylesheet(
|
159
|
-
.must_equal
|
157
|
+
describe "when :allow_comments is true" do
|
158
|
+
it "should preserve comments" do
|
159
|
+
_(@relaxed.stylesheet(".foo { color: #fff; /* comment */ width: 100px; }"))
|
160
|
+
.must_equal ".foo { color: #fff; /* comment */ width: 100px; }"
|
160
161
|
|
161
162
|
_(@relaxed.stylesheet(".foo { color: #fff; /* \n\ncomment */ width: 100px; }"))
|
162
163
|
.must_equal ".foo { color: #fff; /* \n\ncomment */ width: 100px; }"
|
163
164
|
end
|
164
165
|
end
|
165
166
|
|
166
|
-
describe
|
167
|
-
it
|
168
|
-
_(@custom.stylesheet(
|
169
|
-
.must_equal
|
167
|
+
describe "when :allow_comments is false" do
|
168
|
+
it "should strip comments" do
|
169
|
+
_(@custom.stylesheet(".foo { color: #fff; /* comment */ width: 100px; }"))
|
170
|
+
.must_equal ".foo { color: #fff; width: 100px; }"
|
170
171
|
|
171
172
|
_(@custom.stylesheet(".foo { color: #fff; /* \n\ncomment */ width: 100px; }"))
|
172
|
-
.must_equal
|
173
|
+
.must_equal ".foo { color: #fff; width: 100px; }"
|
173
174
|
end
|
174
175
|
end
|
175
176
|
|
176
|
-
describe
|
177
|
-
it
|
178
|
-
_(@relaxed.stylesheet(
|
179
|
-
.must_equal
|
177
|
+
describe "when :allow_hacks is true" do
|
178
|
+
it "should allow common CSS hacks" do
|
179
|
+
_(@relaxed.stylesheet(".foo { _border: 1px solid #fff; *width: 10px }"))
|
180
|
+
.must_equal ".foo { _border: 1px solid #fff; *width: 10px }"
|
180
181
|
end
|
181
182
|
end
|
182
183
|
|
183
|
-
describe
|
184
|
-
it
|
185
|
-
_(@custom.stylesheet(
|
186
|
-
.must_equal
|
184
|
+
describe "when :allow_hacks is false" do
|
185
|
+
it "should not allow common CSS hacks" do
|
186
|
+
_(@custom.stylesheet(".foo { _border: 1px solid #fff; *width: 10px }"))
|
187
|
+
.must_equal ".foo { }"
|
187
188
|
end
|
188
189
|
end
|
189
190
|
end
|
190
191
|
|
191
|
-
describe
|
192
|
-
it
|
193
|
-
tree = Crass.parse(
|
194
|
-
".foo { background: #fff; font: 16pt 'Comic Sans MS'; }\n"
|
192
|
+
describe "#tree!" do
|
193
|
+
it "should sanitize a Crass CSS parse tree" do
|
194
|
+
tree = Crass.parse("@import url(foo.css);\n" \
|
195
|
+
".foo { background: #fff; font: 16pt 'Comic Sans MS'; }\n" \
|
195
196
|
"#bar { top: 125px; background: green; }")
|
196
197
|
|
197
198
|
_(@custom.tree!(tree)).must_be_same_as tree
|
198
199
|
|
199
|
-
_(Crass::Parser.stringify(tree)).must_equal
|
200
|
-
|
201
|
-
|
200
|
+
_(Crass::Parser.stringify(tree)).must_equal "\n" \
|
201
|
+
".foo { background: #fff; }\n" \
|
202
|
+
"#bar { background: green; }"
|
202
203
|
end
|
203
204
|
end
|
204
205
|
end
|
205
206
|
|
206
|
-
describe
|
207
|
-
describe
|
208
|
-
it
|
207
|
+
describe "class methods" do
|
208
|
+
describe ".properties" do
|
209
|
+
it "should sanitize CSS properties with the given config" do
|
209
210
|
css = 'background: #fff; width: expression(alert("hi"));'
|
210
211
|
|
211
|
-
_(Sanitize::CSS.properties(css)).must_equal
|
212
|
-
_(Sanitize::CSS.properties(css, Sanitize::Config::RELAXED[:css])).must_equal
|
213
|
-
_(Sanitize::CSS.properties(css, :
|
212
|
+
_(Sanitize::CSS.properties(css)).must_equal " "
|
213
|
+
_(Sanitize::CSS.properties(css, Sanitize::Config::RELAXED[:css])).must_equal "background: #fff; "
|
214
|
+
_(Sanitize::CSS.properties(css, properties: %w[background color width])).must_equal "background: #fff; "
|
214
215
|
end
|
215
216
|
end
|
216
217
|
|
217
|
-
describe
|
218
|
-
it
|
218
|
+
describe ".stylesheet" do
|
219
|
+
it "should sanitize a CSS stylesheet with the given config" do
|
219
220
|
css = %[
|
220
221
|
/* Yay CSS! */
|
221
222
|
.foo { color: #fff; }
|
@@ -227,43 +228,43 @@ describe 'Sanitize::CSS' do
|
|
227
228
|
}
|
228
229
|
].strip
|
229
230
|
|
230
|
-
_(Sanitize::CSS.stylesheet(css).strip).must_equal %
|
231
|
+
_(Sanitize::CSS.stylesheet(css).strip).must_equal %(
|
231
232
|
.foo { }
|
232
233
|
#bar { }
|
233
|
-
|
234
|
+
).strip
|
234
235
|
|
235
236
|
_(Sanitize::CSS.stylesheet(css, Sanitize::Config::RELAXED[:css])).must_equal css
|
236
237
|
|
237
|
-
_(Sanitize::CSS.stylesheet(css, :
|
238
|
+
_(Sanitize::CSS.stylesheet(css, properties: %w[background color width]).strip).must_equal %(
|
238
239
|
.foo { color: #fff; }
|
239
240
|
#bar { }
|
240
|
-
|
241
|
+
).strip
|
241
242
|
end
|
242
243
|
end
|
243
244
|
|
244
|
-
describe
|
245
|
-
it
|
246
|
-
tree = Crass.parse(
|
247
|
-
".foo { background: #fff; font: 16pt 'Comic Sans MS'; }\n"
|
245
|
+
describe ".tree!" do
|
246
|
+
it "should sanitize a Crass CSS parse tree with the given config" do
|
247
|
+
tree = Crass.parse("@import url(foo.css);\n" \
|
248
|
+
".foo { background: #fff; font: 16pt 'Comic Sans MS'; }\n" \
|
248
249
|
"#bar { top: 125px; background: green; }")
|
249
250
|
|
250
|
-
_(Sanitize::CSS.tree!(tree, :
|
251
|
+
_(Sanitize::CSS.tree!(tree, properties: %w[background color width])).must_be_same_as tree
|
251
252
|
|
252
|
-
_(Crass::Parser.stringify(tree)).must_equal
|
253
|
-
|
254
|
-
|
253
|
+
_(Crass::Parser.stringify(tree)).must_equal "\n" \
|
254
|
+
".foo { background: #fff; }\n" \
|
255
|
+
"#bar { background: green; }"
|
255
256
|
end
|
256
257
|
end
|
257
258
|
end
|
258
259
|
|
259
|
-
describe
|
260
|
+
describe "functionality" do
|
260
261
|
before do
|
261
262
|
@default = Sanitize::CSS.new
|
262
263
|
@relaxed = Sanitize::CSS.new(Sanitize::Config::RELAXED[:css])
|
263
264
|
end
|
264
265
|
|
265
266
|
# https://github.com/rgrove/sanitize/issues/121
|
266
|
-
it
|
267
|
+
it "should parse the contents of @media rules properly" do
|
267
268
|
css = '@media { p[class="center"] { text-align: center; }}'
|
268
269
|
_(@relaxed.stylesheet(css)).must_equal css
|
269
270
|
|
@@ -290,7 +291,7 @@ describe 'Sanitize::CSS' do
|
|
290
291
|
].strip
|
291
292
|
end
|
292
293
|
|
293
|
-
it
|
294
|
+
it "should parse @page rules properly" do
|
294
295
|
css = %[
|
295
296
|
@page { margin: 2cm } /* All margins set to 2cm */
|
296
297
|
|
@@ -323,15 +324,50 @@ describe 'Sanitize::CSS' do
|
|
323
324
|
.foo { color: green; }
|
324
325
|
].strip
|
325
326
|
|
326
|
-
_(@relaxed.stylesheet(css).strip).must_equal %
|
327
|
+
_(@relaxed.stylesheet(css).strip).must_equal %(
|
327
328
|
.foo { color: green; }
|
328
|
-
|
329
|
+
).strip
|
330
|
+
end
|
331
|
+
|
332
|
+
it "preserves allowlisted @container at-rules" do
|
333
|
+
# Sample code courtesy of MDN:
|
334
|
+
# https://developer.mozilla.org/en-US/docs/Web/CSS/@container
|
335
|
+
css = %(
|
336
|
+
@container (width > 400px) {
|
337
|
+
h2 {
|
338
|
+
font-size: 1.5em;
|
339
|
+
}
|
340
|
+
}
|
341
|
+
|
342
|
+
/* with an optional <container-name> */
|
343
|
+
@container tall (height > 30rem) {
|
344
|
+
h2 {
|
345
|
+
line-height: 1.6;
|
346
|
+
}
|
347
|
+
}
|
348
|
+
|
349
|
+
/* multiple queries in a single condition */
|
350
|
+
@container (width > 400px) and style(--responsive: true) {
|
351
|
+
h2 {
|
352
|
+
font-size: 1.5em;
|
353
|
+
}
|
354
|
+
}
|
355
|
+
|
356
|
+
/* condition list */
|
357
|
+
@container card (width > 400px), style(--responsive: true) {
|
358
|
+
h2 {
|
359
|
+
font-size: 1.5em;
|
360
|
+
}
|
361
|
+
}
|
362
|
+
).strip
|
363
|
+
|
364
|
+
_(@relaxed.stylesheet(css).strip).must_equal css
|
329
365
|
end
|
330
366
|
|
331
367
|
describe "when blockless at-rules are allowlisted" do
|
332
368
|
before do
|
333
369
|
@scss = Sanitize::CSS.new(Sanitize::Config.merge(Sanitize::Config::RELAXED[:css], {
|
334
|
-
:
|
370
|
+
at_rules: ["charset", "import"]
|
335
371
|
}))
|
336
372
|
end
|
337
373
|
|
@@ -350,24 +386,23 @@ describe 'Sanitize::CSS' do
|
|
350
386
|
end
|
351
387
|
|
352
388
|
it "should remove them if they have invalid blocks" do
|
353
|
-
css = %
|
389
|
+
css = %(
|
354
390
|
@charset { color: green }
|
355
391
|
@import { color: green }
|
356
392
|
.foo { color: green; }
|
357
|
-
|
393
|
+
).strip
|
358
394
|
|
359
|
-
_(@scss.stylesheet(css).strip).must_equal %
|
395
|
+
_(@scss.stylesheet(css).strip).must_equal %(
|
360
396
|
.foo { color: green; }
|
361
|
-
|
397
|
+
).strip
|
362
398
|
end
|
363
399
|
end
|
364
400
|
|
365
401
|
describe "when validating @import rules" do
|
366
|
-
|
367
402
|
describe "with no validation proc specified" do
|
368
403
|
before do
|
369
404
|
@scss = Sanitize::CSS.new(Sanitize::Config.merge(Sanitize::Config::RELAXED[:css], {
|
370
|
-
:
|
405
|
+
at_rules: ["import"]
|
371
406
|
}))
|
372
407
|
end
|
373
408
|
|
@@ -384,10 +419,10 @@ describe 'Sanitize::CSS' do
|
|
384
419
|
|
385
420
|
describe "with a validation proc specified" do
|
386
421
|
before do
|
387
|
-
google_font_validator =
|
422
|
+
google_font_validator = proc { |url| url.start_with?("https://fonts.googleapis.com") }
|
388
423
|
|
389
424
|
@scss = Sanitize::CSS.new(Sanitize::Config.merge(Sanitize::Config::RELAXED[:css], {
|
390
|
-
:
|
425
|
+
at_rules: ["import"], import_url_validator: google_font_validator
|
391
426
|
}))
|
392
427
|
end
|
393
428
|
|
@@ -410,9 +445,9 @@ describe 'Sanitize::CSS' do
|
|
410
445
|
@import url('https://nastysite.com/nasty_hax0r.css');
|
411
446
|
].strip
|
412
447
|
|
413
|
-
_(@scss.stylesheet(css).strip).must_equal %
|
448
|
+
_(@scss.stylesheet(css).strip).must_equal %(
|
414
449
|
@import 'https://fonts.googleapis.com/css?family=Indie+Flower';
|
415
|
-
|
450
|
+
).strip
|
416
451
|
end
|
417
452
|
|
418
453
|
it "should not allow a blank url" do
|
@@ -422,9 +457,9 @@ describe 'Sanitize::CSS' do
|
|
422
457
|
@import url('');
|
423
458
|
].strip
|
424
459
|
|
425
|
-
_(@scss.stylesheet(css).strip).must_equal %
|
460
|
+
_(@scss.stylesheet(css).strip).must_equal %(
|
426
461
|
@import 'https://fonts.googleapis.com/css?family=Indie+Flower';
|
427
|
-
|
462
|
+
).strip
|
428
463
|
end
|
429
464
|
end
|
430
465
|
end
|