sanitize-html 2.9.0 → 2.11.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.
- package/README.md +68 -1
- package/index.js +81 -21
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,10 @@ sanitize-html provides a simple HTML sanitizer with a clear API.
|
|
|
8
8
|
|
|
9
9
|
sanitize-html is tolerant. It is well suited for cleaning up HTML fragments such as those created by CKEditor and other rich text editors. It is especially handy for removing unwanted CSS when copying and pasting from Word.
|
|
10
10
|
|
|
11
|
-
sanitize-html allows you to specify the tags you want to permit, and the permitted
|
|
11
|
+
sanitize-html allows you to specify the tags you want to permit, and the permitted
|
|
12
|
+
attributes for each of those tags. If an attribute is a known non-boolean value,
|
|
13
|
+
and it is empty, it will be removed. For example `checked` can be empty, but `href`
|
|
14
|
+
cannot.
|
|
12
15
|
|
|
13
16
|
If a tag is not permitted, the contents of the tag are not discarded. There are
|
|
14
17
|
some exceptions to this, discussed below in the "Discarding the entire contents
|
|
@@ -125,6 +128,48 @@ allowedTags: [
|
|
|
125
128
|
"small", "span", "strong", "sub", "sup", "time", "u", "var", "wbr", "caption",
|
|
126
129
|
"col", "colgroup", "table", "tbody", "td", "tfoot", "th", "thead", "tr"
|
|
127
130
|
],
|
|
131
|
+
nonBooleanAttributes: [
|
|
132
|
+
'abbr', 'accept', 'accept-charset', 'accesskey', 'action',
|
|
133
|
+
'allow', 'alt', 'as', 'autocapitalize', 'autocomplete',
|
|
134
|
+
'blocking', 'charset', 'cite', 'class', 'color', 'cols',
|
|
135
|
+
'colspan', 'content', 'contenteditable', 'coords', 'crossorigin',
|
|
136
|
+
'data', 'datetime', 'decoding', 'dir', 'dirname', 'download',
|
|
137
|
+
'draggable', 'enctype', 'enterkeyhint', 'fetchpriority', 'for',
|
|
138
|
+
'form', 'formaction', 'formenctype', 'formmethod', 'formtarget',
|
|
139
|
+
'headers', 'height', 'hidden', 'high', 'href', 'hreflang',
|
|
140
|
+
'http-equiv', 'id', 'imagesizes', 'imagesrcset', 'inputmode',
|
|
141
|
+
'integrity', 'is', 'itemid', 'itemprop', 'itemref', 'itemtype',
|
|
142
|
+
'kind', 'label', 'lang', 'list', 'loading', 'low', 'max',
|
|
143
|
+
'maxlength', 'media', 'method', 'min', 'minlength', 'name',
|
|
144
|
+
'nonce', 'optimum', 'pattern', 'ping', 'placeholder', 'popover',
|
|
145
|
+
'popovertarget', 'popovertargetaction', 'poster', 'preload',
|
|
146
|
+
'referrerpolicy', 'rel', 'rows', 'rowspan', 'sandbox', 'scope',
|
|
147
|
+
'shape', 'size', 'sizes', 'slot', 'span', 'spellcheck', 'src',
|
|
148
|
+
'srcdoc', 'srclang', 'srcset', 'start', 'step', 'style',
|
|
149
|
+
'tabindex', 'target', 'title', 'translate', 'type', 'usemap',
|
|
150
|
+
'value', 'width', 'wrap',
|
|
151
|
+
// Event handlers
|
|
152
|
+
'onauxclick', 'onafterprint', 'onbeforematch', 'onbeforeprint',
|
|
153
|
+
'onbeforeunload', 'onbeforetoggle', 'onblur', 'oncancel',
|
|
154
|
+
'oncanplay', 'oncanplaythrough', 'onchange', 'onclick', 'onclose',
|
|
155
|
+
'oncontextlost', 'oncontextmenu', 'oncontextrestored', 'oncopy',
|
|
156
|
+
'oncuechange', 'oncut', 'ondblclick', 'ondrag', 'ondragend',
|
|
157
|
+
'ondragenter', 'ondragleave', 'ondragover', 'ondragstart',
|
|
158
|
+
'ondrop', 'ondurationchange', 'onemptied', 'onended',
|
|
159
|
+
'onerror', 'onfocus', 'onformdata', 'onhashchange', 'oninput',
|
|
160
|
+
'oninvalid', 'onkeydown', 'onkeypress', 'onkeyup',
|
|
161
|
+
'onlanguagechange', 'onload', 'onloadeddata', 'onloadedmetadata',
|
|
162
|
+
'onloadstart', 'onmessage', 'onmessageerror', 'onmousedown',
|
|
163
|
+
'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout',
|
|
164
|
+
'onmouseover', 'onmouseup', 'onoffline', 'ononline', 'onpagehide',
|
|
165
|
+
'onpageshow', 'onpaste', 'onpause', 'onplay', 'onplaying',
|
|
166
|
+
'onpopstate', 'onprogress', 'onratechange', 'onreset', 'onresize',
|
|
167
|
+
'onrejectionhandled', 'onscroll', 'onscrollend',
|
|
168
|
+
'onsecuritypolicyviolation', 'onseeked', 'onseeking', 'onselect',
|
|
169
|
+
'onslotchange', 'onstalled', 'onstorage', 'onsubmit', 'onsuspend',
|
|
170
|
+
'ontimeupdate', 'ontoggle', 'onunhandledrejection', 'onunload',
|
|
171
|
+
'onvolumechange', 'onwaiting', 'onwheel'
|
|
172
|
+
],
|
|
128
173
|
disallowedTagsMode: 'discard',
|
|
129
174
|
allowedAttributes: {
|
|
130
175
|
a: [ 'href', 'name', 'target' ],
|
|
@@ -167,6 +212,26 @@ allowedTags: false,
|
|
|
167
212
|
allowedAttributes: false
|
|
168
213
|
```
|
|
169
214
|
|
|
215
|
+
#### "What if I want to allow empty attributes, even for cases like href that normally don't make sense?"
|
|
216
|
+
|
|
217
|
+
Very simple! Set `nonBooleanAttributes` to `[]`.
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
nonBooleanAttributes: []
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
#### "What if I want to remove all empty attributes, including valid ones?"
|
|
224
|
+
|
|
225
|
+
Also very simple! Set `nonBooleanAttributes` to `['*']`.
|
|
226
|
+
|
|
227
|
+
**Note**: This will break common valid cases like `checked` and `selected`, so this is
|
|
228
|
+
unlikely to be what you want. For most ordinary HTML use, it is best to avoid making
|
|
229
|
+
this change.
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
nonBooleanAttributes: ['*']
|
|
233
|
+
```
|
|
234
|
+
|
|
170
235
|
#### "What if I don't want to allow *any* tags?"
|
|
171
236
|
|
|
172
237
|
Also simple! Set `allowedTags` to `[]` and `allowedAttributes` to `{}`.
|
|
@@ -255,6 +320,8 @@ allowedClasses: {
|
|
|
255
320
|
}
|
|
256
321
|
```
|
|
257
322
|
|
|
323
|
+
If `allowedClasses` for a certain tag is `false`, all the classes for this tag will be allowed.
|
|
324
|
+
|
|
258
325
|
> Note: It is advised that your regular expressions always begin with `^` so that you are requiring a known prefix. A regular expression with neither `^` nor `$` just requires that something appear in the middle.
|
|
259
326
|
|
|
260
327
|
### Allowed CSS Styles
|
package/index.js
CHANGED
|
@@ -117,12 +117,13 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
117
117
|
options = Object.assign({}, sanitizeHtml.defaults, options);
|
|
118
118
|
options.parser = Object.assign({}, htmlParserDefaults, options.parser);
|
|
119
119
|
|
|
120
|
+
const tagAllowed = function (name) {
|
|
121
|
+
return options.allowedTags === false || (options.allowedTags || []).indexOf(name) > -1;
|
|
122
|
+
};
|
|
123
|
+
|
|
120
124
|
// vulnerableTags
|
|
121
125
|
vulnerableTags.forEach(function (tag) {
|
|
122
|
-
if (
|
|
123
|
-
options.allowedTags !== false && (options.allowedTags || []).indexOf(tag) > -1 &&
|
|
124
|
-
!options.allowVulnerableTags
|
|
125
|
-
) {
|
|
126
|
+
if (tagAllowed(tag) && !options.allowVulnerableTags) {
|
|
126
127
|
console.warn(`\n\n⚠️ Your \`allowedTags\` option includes, \`${tag}\`, which is inherently\nvulnerable to XSS attacks. Please remove it from \`allowedTags\`.\nOr, to disable this warning, add the \`allowVulnerableTags\` option\nand ensure you are accounting for this risk.\n\n`);
|
|
127
128
|
}
|
|
128
129
|
});
|
|
@@ -169,20 +170,24 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
169
170
|
allowedAttributesMap[tag].push('class');
|
|
170
171
|
}
|
|
171
172
|
|
|
172
|
-
allowedClassesMap[tag] =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
173
|
+
allowedClassesMap[tag] = classes;
|
|
174
|
+
|
|
175
|
+
if (Array.isArray(classes)) {
|
|
176
|
+
const globRegex = [];
|
|
177
|
+
allowedClassesMap[tag] = [];
|
|
178
|
+
allowedClassesRegexMap[tag] = [];
|
|
179
|
+
classes.forEach(function(obj) {
|
|
180
|
+
if (typeof obj === 'string' && obj.indexOf('*') >= 0) {
|
|
181
|
+
globRegex.push(escapeStringRegexp(obj).replace(/\\\*/g, '.*'));
|
|
182
|
+
} else if (obj instanceof RegExp) {
|
|
183
|
+
allowedClassesRegexMap[tag].push(obj);
|
|
184
|
+
} else {
|
|
185
|
+
allowedClassesMap[tag].push(obj);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
if (globRegex.length) {
|
|
189
|
+
allowedClassesGlobMap[tag] = new RegExp('^(' + globRegex.join('|') + ')$');
|
|
182
190
|
}
|
|
183
|
-
});
|
|
184
|
-
if (globRegex.length) {
|
|
185
|
-
allowedClassesGlobMap[tag] = new RegExp('^(' + globRegex.join('|') + ')$');
|
|
186
191
|
}
|
|
187
192
|
});
|
|
188
193
|
|
|
@@ -254,7 +259,7 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
254
259
|
}
|
|
255
260
|
}
|
|
256
261
|
|
|
257
|
-
if ((
|
|
262
|
+
if (!tagAllowed(name) || (options.disallowedTagsMode === 'recursiveEscape' && !isEmptyObject(skipMap)) || (options.nestingLimit != null && depth >= options.nestingLimit)) {
|
|
258
263
|
skip = true;
|
|
259
264
|
skipMap[depth] = true;
|
|
260
265
|
if (options.disallowedTagsMode === 'discard') {
|
|
@@ -290,6 +295,12 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
290
295
|
delete frame.attribs[a];
|
|
291
296
|
return;
|
|
292
297
|
}
|
|
298
|
+
// If the value is empty, and this is a known non-boolean attribute, delete it
|
|
299
|
+
// List taken from https://html.spec.whatwg.org/multipage/indices.html#attributes-3
|
|
300
|
+
if (value === '' && (options.nonBooleanAttributes.includes(a) || options.nonBooleanAttributes.includes('*'))) {
|
|
301
|
+
delete frame.attribs[a];
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
293
304
|
// check allowedAttributesMap for the element and attribute and modify the value
|
|
294
305
|
// as necessary if there are specific values defined.
|
|
295
306
|
let passedAllowedAttributesMapCheck = false;
|
|
@@ -450,7 +461,9 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
450
461
|
return;
|
|
451
462
|
}
|
|
452
463
|
} catch (e) {
|
|
453
|
-
|
|
464
|
+
if (typeof window !== 'undefined') {
|
|
465
|
+
console.warn('Failed to parse "' + name + ' {' + value + '}' + '", If you\'re running this in a browser, we recommend to disable style parsing: options.parseStyleAttributes: false, since this only works in a node environment due to a postcss dependency, More info: https://github.com/apostrophecms/sanitize-html/issues/547');
|
|
466
|
+
}
|
|
454
467
|
delete frame.attribs[a];
|
|
455
468
|
return;
|
|
456
469
|
}
|
|
@@ -513,7 +526,7 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
513
526
|
frame.text += text;
|
|
514
527
|
}
|
|
515
528
|
},
|
|
516
|
-
onclosetag: function(name) {
|
|
529
|
+
onclosetag: function(name, isImplied) {
|
|
517
530
|
|
|
518
531
|
if (skipText) {
|
|
519
532
|
skipTextDepth--;
|
|
@@ -563,8 +576,12 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
563
576
|
frame.updateParentNodeMediaChildren();
|
|
564
577
|
frame.updateParentNodeText();
|
|
565
578
|
|
|
566
|
-
if (
|
|
579
|
+
if (
|
|
567
580
|
// Already output />
|
|
581
|
+
options.selfClosing.indexOf(name) !== -1 ||
|
|
582
|
+
// Escaped tag, closing tag is implied
|
|
583
|
+
(isImplied && !tagAllowed(name) && [ 'escape', 'recursiveEscape' ].indexOf(options.disallowedTagsMode) >= 0)
|
|
584
|
+
) {
|
|
568
585
|
if (skip) {
|
|
569
586
|
result = tempResult;
|
|
570
587
|
tempResult = '';
|
|
@@ -809,6 +826,49 @@ sanitizeHtml.defaults = {
|
|
|
809
826
|
'caption', 'col', 'colgroup', 'table', 'tbody', 'td', 'tfoot', 'th',
|
|
810
827
|
'thead', 'tr'
|
|
811
828
|
],
|
|
829
|
+
// Tags that cannot be boolean
|
|
830
|
+
nonBooleanAttributes: [
|
|
831
|
+
'abbr', 'accept', 'accept-charset', 'accesskey', 'action',
|
|
832
|
+
'allow', 'alt', 'as', 'autocapitalize', 'autocomplete',
|
|
833
|
+
'blocking', 'charset', 'cite', 'class', 'color', 'cols',
|
|
834
|
+
'colspan', 'content', 'contenteditable', 'coords', 'crossorigin',
|
|
835
|
+
'data', 'datetime', 'decoding', 'dir', 'dirname', 'download',
|
|
836
|
+
'draggable', 'enctype', 'enterkeyhint', 'fetchpriority', 'for',
|
|
837
|
+
'form', 'formaction', 'formenctype', 'formmethod', 'formtarget',
|
|
838
|
+
'headers', 'height', 'hidden', 'high', 'href', 'hreflang',
|
|
839
|
+
'http-equiv', 'id', 'imagesizes', 'imagesrcset', 'inputmode',
|
|
840
|
+
'integrity', 'is', 'itemid', 'itemprop', 'itemref', 'itemtype',
|
|
841
|
+
'kind', 'label', 'lang', 'list', 'loading', 'low', 'max',
|
|
842
|
+
'maxlength', 'media', 'method', 'min', 'minlength', 'name',
|
|
843
|
+
'nonce', 'optimum', 'pattern', 'ping', 'placeholder', 'popover',
|
|
844
|
+
'popovertarget', 'popovertargetaction', 'poster', 'preload',
|
|
845
|
+
'referrerpolicy', 'rel', 'rows', 'rowspan', 'sandbox', 'scope',
|
|
846
|
+
'shape', 'size', 'sizes', 'slot', 'span', 'spellcheck', 'src',
|
|
847
|
+
'srcdoc', 'srclang', 'srcset', 'start', 'step', 'style',
|
|
848
|
+
'tabindex', 'target', 'title', 'translate', 'type', 'usemap',
|
|
849
|
+
'value', 'width', 'wrap',
|
|
850
|
+
// Event handlers
|
|
851
|
+
'onauxclick', 'onafterprint', 'onbeforematch', 'onbeforeprint',
|
|
852
|
+
'onbeforeunload', 'onbeforetoggle', 'onblur', 'oncancel',
|
|
853
|
+
'oncanplay', 'oncanplaythrough', 'onchange', 'onclick', 'onclose',
|
|
854
|
+
'oncontextlost', 'oncontextmenu', 'oncontextrestored', 'oncopy',
|
|
855
|
+
'oncuechange', 'oncut', 'ondblclick', 'ondrag', 'ondragend',
|
|
856
|
+
'ondragenter', 'ondragleave', 'ondragover', 'ondragstart',
|
|
857
|
+
'ondrop', 'ondurationchange', 'onemptied', 'onended',
|
|
858
|
+
'onerror', 'onfocus', 'onformdata', 'onhashchange', 'oninput',
|
|
859
|
+
'oninvalid', 'onkeydown', 'onkeypress', 'onkeyup',
|
|
860
|
+
'onlanguagechange', 'onload', 'onloadeddata', 'onloadedmetadata',
|
|
861
|
+
'onloadstart', 'onmessage', 'onmessageerror', 'onmousedown',
|
|
862
|
+
'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout',
|
|
863
|
+
'onmouseover', 'onmouseup', 'onoffline', 'ononline', 'onpagehide',
|
|
864
|
+
'onpageshow', 'onpaste', 'onpause', 'onplay', 'onplaying',
|
|
865
|
+
'onpopstate', 'onprogress', 'onratechange', 'onreset', 'onresize',
|
|
866
|
+
'onrejectionhandled', 'onscroll', 'onscrollend',
|
|
867
|
+
'onsecuritypolicyviolation', 'onseeked', 'onseeking', 'onselect',
|
|
868
|
+
'onslotchange', 'onstalled', 'onstorage', 'onsubmit', 'onsuspend',
|
|
869
|
+
'ontimeupdate', 'ontoggle', 'onunhandledrejection', 'onunload',
|
|
870
|
+
'onvolumechange', 'onwaiting', 'onwheel'
|
|
871
|
+
],
|
|
812
872
|
disallowedTagsMode: 'discard',
|
|
813
873
|
allowedAttributes: {
|
|
814
874
|
a: [ 'href', 'name', 'target' ],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanitize-html",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "Clean up user-submitted HTML, preserving allowlisted elements and allowlisted attributes on a per-element basis",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "index.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"eslint-plugin-node": "^11.1.0",
|
|
39
39
|
"eslint-plugin-promise": "^4.2.1",
|
|
40
40
|
"eslint-plugin-standard": "^4.0.1",
|
|
41
|
-
"mocha": "^
|
|
41
|
+
"mocha": "^10.2.0",
|
|
42
42
|
"sinon": "^9.0.2"
|
|
43
43
|
}
|
|
44
44
|
}
|