dandruff 0.8.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 +7 -0
- data/.rubocop.yml +23 -0
- data/CHANGELOG.md +69 -0
- data/COMPARISON.md +175 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +142 -0
- data/LICENSE.txt +21 -0
- data/Makefile +41 -0
- data/README.md +1196 -0
- data/Rakefile +12 -0
- data/examples/basic_usage.rb +84 -0
- data/examples/email_sanitization_example.md +268 -0
- data/failed-expectations.md +192 -0
- data/lib/dandruff/attributes.rb +223 -0
- data/lib/dandruff/config.rb +500 -0
- data/lib/dandruff/expressions.rb +103 -0
- data/lib/dandruff/tags.rb +160 -0
- data/lib/dandruff/utils.rb +27 -0
- data/lib/dandruff/version.rb +5 -0
- data/lib/dandruff.rb +1095 -0
- metadata +134 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
|
|
6
|
+
require 'dandruff'
|
|
7
|
+
|
|
8
|
+
# Example usage demonstrating Dandruff Ruby functionality
|
|
9
|
+
|
|
10
|
+
puts 'Dandruff Ruby Examples'
|
|
11
|
+
puts '========================'
|
|
12
|
+
puts
|
|
13
|
+
|
|
14
|
+
# Basic sanitization
|
|
15
|
+
puts '1. Basic sanitization:'
|
|
16
|
+
dirty = '<script>alert("xss")</script><p>Safe content</p>'
|
|
17
|
+
dandruff = Dandruff.new
|
|
18
|
+
clean = dandruff.sanitize(dirty)
|
|
19
|
+
puts " Original: #{dirty}"
|
|
20
|
+
puts " Clean: #{clean}"
|
|
21
|
+
puts
|
|
22
|
+
|
|
23
|
+
# Removing dangerous attributes
|
|
24
|
+
puts '2. Removing dangerous attributes:'
|
|
25
|
+
dirty = '<div onclick="alert(\'xss\')" class="safe">Click me</div>'
|
|
26
|
+
dandruff = Dandruff.new
|
|
27
|
+
clean = dandruff.sanitize(dirty)
|
|
28
|
+
puts " Original: #{dirty}"
|
|
29
|
+
puts " Clean: #{clean}"
|
|
30
|
+
puts
|
|
31
|
+
|
|
32
|
+
# Configuration example
|
|
33
|
+
puts '3. Custom configuration:'
|
|
34
|
+
dandruff = Dandruff.new do |c|
|
|
35
|
+
c.allowed_tags = %w[p b i]
|
|
36
|
+
c.allowed_attributes = ['class']
|
|
37
|
+
end
|
|
38
|
+
dirty = '<p class="text"><b>Bold</b> <i>Italic</i></p>'
|
|
39
|
+
clean = dandruff.sanitize(dirty)
|
|
40
|
+
puts " Original: #{dirty}"
|
|
41
|
+
puts " Clean: #{clean}"
|
|
42
|
+
puts
|
|
43
|
+
|
|
44
|
+
# Data attributes
|
|
45
|
+
puts '4. Data attributes:'
|
|
46
|
+
dirty = '<div data-user="123" data-role="admin">User content</div>'
|
|
47
|
+
dandruff = Dandruff.new
|
|
48
|
+
clean = dandruff.sanitize(dirty)
|
|
49
|
+
puts " Original: #{dirty}"
|
|
50
|
+
puts " Clean: #{clean}"
|
|
51
|
+
puts
|
|
52
|
+
|
|
53
|
+
# Template safety
|
|
54
|
+
puts '5. Template safety:'
|
|
55
|
+
dirty = '<p>{{ user_input }}</p><script>${malicious()}</script>'
|
|
56
|
+
dandruff = Dandruff.new
|
|
57
|
+
clean = dandruff.sanitize(dirty, safe_for_templates: true)
|
|
58
|
+
puts " Original: #{dirty}"
|
|
59
|
+
puts " Clean: #{clean}"
|
|
60
|
+
puts
|
|
61
|
+
|
|
62
|
+
# Hooks example
|
|
63
|
+
puts '6. Using hooks:'
|
|
64
|
+
hook_called = false
|
|
65
|
+
dandruff = Dandruff.new
|
|
66
|
+
dandruff.add_hook(:before_sanitize_elements) do |node, _data, _config|
|
|
67
|
+
hook_called = true
|
|
68
|
+
puts " Hook: Processing #{node.name} element"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
dandruff.sanitize('<div>Test</div>')
|
|
72
|
+
puts " Hook was called: #{hook_called}"
|
|
73
|
+
puts
|
|
74
|
+
|
|
75
|
+
# Profiles
|
|
76
|
+
puts '7. Using profiles:'
|
|
77
|
+
dandruff = Dandruff.new(use_profiles: { svg: true })
|
|
78
|
+
dirty = '<svg><circle r="10" fill="red"/></svg>'
|
|
79
|
+
clean = dandruff.sanitize(dirty)
|
|
80
|
+
puts " Original: #{dirty}"
|
|
81
|
+
puts " Clean: #{clean}"
|
|
82
|
+
puts
|
|
83
|
+
|
|
84
|
+
puts 'Examples completed!'
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# HTML Email Sanitization Example
|
|
2
|
+
|
|
3
|
+
This example demonstrates how to use Dandruff to safely sanitize HTML emails for display, protecting against XSS while preserving formatting.
|
|
4
|
+
|
|
5
|
+
## Use Case
|
|
6
|
+
|
|
7
|
+
When displaying HTML emails from external sources in a web application, you need to:
|
|
8
|
+
- Remove malicious scripts and XSS payloads
|
|
9
|
+
- Preserve email formatting (tables, styling, links)
|
|
10
|
+
- Allow safe email content like images and basic formatting
|
|
11
|
+
- Prevent iframe escape attempts
|
|
12
|
+
|
|
13
|
+
## Implementation
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
require 'dandruff'
|
|
17
|
+
|
|
18
|
+
class EmailSanitizer
|
|
19
|
+
def self.sanitize_for_iframe(html_content)
|
|
20
|
+
# Configure Dandruff for email content
|
|
21
|
+
dandruff = Dandruff.new do |c|
|
|
22
|
+
# Allow email-specific tags
|
|
23
|
+
c.allowed_tags = [
|
|
24
|
+
# Basic formatting
|
|
25
|
+
'p', 'br', 'div', 'span',
|
|
26
|
+
# Text formatting
|
|
27
|
+
'strong', 'b', 'em', 'i', 'u', 's', 'strike',
|
|
28
|
+
# Headings
|
|
29
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
30
|
+
# Lists
|
|
31
|
+
'ul', 'ol', 'li', 'dl', 'dt', 'dd',
|
|
32
|
+
# Tables (essential for emails)
|
|
33
|
+
'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption',
|
|
34
|
+
# Links and images
|
|
35
|
+
'a', 'img',
|
|
36
|
+
# Block elements
|
|
37
|
+
'blockquote', 'hr', 'pre', 'code',
|
|
38
|
+
# Email-specific
|
|
39
|
+
'font', 'center'
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
# Allow safe attributes
|
|
43
|
+
c.allowed_attributes = [
|
|
44
|
+
# Links
|
|
45
|
+
'href', 'title', 'target',
|
|
46
|
+
# Images
|
|
47
|
+
'src', 'alt', 'width', 'height', 'border',
|
|
48
|
+
# Tables
|
|
49
|
+
'cellpadding', 'cellspacing', 'colspan', 'rowspan', 'align', 'valign',
|
|
50
|
+
# Text formatting
|
|
51
|
+
'color', 'size', 'face', 'style', 'class',
|
|
52
|
+
# Lists
|
|
53
|
+
'type', 'start',
|
|
54
|
+
# General
|
|
55
|
+
'id', 'dir'
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
# Security settings
|
|
59
|
+
c.forbidden_tags = ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button']
|
|
60
|
+
c.forbidden_attributes = ['onclick', 'onload', 'onerror', 'onmouseover', 'javascript:']
|
|
61
|
+
|
|
62
|
+
# Allow data attributes for tracking (optional)
|
|
63
|
+
c.allow_data_attributes = true
|
|
64
|
+
|
|
65
|
+
# Allow ARIA attributes for accessibility
|
|
66
|
+
c.allow_aria_attributes = true
|
|
67
|
+
|
|
68
|
+
# Safe for templates (remove template expressions)
|
|
69
|
+
c.safe_for_templates = true
|
|
70
|
+
|
|
71
|
+
# Keep comments for email client compatibility
|
|
72
|
+
c.safe_for_xml = false
|
|
73
|
+
|
|
74
|
+
# Don't return whole document (we want fragment)
|
|
75
|
+
c.whole_document = false
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Add hooks for additional security
|
|
79
|
+
add_email_security_hooks(dandruff)
|
|
80
|
+
|
|
81
|
+
# Sanitize the email content
|
|
82
|
+
clean_html = dandruff.sanitize(html_content)
|
|
83
|
+
|
|
84
|
+
# Post-process for iframe safety
|
|
85
|
+
post_process_for_iframe(clean_html)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def self.add_email_security_hooks(dandruff)
|
|
91
|
+
# Hook to sanitize URLs
|
|
92
|
+
dandruff.add_hook(:upon_sanitize_attribute) do |node, data, config|
|
|
93
|
+
case data[:attr_name]
|
|
94
|
+
when 'href'
|
|
95
|
+
# Block dangerous protocols
|
|
96
|
+
href = data[:value]
|
|
97
|
+
if href.match?(/\b(javascript|data|vbscript):/i)
|
|
98
|
+
data[:keep_attr] = false
|
|
99
|
+
end
|
|
100
|
+
when 'src'
|
|
101
|
+
# Only allow http/https protocols for images
|
|
102
|
+
src = data[:value]
|
|
103
|
+
unless src.match?(/\b(https?):/i)
|
|
104
|
+
data[:keep_attr] = false
|
|
105
|
+
end
|
|
106
|
+
when 'style'
|
|
107
|
+
# Remove dangerous CSS
|
|
108
|
+
style = data[:value]
|
|
109
|
+
if style.match?(/(expression|javascript|behavior|import)/i)
|
|
110
|
+
data[:keep_attr] = false
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Hook to process table elements
|
|
116
|
+
dandruff.add_hook(:before_sanitize_elements) do |node, data, config|
|
|
117
|
+
# Remove tables with excessive nesting (potential DoS)
|
|
118
|
+
if node.name == 'table'
|
|
119
|
+
depth = count_table_depth(node)
|
|
120
|
+
if depth > 10
|
|
121
|
+
data[:keep_node] = false
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def self.count_table_depth(node, depth = 0)
|
|
128
|
+
return depth if depth > 10
|
|
129
|
+
|
|
130
|
+
max_depth = depth
|
|
131
|
+
node.children.each do |child|
|
|
132
|
+
if child.name == 'table'
|
|
133
|
+
child_depth = count_table_depth(child, depth + 1)
|
|
134
|
+
max_depth = [max_depth, child_depth].max
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
max_depth
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def self.post_process_for_iframe(html)
|
|
142
|
+
# Add sandbox attributes if iframe will be used
|
|
143
|
+
# This is typically done in the HTML template, but we document it here
|
|
144
|
+
|
|
145
|
+
# Example iframe usage:
|
|
146
|
+
# <iframe sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox"
|
|
147
|
+
# srcdoc="#{sanitized_html}">
|
|
148
|
+
# </iframe>
|
|
149
|
+
|
|
150
|
+
html
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Usage example
|
|
155
|
+
class EmailController < ApplicationController
|
|
156
|
+
def show
|
|
157
|
+
# Get email from database or external source
|
|
158
|
+
raw_email = Email.find(params[:id]).html_content
|
|
159
|
+
|
|
160
|
+
# Sanitize for safe display
|
|
161
|
+
@sanitized_email = EmailSanitizer.sanitize_for_iframe(raw_email)
|
|
162
|
+
|
|
163
|
+
# Render with iframe
|
|
164
|
+
# In your view:
|
|
165
|
+
# <iframe sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox"
|
|
166
|
+
# srcdoc="#{@sanitized_email}"
|
|
167
|
+
# width="100%" height="600">
|
|
168
|
+
# </iframe>
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Security Considerations
|
|
174
|
+
|
|
175
|
+
### 1. **Iframe Sandbox Attributes**
|
|
176
|
+
```html
|
|
177
|
+
<iframe sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox"
|
|
178
|
+
srcdoc="#{@sanitized_email}"
|
|
179
|
+
width="100%" height="600">
|
|
180
|
+
</iframe>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 2. **Content Security Policy**
|
|
184
|
+
```ruby
|
|
185
|
+
# In your Rails app or web server config
|
|
186
|
+
response.headers['Content-Security-Policy'] =
|
|
187
|
+
"default-src 'self'; " \
|
|
188
|
+
"img-src https: data:; " \
|
|
189
|
+
"style-src 'unsafe-inline'; " \
|
|
190
|
+
"script-src 'none';"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 3. **Additional Protections**
|
|
194
|
+
- Rate limiting email processing
|
|
195
|
+
- File size limits for email content
|
|
196
|
+
- Virus scanning for attachments
|
|
197
|
+
- Logging of sanitization attempts
|
|
198
|
+
|
|
199
|
+
## Testing the Sanitizer
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
# Test malicious content
|
|
203
|
+
malicious_email = <<~HTML
|
|
204
|
+
<html>
|
|
205
|
+
<body>
|
|
206
|
+
<h1>Important Email</h1>
|
|
207
|
+
<p>Click <a href="javascript:alert('XSS')">here</a> for prize!</p>
|
|
208
|
+
<script>steal_data();</script>
|
|
209
|
+
<iframe src="http://evil.com"></iframe>
|
|
210
|
+
<table>
|
|
211
|
+
<tr><td>Safe content</td></tr>
|
|
212
|
+
</table>
|
|
213
|
+
<img src="data:image/svg+xml;base64,PHN2ZyBvbmxvYWQ9YWxlcnQoJ1hTUyknPg==" />
|
|
214
|
+
</body>
|
|
215
|
+
</html>
|
|
216
|
+
HTML
|
|
217
|
+
|
|
218
|
+
sanitized = EmailSanitizer.sanitize_for_iframe(malicious_email)
|
|
219
|
+
puts sanitized
|
|
220
|
+
# => <h1>Important Email</h1><p>Click <a>here</a> for prize!</p><table><tbody><tr><td>Safe content</td></tr></tbody></table>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Performance Considerations
|
|
224
|
+
|
|
225
|
+
- Cache sanitized emails when possible
|
|
226
|
+
- Process emails asynchronously for large volumes
|
|
227
|
+
- Monitor memory usage with large email attachments
|
|
228
|
+
- Consider streaming for very large email content
|
|
229
|
+
|
|
230
|
+
## Integration Examples
|
|
231
|
+
|
|
232
|
+
### Rails Integration
|
|
233
|
+
```ruby
|
|
234
|
+
# app/helpers/email_helper.rb
|
|
235
|
+
module EmailHelper
|
|
236
|
+
def safe_email_iframe(email_content)
|
|
237
|
+
sanitized = EmailSanitizer.sanitize_for_iframe(email_content)
|
|
238
|
+
content_tag(:iframe, '',
|
|
239
|
+
sandbox: 'allow-same-origin allow-popups allow-popups-to-escape-sandbox',
|
|
240
|
+
srcdoc: sanitized,
|
|
241
|
+
width: '100%',
|
|
242
|
+
height: '600',
|
|
243
|
+
style: 'border: 1px solid #ccc;')
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# In view:
|
|
248
|
+
# <%= safe_email_iframe(@email.html_content) %>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Sinatra Integration
|
|
252
|
+
```ruby
|
|
253
|
+
# Email processing endpoint
|
|
254
|
+
post '/emails/sanitize' do
|
|
255
|
+
content_type :json
|
|
256
|
+
|
|
257
|
+
begin
|
|
258
|
+
raw_html = request.body.read
|
|
259
|
+
sanitized = EmailSanitizer.sanitize_for_iframe(raw_html)
|
|
260
|
+
|
|
261
|
+
{ success: true, html: sanitized }.to_json
|
|
262
|
+
rescue => e
|
|
263
|
+
{ success: false, error: e.message }.to_json
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
This example provides a comprehensive solution for safely displaying HTML emails in iframes while maintaining security and preserving email formatting.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Root Causes for Scrubber vs DOMPurify Expectation Failures
|
|
2
|
+
|
|
3
|
+
This document analyzes the root causes of why the Scrubber library produces different outputs compared to the expected DOMPurify behaviors in the test suite.
|
|
4
|
+
|
|
5
|
+
## Summary
|
|
6
|
+
|
|
7
|
+
The Scrubber library's default configuration is more restrictive than DOMPurify's in several areas:
|
|
8
|
+
- Data URIs are blocked by default
|
|
9
|
+
- Certain HTML attributes (e.g., `checked`, `selected`) are not allowed by default
|
|
10
|
+
- SVG and MathML attribute handling differs
|
|
11
|
+
- DOM clobbering protection is more aggressive
|
|
12
|
+
- Some attributes expected to be preserved are removed
|
|
13
|
+
|
|
14
|
+
## Detailed Root Causes by Failure
|
|
15
|
+
|
|
16
|
+
### 1. Data URI Handling
|
|
17
|
+
**Failures:** Don't remove data URIs from SVG images, Don't remove data URIs from SVG images with href attribute, src Attributes for IMG, AUDIO, VIDEO and SOURCE, Image with data URI src, Image with data URI src with whitespace
|
|
18
|
+
|
|
19
|
+
**Root Cause:** Scrubber blocks data URIs by default (`allow_data_uri = false`), while DOMPurify allows data URIs for specific attributes on specific tags (img src, audio src, video src, source src, SVG image xlink:href/href). The URI validation regex `IS_ALLOWED_URI` does not include `data:` protocol, and the special case for data URIs requires `allow_data_uri = true` or tag-specific allowances.
|
|
20
|
+
|
|
21
|
+
**Remediation Recommendation:** Implement via `dompurify` profile to allow data URIs for specific safe tags. Keep default restrictive for security. Security implications: Data URIs can contain embedded scripts or large payloads that could cause performance issues, but restricting to specific tags and validating content types mitigates most risks.
|
|
22
|
+
|
|
23
|
+
**Code Location:** `lib/scrubber.rb` `valid_attribute?` method, `lib/scrubber/expressions.rb` `IS_ALLOWED_URI`
|
|
24
|
+
|
|
25
|
+
### 2. ARIA Attributes
|
|
26
|
+
**Failures:** Don't remove ARIA attributes if not prohibited
|
|
27
|
+
|
|
28
|
+
**Root Cause:** Despite `allow_aria_attributes = true` in default config, ARIA attributes are being removed. This may be due to the attribute validation logic not properly applying the permissive checks, or a bug in the order of validation.
|
|
29
|
+
|
|
30
|
+
**Remediation Recommendation:** Fix the bug in `valid_attribute?` to properly apply `allow_aria_attributes` logic. Alternatively, ensure ARIA attributes are included in the `dompurify` profile's allowed attributes. Keep the default as allowing ARIA attributes since they are essential for accessibility and pose no security risk.
|
|
31
|
+
|
|
32
|
+
**Code Location:** `lib/scrubber.rb` `valid_attribute?` method, config `allow_aria_attributes`
|
|
33
|
+
|
|
34
|
+
### 3. Binary Attributes
|
|
35
|
+
**Failures:** Don't remove binary attributes if considered safe
|
|
36
|
+
|
|
37
|
+
**Root Cause:** Attributes like `checked` are not included in the default allowed attributes list. DOMPurify allows these HTML boolean attributes by default, but Scrubber requires them to be explicitly allowed.
|
|
38
|
+
|
|
39
|
+
**Remediation Recommendation:** Implement via `dompurify` profile by expanding the allowed attributes list to include common HTML boolean attributes. Keep defaults restrictive for security. Security implications: These attributes are safe and commonly used in HTML forms; they don't introduce XSS risks.
|
|
40
|
+
|
|
41
|
+
**Code Location:** `lib/scrubber/attributes.rb` `HTML` list, `lib/scrubber.rb` `valid_attribute?` default checks
|
|
42
|
+
|
|
43
|
+
### 4. SVG Filter Elements
|
|
44
|
+
**Failures:** Avoid over-zealous stripping of SVG filter elements
|
|
45
|
+
|
|
46
|
+
**Root Cause:** SVG filter elements and their attributes are not fully supported in the default allowed tags/attributes. The `feGaussianBlur` element and its `in` and `stdDeviation` attributes are being removed.
|
|
47
|
+
|
|
48
|
+
**Remediation Recommendation:** Relax restrictions by ensuring all SVG filter elements and their standard attributes are included in the default allowed lists. Security implications: SVG filters are generally safe for display purposes and are commonly used in web graphics; they don't execute scripts.
|
|
49
|
+
|
|
50
|
+
**Code Location:** `lib/scrubber/tags.rb` `SVG_FILTERS`, `lib/scrubber/attributes.rb` `SVG`
|
|
51
|
+
|
|
52
|
+
### 5. URI-like Attributes
|
|
53
|
+
**Failures:** safe usage of URI-like attribute values
|
|
54
|
+
|
|
55
|
+
**Root Cause:** The `href` attribute is removed when it contains `javascript:`, but `title` is kept. This is correct behavior for security, but the test expects both to be handled differently.
|
|
56
|
+
|
|
57
|
+
**Remediation Recommendation:** Keep the more restrictive mode for URI attributes like `href` to prevent XSS, while allowing non-URI attributes like `title` to contain URI-like strings. Security implications: This is actually a security feature - blocking `javascript:` in navigation attributes prevents XSS attacks.
|
|
58
|
+
|
|
59
|
+
**Code Location:** `lib/scrubber.rb` `valid_attribute?` URI validation
|
|
60
|
+
|
|
61
|
+
### 6. DOM Clobbering Protection
|
|
62
|
+
**Failures:** Multiple DOM clobbering related failures (cookie, getElementById, nodeName, etc.)
|
|
63
|
+
|
|
64
|
+
**Root Cause:** Scrubber's DOM clobbering protection is more aggressive than DOMPurify's. Attributes with names that could shadow DOM properties are removed, even when DOMPurify allows them.
|
|
65
|
+
|
|
66
|
+
**Remediation Recommendation:** Keep the more restrictive mode for DOM clobbering protection as it provides better security. The `dompurify` profile could optionally disable this for compatibility. Security implications: DOM clobbering can be used to manipulate JavaScript code execution; being more restrictive prevents potential security vulnerabilities at the cost of some compatibility.
|
|
67
|
+
|
|
68
|
+
**Code Location:** `lib/scrubber.rb` `valid_attribute?` DOM clobbering check, `lib/scrubber/attributes.rb` `DOM_CLOBBERING`
|
|
69
|
+
|
|
70
|
+
### 7. MathML Handling
|
|
71
|
+
**Failures:** MathML example
|
|
72
|
+
|
|
73
|
+
**Root Cause:** MathML elements and attributes are not properly supported or the serialization differs from DOMPurify's output.
|
|
74
|
+
|
|
75
|
+
**Remediation Recommendation:** Relax restrictions by improving MathML support to match DOMPurify's handling. Security implications: MathML is generally safe for mathematical content display and doesn't execute scripts; better support improves compatibility without significant security risks.
|
|
76
|
+
|
|
77
|
+
**Code Location:** `lib/scrubber/tags.rb` `MATH_ML`, `lib/scrubber/attributes.rb` `MATH_ML`
|
|
78
|
+
|
|
79
|
+
### 8. Template and Shadow DOM
|
|
80
|
+
**Failures:** Img element inside shadow DOM template
|
|
81
|
+
|
|
82
|
+
**Root Cause:** Template elements and their contents are not handled the same way as DOMPurify.
|
|
83
|
+
|
|
84
|
+
**Remediation Recommendation:** Keep the more restrictive mode for template elements as they can contain executable content. Security implications: Template elements can be used to inject scripts that execute when the template is instantiated; being restrictive prevents potential XSS through template injection.
|
|
85
|
+
|
|
86
|
+
**Code Location:** `lib/scrubber.rb` `sanitize_document` forbidden tags
|
|
87
|
+
|
|
88
|
+
### 9. Style Attributes
|
|
89
|
+
**Failures:** Multiple style-related failures (p[foo=bar], @import, etc.)
|
|
90
|
+
|
|
91
|
+
**Root Cause:** CSS sanitization is more restrictive, removing valid CSS that DOMPurify allows.
|
|
92
|
+
|
|
93
|
+
**Remediation Recommendation:** Partially relax restrictions for safe CSS properties while keeping restrictions on dangerous ones like `@import` with external URLs. Security implications: CSS can be used for XSS (e.g., `expression()` in IE, `url()` with javascript), so selective relaxation is needed to balance security and functionality.
|
|
94
|
+
|
|
95
|
+
**Code Location:** `lib/scrubber.rb` `sanitize_style_value`, `unsafe_inline_style?`
|
|
96
|
+
|
|
97
|
+
### 10. Form and Input Handling
|
|
98
|
+
**Failures:** Various form and input attribute failures (type, method, etc.)
|
|
99
|
+
|
|
100
|
+
**Root Cause:** Form-related attributes are not all allowed by default.
|
|
101
|
+
|
|
102
|
+
**Remediation Recommendation:** Relax restrictions by adding common form attributes to the default allowed list. Security implications: Form attributes are generally safe and necessary for HTML forms; they don't introduce XSS risks when properly validated.
|
|
103
|
+
|
|
104
|
+
**Code Location:** `lib/scrubber/attributes.rb` `HTML`
|
|
105
|
+
|
|
106
|
+
### 11. SVG and XML Namespaces
|
|
107
|
+
**Failures:** SVG namespace handling
|
|
108
|
+
|
|
109
|
+
**Root Cause:** XML namespace attributes and SVG-specific handling differs.
|
|
110
|
+
|
|
111
|
+
**Remediation Recommendation:** Relax restrictions to properly support SVG namespaces. Security implications: Namespaces are metadata and don't execute code; proper support improves SVG compatibility without security risks.
|
|
112
|
+
|
|
113
|
+
**Code Location:** `lib/scrubber.rb` `sanitize_element` namespace check
|
|
114
|
+
|
|
115
|
+
### 12. Comment and Script Handling
|
|
116
|
+
**Failures:** Noscript content handling
|
|
117
|
+
|
|
118
|
+
**Root Cause:** Comment and script content sanitization differs.
|
|
119
|
+
|
|
120
|
+
**Remediation Recommendation:** Keep the more restrictive mode for comments and scripts. Security implications: Comments can contain sensitive information, and scripts are inherently dangerous; removing them prevents information leakage and script injection.
|
|
121
|
+
|
|
122
|
+
**Code Location:** `lib/scrubber.rb` `sanitize_comment_node`
|
|
123
|
+
|
|
124
|
+
### 13. CDATA and Special Content
|
|
125
|
+
**Failures:** SVG with CDATA
|
|
126
|
+
|
|
127
|
+
**Root Cause:** CDATA sections and special HTML constructs are handled differently.
|
|
128
|
+
|
|
129
|
+
**Remediation Recommendation:** Adjust serialization to match DOMPurify's handling of CDATA and special content. Security implications: CDATA sections can contain script-like content, so careful validation is needed, but matching DOMPurify's behavior improves compatibility.
|
|
130
|
+
|
|
131
|
+
**Code Location:** `lib/scrubber.rb` `serialize_html`
|
|
132
|
+
|
|
133
|
+
### 14. Attribute Value Sanitization
|
|
134
|
+
**Failures:** Various attribute value transformations
|
|
135
|
+
|
|
136
|
+
**Root Cause:** Attribute value processing (encoding, decoding, validation) differs from DOMPurify.
|
|
137
|
+
|
|
138
|
+
**Remediation Recommendation:** Adjust value processing to match DOMPurify's behavior while maintaining security. Security implications: Proper value sanitization prevents XSS through attribute injection; changes should preserve security guarantees.
|
|
139
|
+
|
|
140
|
+
**Code Location:** `lib/scrubber.rb` `valid_attribute?` value processing
|
|
141
|
+
|
|
142
|
+
### 15. Element Content Preservation
|
|
143
|
+
**Failures:** Content preservation in certain elements
|
|
144
|
+
|
|
145
|
+
**Root Cause:** The `keep_content` logic differs for certain forbidden elements.
|
|
146
|
+
|
|
147
|
+
**Remediation Recommendation:** Keep the more restrictive mode for content preservation. Security implications: Preserving content from forbidden elements can lead to information leakage or script execution; being restrictive prevents these issues.
|
|
148
|
+
|
|
149
|
+
**Code Location:** `lib/scrubber.rb` `sanitize_element`
|
|
150
|
+
|
|
151
|
+
## Alternative Solution: DOMPurify Profile
|
|
152
|
+
|
|
153
|
+
Most of these discrepancies could be addressed by adding a `dompurify` profile that configures Scrubber to match DOMPurify's more permissive behavior. This would allow users to opt into DOMPurify-compatible sanitization while maintaining Scrubber's secure defaults.
|
|
154
|
+
|
|
155
|
+
### Profile Implementation
|
|
156
|
+
|
|
157
|
+
Add a `dompurify` profile to `process_profiles` in `lib/scrubber/config.rb`:
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
if @use_profiles[:dompurify]
|
|
161
|
+
@allowed_tags += Tags::HTML + Tags::SVG + Tags::SVG_FILTERS + Tags::MATH_ML
|
|
162
|
+
@allowed_attributes += Attributes::HTML + Attributes::SVG + Attributes::MATH_ML + Attributes::XML
|
|
163
|
+
@allow_data_uri = true # Allow data URIs for compatible tags
|
|
164
|
+
@allow_unknown_protocols = true # More permissive protocol handling
|
|
165
|
+
@sanitize_dom = false # Less aggressive DOM clobbering protection
|
|
166
|
+
@allow_style_tags = true # Allow style tags
|
|
167
|
+
# Add more permissive settings as needed
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Usage
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
Scrubber.new(dompurify: true).sanitize(html)
|
|
175
|
+
# or
|
|
176
|
+
Scrubber.new(use_profiles: { dompurify: true }).sanitize(html)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Benefits
|
|
180
|
+
|
|
181
|
+
- **Maintains Security by Default:** Keeps Scrubber's restrictive defaults for security-conscious users
|
|
182
|
+
- **Provides Compatibility:** Offers DOMPurify-like behavior when needed
|
|
183
|
+
- **Selective Relaxation:** Only relaxes restrictions where DOMPurify differs, maintaining security where possible
|
|
184
|
+
- **Backward Compatible:** Doesn't break existing code
|
|
185
|
+
|
|
186
|
+
### Security Considerations
|
|
187
|
+
|
|
188
|
+
The `dompurify` profile would be less secure than Scrubber's defaults but more compatible with DOMPurify. Users should understand the trade-offs when enabling this profile.
|
|
189
|
+
|
|
190
|
+
### Implementation Priority
|
|
191
|
+
|
|
192
|
+
Most root causes (1-4, 7, 9-11, 13-14) could be addressed through the `dompurify` profile, while security-critical areas (5-6, 8, 12, 15) should remain restrictive by default with optional relaxation in the profile.
|