sanitize 7.0.1 → 7.0.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 +16 -0
- data/lib/sanitize/config.rb +0 -2
- data/lib/sanitize/css.rb +0 -1
- data/lib/sanitize/transformers/clean_comment.rb +38 -2
- data/lib/sanitize/transformers/clean_element.rb +75 -4
- data/lib/sanitize/version.rb +1 -1
- data/lib/sanitize.rb +1 -2
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9dcbdd9a00a2f80ddded816eb9b52215365ae34ec23e99f0d31b2a2b11757b49
|
|
4
|
+
data.tar.gz: 38be6ac4c8215e558b4b1f4c8ae2eb570f6225a6473964b52ffbcb49c2806779
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d802d1606fa2a8cb2056125d26092ef056146252c38ff55a086776c35eda10b70c63882f7b473656f4ea5cf18dfc9864e1e2bc6d257b204a1072568a5a65d440
|
|
7
|
+
data.tar.gz: 0f64bb78dd9c4b2dca863530c02a08559c36ed9ec761c0d3f3b1d81fef3b1eefe39e7632d0354eaf75b49b5e755c0e0191f92b6fb6a669255fd48bde2df332d7
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
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.2 (2026-11-02)
|
|
6
|
+
|
|
7
|
+
### Security
|
|
8
|
+
|
|
9
|
+
- MathML and SVG elements whose `<math>` or `<svg>` ancestor has been removed are now also removed (keeping their contents, subject to `:remove_contents`), even if they're allowed by the config or have been allowlisted by a transformer. Previously, if Sanitize removed a `<math>` or `<svg>` element but kept its contents, its descendants were kept even though they'd been parsed as foreign elements, and browsers would reparse them as HTML elements. Since some elements are parsed very differently in foreign content than in HTML, this could allow arbitrary HTML to be injected. For example, the contents of a `<textarea>`, `<title>`, `<xmp>`, `<noembed>`, or `<noframes>` element inside a MathML or SVG element are parsed as markup rather than text, so an attribute value, comment, or raw text element inside it like `<svg><textarea><!--</textarea><img src=x onerror=alert(1)>-->` could close the element early once it was reparsed as HTML. Similarly, browsers that use legacy `<select>` parsing rules would parse the contents of a raw text element inside a `<select>` element like `<math><select><mi><xmp><input><img src=x onerror=alert(1)></xmp>` as markup. This only affected custom configs that don't remove the contents of `<math>` or `<svg>` elements and allow one of the affected elements. Sanitize's default and built-in configs aren't affected. Thanks to Mike Dalessio (@flavorjones) for reporting this issue. (GHSA-599p-54q3-ff89)
|
|
10
|
+
|
|
11
|
+
- Elements and comments inside text content elements such as `<textarea>`, `<title>`, `<xmp>`, `<noembed>`, and `<noframes>` are now always removed, even if they're allowed by the config or have been allowlisted by a transformer. Only their text is kept, subject to `:remove_contents`. This provides defense in depth against the issue above. (GHSA-599p-54q3-ff89)
|
|
12
|
+
|
|
13
|
+
- Sanitize now requires Nokogiri 1.19.7 or higher. Nokogiri versions since 1.19.5 escape `<` and `>` in serialized attribute values to match browser behavior, which provides defense in depth against the issue above. (GHSA-599p-54q3-ff89)
|
|
14
|
+
|
|
15
|
+
- Elements inside `<select>`, `<option>`, and `<optgroup>` elements that browsers using legacy `<select>` parsing rules wouldn't allow there are now always removed (keeping their contents, subject to `:remove_contents`), even if they're allowed by the config or have been allowlisted by a transformer. Previously, if Sanitize removed a `<template>` element inside a `<select>` element but kept its contents, any raw text element inside the `<template>` element would end up inside the `<select>` element. Browsers that use legacy `<select>` parsing rules ignore the start tags of raw text elements inside a `<select>` element and parse their contents as markup, so input like `<select><template><xmp><input><img src=x onerror=alert(1)></xmp>` could be used to inject arbitrary HTML. This only affected custom configs that allow `<select>` and at least one raw text element such as `<style>`, `<xmp>`, `<noembed>`, `<noframes>`, or `<plaintext>`. Sanitize's default and built-in configs aren't affected. (GHSA-mg79-gw37-jxx7)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Ruby 3.2.0 is now the oldest supported Ruby version, since Nokogiri 1.19.x requires Ruby 3.2.0 or higher. Ruby 3.1 reached end of life in March 2025.
|
|
20
|
+
|
|
5
21
|
## 7.0.1 (2026-10-21)
|
|
6
22
|
|
|
7
23
|
### Security
|
data/lib/sanitize/config.rb
CHANGED
data/lib/sanitize/css.rb
CHANGED
|
@@ -2,12 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
class Sanitize
|
|
4
4
|
module Transformers
|
|
5
|
+
# Names of elements whose content is parsed as text rather than markup when
|
|
6
|
+
# they're in the HTML namespace.
|
|
7
|
+
#
|
|
8
|
+
# An element with one of these names can contain markup if it's in a foreign
|
|
9
|
+
# namespace like MathML or SVG. If Sanitize removes the foreign ancestor but
|
|
10
|
+
# keeps the element, its serialized contents will be reparsed as text inside
|
|
11
|
+
# an HTML element, so a closing tag inside a comment or an unescaped raw
|
|
12
|
+
# text element could break out of the element and inject arbitrary markup.
|
|
13
|
+
# To prevent this, Sanitize removes all elements and comments inside these
|
|
14
|
+
# elements, leaving only text.
|
|
15
|
+
TEXT_CONTENT_ELEMENTS = Set.new(%w[
|
|
16
|
+
iframe
|
|
17
|
+
noembed
|
|
18
|
+
noframes
|
|
19
|
+
noscript
|
|
20
|
+
plaintext
|
|
21
|
+
script
|
|
22
|
+
style
|
|
23
|
+
textarea
|
|
24
|
+
title
|
|
25
|
+
xmp
|
|
26
|
+
]).freeze
|
|
27
|
+
|
|
5
28
|
CleanComment = lambda do |env|
|
|
6
29
|
node = env[:node]
|
|
7
30
|
|
|
8
|
-
|
|
9
|
-
|
|
31
|
+
return unless node.type == Nokogiri::XML::Node::COMMENT_NODE
|
|
32
|
+
|
|
33
|
+
# Comments inside text content elements are always removed, even if
|
|
34
|
+
# they're allowlisted, since they can never be serialized safely.
|
|
35
|
+
parent = node.parent
|
|
36
|
+
|
|
37
|
+
while parent&.element?
|
|
38
|
+
if TEXT_CONTENT_ELEMENTS.include?(parent.name.downcase)
|
|
39
|
+
node.unlink
|
|
40
|
+
return
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
parent = parent.parent
|
|
10
44
|
end
|
|
45
|
+
|
|
46
|
+
node.unlink unless env[:config][:allow_comments] || env[:is_allowlisted]
|
|
11
47
|
end
|
|
12
48
|
end
|
|
13
49
|
end
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "set"
|
|
4
|
-
|
|
5
3
|
class Sanitize
|
|
6
4
|
module Transformers
|
|
7
5
|
class CleanElement
|
|
6
|
+
# Maps the namespace URI of each foreign element type supported by HTML to
|
|
7
|
+
# the name of its root element.
|
|
8
|
+
FOREIGN_NAMESPACE_ROOTS = {
|
|
9
|
+
"http://www.w3.org/1998/Math/MathML" => "math",
|
|
10
|
+
"http://www.w3.org/2000/svg" => "svg"
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
8
13
|
# Matches a valid HTML5 data attribute name. The unicode ranges included
|
|
9
14
|
# here are a conservative subset of the full range of characters that are
|
|
10
15
|
# technically allowed, with the intent of matching the most common
|
|
@@ -20,6 +25,14 @@ class Sanitize
|
|
|
20
25
|
# https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes
|
|
21
26
|
REGEX_DATA_ATTR = /\Adata-(?!xml)[a-z_][\w.\u00E0-\u00F6\u00F8-\u017F\u01DD-\u02AF-]*\z/u
|
|
22
27
|
|
|
28
|
+
# Maps `<select>` and related elements to the names of the elements that
|
|
29
|
+
# browsers using legacy `<select>` parsing rules allow as their children.
|
|
30
|
+
SELECT_CONTENT_MODEL = {
|
|
31
|
+
"optgroup" => Set["option"].freeze,
|
|
32
|
+
"option" => Set.new.freeze,
|
|
33
|
+
"select" => Set["hr", "optgroup", "option"].freeze
|
|
34
|
+
}.freeze
|
|
35
|
+
|
|
23
36
|
# Elements whose content is treated as unescaped text by HTML parsers.
|
|
24
37
|
UNESCAPED_TEXT_ELEMENTS = Set.new(%w[
|
|
25
38
|
iframe
|
|
@@ -90,9 +103,18 @@ class Sanitize
|
|
|
90
103
|
|
|
91
104
|
def call(env)
|
|
92
105
|
node = env[:node]
|
|
93
|
-
return if node.type != Nokogiri::XML::Node::ELEMENT_NODE
|
|
106
|
+
return if node.type != Nokogiri::XML::Node::ELEMENT_NODE
|
|
94
107
|
|
|
95
108
|
name = env[:node_name]
|
|
109
|
+
parent = node.parent
|
|
110
|
+
|
|
111
|
+
# Some elements are always removed, even if they're allowlisted, since
|
|
112
|
+
# browsers would parse them differently than Nokogiri did.
|
|
113
|
+
force_remove = orphaned_foreign_element?(node, name, parent) ||
|
|
114
|
+
invalid_select_descendant?(name, parent) ||
|
|
115
|
+
text_content_element?(parent)
|
|
116
|
+
|
|
117
|
+
return if env[:is_allowlisted] && !force_remove
|
|
96
118
|
|
|
97
119
|
# Delete any element that isn't in the config allowlist, unless the node
|
|
98
120
|
# has already been deleted from the document.
|
|
@@ -100,7 +122,7 @@ class Sanitize
|
|
|
100
122
|
# It's important that we not try to reparent the children of a node that
|
|
101
123
|
# has already been deleted, since that seems to trigger a memory leak in
|
|
102
124
|
# Nokogiri.
|
|
103
|
-
|
|
125
|
+
if (force_remove || !@elements.include?(name)) && !parent.nil?
|
|
104
126
|
# Elements like br, div, p, etc. need to be replaced with whitespace
|
|
105
127
|
# in order to preserve readability.
|
|
106
128
|
if @whitespace_elements.include?(name)
|
|
@@ -245,6 +267,55 @@ class Sanitize
|
|
|
245
267
|
node.unlink
|
|
246
268
|
end
|
|
247
269
|
end
|
|
270
|
+
|
|
271
|
+
private
|
|
272
|
+
|
|
273
|
+
# Returns `true` if _parent_ is an HTML `<select>`, `<option>`, or
|
|
274
|
+
# `<optgroup>` element and _name_ isn't an element that browsers allow
|
|
275
|
+
# inside it.
|
|
276
|
+
#
|
|
277
|
+
# Browsers that use legacy `<select>` parsing rules ignore the start tags
|
|
278
|
+
# of most elements inside a `<select>` element, including raw text
|
|
279
|
+
# elements like `<style>` and `<xmp>`, so these elements' contents would
|
|
280
|
+
# be parsed as markup. Nokogiri parses these elements the same way, but
|
|
281
|
+
# they can still end up inside a `<select>` element via a `<template>`
|
|
282
|
+
# element or foreign content that Sanitize removes. Since traversal is
|
|
283
|
+
# top-down, removing these elements ensures that only allowed elements
|
|
284
|
+
# and text remain inside `<select>` elements.
|
|
285
|
+
def invalid_select_descendant?(name, parent)
|
|
286
|
+
return false unless parent&.element? && SELECT_CONTENT_MODEL.key?(parent.name.downcase)
|
|
287
|
+
return false if FOREIGN_NAMESPACE_ROOTS.key?(parent.namespace&.href)
|
|
288
|
+
|
|
289
|
+
!SELECT_CONTENT_MODEL[parent.name.downcase].include?(name)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# Returns `true` if _node_ is a MathML or SVG element other than a
|
|
293
|
+
# `<math>` or `<svg>` element and its parent isn't a MathML or SVG element.
|
|
294
|
+
#
|
|
295
|
+
# This happens when Sanitize removes a `<math>` or `<svg>` element but
|
|
296
|
+
# keeps its contents. Nokogiri parsed these descendants as foreign
|
|
297
|
+
# elements, but browsers would reparse them as HTML elements, which may
|
|
298
|
+
# be parsed very differently. For example, the contents of a foreign
|
|
299
|
+
# `<textarea>` element are parsed as markup rather than as text. Since
|
|
300
|
+
# traversal is top-down, removing these elements ensures that no foreign
|
|
301
|
+
# element is kept without a foreign root.
|
|
302
|
+
def orphaned_foreign_element?(node, name, parent)
|
|
303
|
+
root_name = FOREIGN_NAMESPACE_ROOTS[node.namespace&.href]
|
|
304
|
+
return false if root_name.nil? || name == root_name
|
|
305
|
+
|
|
306
|
+
!(parent&.element? && FOREIGN_NAMESPACE_ROOTS.key?(parent.namespace&.href))
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Returns `true` if _parent_ is a text content element, such as
|
|
310
|
+
# `<textarea>` or `<xmp>`.
|
|
311
|
+
#
|
|
312
|
+
# Browsers parse the contents of these elements as text when they're in
|
|
313
|
+
# the HTML namespace, so any element inside one must have been parsed in
|
|
314
|
+
# a foreign namespace. Since traversal is top-down, removing these
|
|
315
|
+
# elements ensures that only text remains inside text content elements.
|
|
316
|
+
def text_content_element?(parent)
|
|
317
|
+
!!parent&.element? && TEXT_CONTENT_ELEMENTS.include?(parent.name.downcase)
|
|
318
|
+
end
|
|
248
319
|
end
|
|
249
320
|
end
|
|
250
321
|
end
|
data/lib/sanitize/version.rb
CHANGED
data/lib/sanitize.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "nokogiri"
|
|
4
|
-
require "set"
|
|
5
4
|
|
|
6
5
|
require_relative "sanitize/version"
|
|
7
6
|
require_relative "sanitize/config"
|
|
@@ -97,7 +96,7 @@ class Sanitize
|
|
|
97
96
|
# Default transformers always run at the end of the chain, after any custom
|
|
98
97
|
# transformers.
|
|
99
98
|
@transformers << Transformers::CleanElement.new(@config)
|
|
100
|
-
@transformers << Transformers::CleanComment
|
|
99
|
+
@transformers << Transformers::CleanComment
|
|
101
100
|
|
|
102
101
|
if @config[:elements].include?("style")
|
|
103
102
|
scss = Sanitize::CSS.new(config)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ryan Grove
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 1.
|
|
32
|
+
version: 1.19.7
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 1.
|
|
39
|
+
version: 1.19.7
|
|
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
|
|
@@ -76,7 +76,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
76
76
|
requirements:
|
|
77
77
|
- - ">="
|
|
78
78
|
- !ruby/object:Gem::Version
|
|
79
|
-
version: 3.
|
|
79
|
+
version: 3.2.0
|
|
80
80
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
81
|
requirements:
|
|
82
82
|
- - ">="
|