loofah 2.25.1 → 2.25.2
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 +15 -0
- data/lib/loofah/html5/safelist.rb +6 -2
- data/lib/loofah/html5/scrub.rb +86 -13
- data/lib/loofah/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a721138b6b152897d72edddfb80cdb0f35200c39a8f37a2024b503e6cfacc95
|
|
4
|
+
data.tar.gz: b342436a5a4e544b504d3e4234935ecd09dd87eddb55be893a1105cfd6a627f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e8ffd17d4b47d19bbff8070fcfe0d111530e7f146abc3501deaed1eb3198481f5ae16ee597e2a0d033eeea8bda27a038d1832c7237e24067a36e724e037e06a9
|
|
7
|
+
data.tar.gz: 71f741d1fbcf65fc137b25906c183cf767c091b203210e06d59dbb8e209c12274c77415b03406b021eb16dd149e2f9822f70124cec0947494f955d3fd1e458ee
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.25.2 / 2026-07-15
|
|
4
|
+
|
|
5
|
+
### Security
|
|
6
|
+
|
|
7
|
+
* Ensure `Loofah::HTML5::Scrub.allowed_uri?` recognizes numeric character references without semicolons (e.g. `javascript:alert(1)`), which browsers decode and execute, and rejects schemes split by them. See [GHSA-5qhf-9phg-95m2](https://github.com/flavorjones/loofah/security/advisories/GHSA-5qhf-9phg-95m2). @flavorjones
|
|
8
|
+
* Ensure `Loofah::HTML5::Scrub.allowed_uri?` recognizes the named character references `	` and `
`, which `CGI.unescapeHTML` does not decode and browsers strip from URIs, and rejects schemes split by them (e.g. `java	script:alert(1)`). See [GHSA-8whx-365g-h9vv](https://github.com/flavorjones/loofah/security/advisories/GHSA-8whx-365g-h9vv). @flavorjones
|
|
9
|
+
* Ensure that both `href` and `xlink:href` attributes on SVG elements like `use` are restricted to local (same-document) references. Previously only `xlink:href` was restricted, allowing the SVG 2 `href` attribute to reference external documents. See [GHSA-9wjq-cp2p-hrgf](https://github.com/flavorjones/loofah/security/advisories/GHSA-9wjq-cp2p-hrgf). @flavorjones
|
|
10
|
+
|
|
11
|
+
### Improved
|
|
12
|
+
|
|
13
|
+
* Harden `data:` URI mediatype parsing in `Loofah::HTML5::Scrub.allowed_uri?`. The mediatype is now parsed following the [WHATWG data: URL spec](https://fetch.spec.whatwg.org/#data-urls) and [RFC 2397](https://www.rfc-editor.org/rfc/rfc2397) instead of simply being split on a colon. A `data:` URI with an omitted or malformed mediatype is now treated as `text/plain` and allowed, and one without the required comma is now rejected. #305 @flavorjones
|
|
14
|
+
* Remove `feed` from the default set of allowed protocols. The [feed URI scheme](https://en.wikipedia.org/wiki/Feed_URI_scheme) was never accepted as a standard protocol, and no major browser supports it. Removing it reduces the attack surface particularly for non-browser contexts. #304 @flavorjones
|
|
15
|
+
* Remove a vestigial `p` alternative from `Loofah::HTML5::SafeList::PROTOCOL_SEPARATOR`. This appears to be an ancient typo dating back to pre-extraction Rails circa 2007. #305 @flavorjones
|
|
16
|
+
|
|
17
|
+
|
|
3
18
|
## 2.25.1 / 2026-03-17
|
|
4
19
|
|
|
5
20
|
* Ensure `Loofah::HTML5::Scrub.allowed_uri?` recognizes unescaped whitespace entities and rejects schemas containing them. See [GHSA-46fp-8f5p-pf2m](https://github.com/flavorjones/loofah/security/advisories/GHSA-46fp-8f5p-pf2m). #302 @flavorjones
|
|
@@ -605,6 +605,11 @@ module Loofah
|
|
|
605
605
|
"stroke",
|
|
606
606
|
])
|
|
607
607
|
|
|
608
|
+
SVG_HREF_ATTRIBUTES = Set.new([
|
|
609
|
+
"xlink:href",
|
|
610
|
+
"href",
|
|
611
|
+
])
|
|
612
|
+
|
|
608
613
|
SVG_ALLOW_LOCAL_HREF = Set.new([
|
|
609
614
|
"altGlyph",
|
|
610
615
|
"animate",
|
|
@@ -978,7 +983,7 @@ module Loofah
|
|
|
978
983
|
"stroke-opacity",
|
|
979
984
|
])
|
|
980
985
|
|
|
981
|
-
PROTOCOL_SEPARATOR = /:|(�*58)|(&#
|
|
986
|
+
PROTOCOL_SEPARATOR = /:|(�*58)|(�*3a)|(%|%)3A/i
|
|
982
987
|
|
|
983
988
|
ACCEPTABLE_PROTOCOLS = Set.new([
|
|
984
989
|
"afs",
|
|
@@ -987,7 +992,6 @@ module Loofah
|
|
|
987
992
|
"data",
|
|
988
993
|
"ed2k",
|
|
989
994
|
"fax",
|
|
990
|
-
"feed",
|
|
991
995
|
"ftp",
|
|
992
996
|
"gopher",
|
|
993
997
|
"http",
|
data/lib/loofah/html5/scrub.rb
CHANGED
|
@@ -14,7 +14,43 @@ module Loofah
|
|
|
14
14
|
CSS_WHITESPACE = " "
|
|
15
15
|
CSS_PROPERTY_STRING_WITHOUT_EMBEDDED_QUOTES = /\A(["'])?[^"']+\1\z/
|
|
16
16
|
DATA_ATTRIBUTE_NAME = /\Adata-[\w-]+\z/
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
# Decimal (`:`) or hexadecimal (`:`) form, with or without the trailing semicolon that
|
|
19
|
+
# CGI.unescapeHTML requires but browsers do not.
|
|
20
|
+
NUMERIC_CHARACTER_REFERENCE = /&#(x[0-9a-f]+|[0-9]+);?/i
|
|
21
|
+
|
|
22
|
+
# A scheme (RFC 3986) followed by a protocol separator. The separator must recognize the same
|
|
23
|
+
# encoded-colon forms as PROTOCOL_SEPARATOR, otherwise a scheme split by an encoded colon (for
|
|
24
|
+
# example "javascript:alert(1)") would not be recognized as having a scheme and would skip
|
|
25
|
+
# protocol validation.
|
|
26
|
+
URI_PROTOCOL_REGEX = /\A[a-z][a-z0-9+\-.]*#{SafeList::PROTOCOL_SEPARATOR}/
|
|
27
|
+
|
|
28
|
+
# Matches a valid MIME type "essence" (type "/" subtype, no parameters), used to
|
|
29
|
+
# decide whether a data: URI mediatype is well-formed; a non-match is not a valid
|
|
30
|
+
# MIME type, which the data: URL processor treats as text/plain. Specs:
|
|
31
|
+
#
|
|
32
|
+
# https://mimesniff.spec.whatwg.org/#valid-mime-type
|
|
33
|
+
# https://mimesniff.spec.whatwg.org/#mime-type-essence
|
|
34
|
+
# https://mimesniff.spec.whatwg.org/#http-token-code-point
|
|
35
|
+
#
|
|
36
|
+
# The character class below is the HTTP token set (tchar) from RFC 9110 section
|
|
37
|
+
# 5.6.2, https://www.rfc-editor.org/rfc/rfc9110#name-tokens :
|
|
38
|
+
#
|
|
39
|
+
# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^"
|
|
40
|
+
# / "_" / "`" / "|" / "~" / DIGIT / ALPHA
|
|
41
|
+
#
|
|
42
|
+
# ALPHA is written a-z, not a-zA-Z, because allowed_uri? downcases the input first.
|
|
43
|
+
DATA_URI_MEDIATYPE = %r{
|
|
44
|
+
\A
|
|
45
|
+
[a-z0-9!\#$%&'*+\-.^_`|~]+ # type: 1*tchar
|
|
46
|
+
/ # "/" is not a tchar, so it is the sole delimiter
|
|
47
|
+
[a-z0-9!\#$%&'*+\-.^_`|~]+ # subtype: 1*tchar
|
|
48
|
+
\z
|
|
49
|
+
}x
|
|
50
|
+
|
|
51
|
+
# HTML5 named character references for whitespace that browsers strip from
|
|
52
|
+
# URIs. CGI.unescapeHTML does not decode these, so they are handled explicitly.
|
|
53
|
+
WHITESPACE_CHARACTER_REFERENCES = /&(Tab|NewLine);/
|
|
18
54
|
|
|
19
55
|
class << self
|
|
20
56
|
def allowed_element?(element_name)
|
|
@@ -48,7 +84,7 @@ module Loofah
|
|
|
48
84
|
end
|
|
49
85
|
|
|
50
86
|
next unless SafeList::SVG_ALLOW_LOCAL_HREF.include?(node.name) &&
|
|
51
|
-
attr_name
|
|
87
|
+
SafeList::SVG_HREF_ATTRIBUTES.include?(attr_name) &&
|
|
52
88
|
attr_node.value =~ /^\s*[^#\s].*/m
|
|
53
89
|
|
|
54
90
|
attr_node.remove
|
|
@@ -141,29 +177,52 @@ module Loofah
|
|
|
141
177
|
attr_node.value = values.join(" ")
|
|
142
178
|
end
|
|
143
179
|
|
|
144
|
-
# Returns true if the given URI string is safe, false otherwise.
|
|
145
|
-
#
|
|
146
|
-
# requiring a Nokogiri DOM node.
|
|
180
|
+
# Returns true if the given URI string is safe, false otherwise. This method can be used to
|
|
181
|
+
# validate URI attribute values without requiring a Nokogiri DOM node.
|
|
147
182
|
def allowed_uri?(uri_string)
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
183
|
+
# CGI.unescapeHTML decodes numeric references only when they carry a trailing semicolon, so
|
|
184
|
+
# also decode the semicolon-less ones, which browsers still decode and execute. Normalizing
|
|
185
|
+
# more aggressively than a browser only rejects more, which is safe. Control characters are
|
|
186
|
+
# stripped both before and after decoding, since decoding can produce them. That strip must
|
|
187
|
+
# precede WHITESPACE_CHARACTER_REFERENCES: removing a control character can reveal a named
|
|
188
|
+
# whitespace reference.
|
|
189
|
+
uri_string = decode_numeric_character_references(CGI.unescapeHTML(uri_string.gsub(CONTROL_CHARACTERS, "")))
|
|
190
|
+
uri_string.gsub!(CONTROL_CHARACTERS, "")
|
|
191
|
+
uri_string.gsub!(WHITESPACE_CHARACTER_REFERENCES, "")
|
|
192
|
+
uri_string.gsub!(":", ":")
|
|
193
|
+
uri_string.downcase!
|
|
153
194
|
if URI_PROTOCOL_REGEX.match?(uri_string)
|
|
154
195
|
protocol = uri_string.split(SafeList::PROTOCOL_SEPARATOR)[0]
|
|
155
196
|
return false unless SafeList::ALLOWED_PROTOCOLS.include?(protocol)
|
|
156
197
|
|
|
157
198
|
if protocol == "data"
|
|
158
199
|
# permit only allowed data mediatypes
|
|
159
|
-
|
|
160
|
-
mediatype, _ = mediatype.split(/[;,]/)[0..1] if mediatype
|
|
161
|
-
return false if mediatype && !SafeList::ALLOWED_URI_DATA_MEDIATYPES.include?(mediatype)
|
|
200
|
+
return false unless SafeList::ALLOWED_URI_DATA_MEDIATYPES.include?(data_uri_mediatype(uri_string))
|
|
162
201
|
end
|
|
163
202
|
end
|
|
164
203
|
true
|
|
165
204
|
end
|
|
166
205
|
|
|
206
|
+
def decode_numeric_character_references(string)
|
|
207
|
+
string.gsub(NUMERIC_CHARACTER_REFERENCE) do |reference|
|
|
208
|
+
digits = ::Regexp.last_match(1)
|
|
209
|
+
hexadecimal = digits.start_with?("x", "X")
|
|
210
|
+
digits = digits[1..-1] if hexadecimal
|
|
211
|
+
significant_digits = digits.sub(/\A0+/, "")
|
|
212
|
+
|
|
213
|
+
# The largest code point is U+10FFFF: 7 decimal or 6 hexadecimal significant digits.
|
|
214
|
+
# Anything longer is out of range; skip it without building a large integer from it.
|
|
215
|
+
next reference if significant_digits.length > (hexadecimal ? 6 : 7)
|
|
216
|
+
|
|
217
|
+
codepoint = significant_digits.to_i(hexadecimal ? 16 : 10)
|
|
218
|
+
begin
|
|
219
|
+
codepoint.chr(Encoding::UTF_8)
|
|
220
|
+
rescue RangeError
|
|
221
|
+
reference
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
167
226
|
def scrub_uri_attribute(attr_node)
|
|
168
227
|
if allowed_uri?(attr_node.value)
|
|
169
228
|
false
|
|
@@ -238,6 +297,20 @@ module Loofah
|
|
|
238
297
|
string
|
|
239
298
|
end
|
|
240
299
|
end
|
|
300
|
+
|
|
301
|
+
private
|
|
302
|
+
|
|
303
|
+
# Returns the mediatype of a data: URI per RFC 2397, or nil when the
|
|
304
|
+
# required comma is absent. allowed_uri? entity-decodes, downcases, and
|
|
305
|
+
# strips control characters before calling this. An omitted or malformed
|
|
306
|
+
# mediatype resolves to "text/plain", matching the WHATWG data: URL processor.
|
|
307
|
+
def data_uri_mediatype(uri_string)
|
|
308
|
+
metadata, comma, _data = uri_string.delete_prefix("data:").partition(",")
|
|
309
|
+
return nil if comma.empty?
|
|
310
|
+
|
|
311
|
+
mediatype = metadata.delete_suffix(";base64").split(";", 2).first.to_s.strip
|
|
312
|
+
mediatype.match?(DATA_URI_MEDIATYPE) ? mediatype : "text/plain"
|
|
313
|
+
end
|
|
241
314
|
end
|
|
242
315
|
end
|
|
243
316
|
end
|
data/lib/loofah/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: loofah
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.25.
|
|
4
|
+
version: 2.25.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mike Dalessio
|
|
@@ -96,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
96
96
|
- !ruby/object:Gem::Version
|
|
97
97
|
version: '0'
|
|
98
98
|
requirements: []
|
|
99
|
-
rubygems_version: 4.0.
|
|
99
|
+
rubygems_version: 4.0.10
|
|
100
100
|
specification_version: 4
|
|
101
101
|
summary: Loofah is a general library for manipulating and transforming HTML/XML documents
|
|
102
102
|
and fragments, built on top of Nokogiri.
|