sanitize-html 2.3.3 → 2.5.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.
- package/CHANGELOG.md +17 -0
- package/README.md +49 -8
- package/index.js +71 -10
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.5.2 (2021-10-13):
|
|
4
|
+
|
|
5
|
+
- Nullish HTML input now returns an empty string. Nullish value may be explicit `null`, `undefined` or implicit `undefined` when value is not provided. Thanks to Artem Kostiuk for the contribution.
|
|
6
|
+
- Documented that all text content is escaped. Thanks to Siddharth Singh.
|
|
7
|
+
|
|
8
|
+
## 2.5.1 (2021-09-14):
|
|
9
|
+
- The `allowedScriptHostnames` and `allowedScriptDomains` options now implicitly purge the inline content of all script tags, not just those with `src` attributes. This behavior was already strongly implied by the fact that they purged it in the case where a `src` attribute was actually present, and is necessary for the feature to provide any real security. Thanks to Grigorii Duca for pointing out the issue.
|
|
10
|
+
|
|
11
|
+
## 2.5.0 (2021-09-08):
|
|
12
|
+
|
|
13
|
+
- New `allowedScriptHostnames` option, it enables you to specify which hostnames are allowed in a script tag.
|
|
14
|
+
- New `allowedScriptDomains` option, it enables you to specify which domains are allowed in a script tag. Thank you to [Yorick Girard](https://github.com/yorickgirard) for this and the `allowedScriptHostnames` contribution.
|
|
15
|
+
- Updates whitelist to allowlist.
|
|
16
|
+
|
|
17
|
+
## 2.4.0 (2021-05-19):
|
|
18
|
+
- Added support for class names with wildcards in `allowedClasses`. Thanks to [zhangbenber](https://github.com/zhangbenber) for the contribution.
|
|
19
|
+
|
|
3
20
|
## 2.3.3 (2021-03-19):
|
|
4
21
|
- Security fix: `allowedSchemes` and related options did not properly block schemes containing a hyphen, plus sign, period or digit, such as `ms-calculator:`. Thanks to Lukas Euler for pointing out the issue.
|
|
5
22
|
- Added a security note about the known risks associated with using the `parser` option, especially `decodeEntities: false`. See the documentation.
|
package/README.md
CHANGED
|
@@ -21,14 +21,25 @@ The syntax of poorly closed `p` and `img` elements is cleaned up.
|
|
|
21
21
|
Allowing particular urls as a `src` to an iframe tag by filtering hostnames is also supported.
|
|
22
22
|
|
|
23
23
|
HTML comments are not preserved.
|
|
24
|
+
Additionally, `sanitize-html` escapes _ALL_ text content - this means that ampersands, greater-than, and less-than signs are converted to their equivalent HTML character references (`&` --> `&`, `<` --> `<`, and so on). Additionally, in attribute values, quotation marks are escaped as well (`"` --> `"`).
|
|
24
25
|
|
|
25
26
|
## Requirements
|
|
26
27
|
|
|
27
28
|
sanitize-html is intended for use with Node.js and supports Node 10+. All of its npm dependencies are pure JavaScript. sanitize-html is built on the excellent `htmlparser2` module.
|
|
28
29
|
|
|
29
|
-
### Regarding
|
|
30
|
+
### Regarding TypeScript
|
|
30
31
|
|
|
31
|
-
sanitize-html is not written in
|
|
32
|
+
sanitize-html is not written in TypeScript and there is no plan to directly support it. There is a community supported typing definition, [`@types/sanitize-html`](https://www.npmjs.com/package/@types/sanitize-html), however.
|
|
33
|
+
```bash
|
|
34
|
+
npm install -D @types/sanitize-html
|
|
35
|
+
```
|
|
36
|
+
If `esModuleInterop=true` is not set in your `tsconfig.json` file, you have to import it with:
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
import * as sanitizeHtml from 'sanitize-html';
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Any questions or problems while using `@types/sanitize-html` should be directed to its maintainers as directed by that project's contribution guidelines.
|
|
32
43
|
|
|
33
44
|
## How to use
|
|
34
45
|
|
|
@@ -38,11 +49,14 @@ sanitize-html is not written in Typescript and there is no plan to directly supp
|
|
|
38
49
|
|
|
39
50
|
But, perhaps you'd like to display sanitized HTML immediately in the browser for preview. Or ask the browser to do the sanitization work on every page load. You can if you want to!
|
|
40
51
|
|
|
41
|
-
*
|
|
42
|
-
* Run npm install and:
|
|
52
|
+
* Install the package:
|
|
43
53
|
|
|
44
54
|
```bash
|
|
45
|
-
npm install sanitize-html
|
|
55
|
+
npm install sanitize-html
|
|
56
|
+
```
|
|
57
|
+
or
|
|
58
|
+
```
|
|
59
|
+
yarn add sanitize-html
|
|
46
60
|
```
|
|
47
61
|
|
|
48
62
|
The primary change in the 2.x version of sanitize-html is that it no longer includes a build that is ready for browser use. Developers are expected to include sanitize-html in their project builds (e.g., webpack) as they would any other dependency. So while sanitize-html is no longer ready to link to directly in HTML, developers can now more easily process it according to their needs.
|
|
@@ -69,7 +83,7 @@ npm install sanitize-html
|
|
|
69
83
|
|
|
70
84
|
Import the module:
|
|
71
85
|
|
|
72
|
-
```
|
|
86
|
+
```js
|
|
73
87
|
// In ES modules
|
|
74
88
|
import sanitizeHtml from 'sanitize-html';
|
|
75
89
|
|
|
@@ -224,16 +238,17 @@ const clean = sanitizeHtml(dirty, {
|
|
|
224
238
|
});
|
|
225
239
|
```
|
|
226
240
|
|
|
227
|
-
Similar to `allowedAttributes`, you can use `*` as a tag name
|
|
241
|
+
Similar to `allowedAttributes`, you can use `*` to allow classes with a certain prefix, or use `*` as a tag name to allow listed classes to be valid for any tag:
|
|
228
242
|
```js
|
|
229
243
|
allowedClasses: {
|
|
244
|
+
'code': [ 'language-*', 'lang-*' ],
|
|
230
245
|
'*': [ 'fancy', 'simple' ]
|
|
231
246
|
}
|
|
232
247
|
```
|
|
233
248
|
|
|
234
249
|
### Allowed CSS Styles
|
|
235
250
|
|
|
236
|
-
If you wish to allow specific CSS _styles_ on a particular element, you can do that with the `allowedStyles` option. Simply declare your desired attributes as regular expression options within an array for the given attribute. Specific elements will inherit
|
|
251
|
+
If you wish to allow specific CSS _styles_ on a particular element, you can do that with the `allowedStyles` option. Simply declare your desired attributes as regular expression options within an array for the given attribute. Specific elements will inherit allowlisted attributes from the global (`*`) attribute. Any other CSS classes are discarded.
|
|
237
252
|
|
|
238
253
|
**You must also use `allowedAttributes`** to activate the `style` attribute for the relevant elements. Otherwise this feature will never come into play.
|
|
239
254
|
|
|
@@ -502,6 +517,32 @@ const clean = sanitizeHtml('<p><iframe src="https://us02web.zoom.us/embed/12345"
|
|
|
502
517
|
});
|
|
503
518
|
```
|
|
504
519
|
|
|
520
|
+
### Script Filters
|
|
521
|
+
|
|
522
|
+
Similarly to iframes you can allow a script tag on a list of allowlisted domains
|
|
523
|
+
|
|
524
|
+
```js
|
|
525
|
+
const clean = sanitizeHtml('<script src="https://www.safe.authorized.com/lib.js"></script>', {
|
|
526
|
+
allowedTags: ['script'],
|
|
527
|
+
allowedAttributes: {
|
|
528
|
+
script: ['src']
|
|
529
|
+
},
|
|
530
|
+
allowedScriptDomains: ['authorized.com'],
|
|
531
|
+
})
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
You can allow a script tag on a list of allowlisted hostnames too
|
|
535
|
+
|
|
536
|
+
```js
|
|
537
|
+
const clean = sanitizeHtml('<script src="https://www.authorized.com/lib.js"></script>', {
|
|
538
|
+
allowedTags: ['script'],
|
|
539
|
+
allowedAttributes: {
|
|
540
|
+
script: ['src']
|
|
541
|
+
},
|
|
542
|
+
allowedScriptHostnames: [ 'www.authorized.com' ],
|
|
543
|
+
})
|
|
544
|
+
```
|
|
545
|
+
|
|
505
546
|
### Allowed URL schemes
|
|
506
547
|
|
|
507
548
|
By default, we allow the following URL schemes in cases where `href`, `src`, etc. are allowed:
|
package/index.js
CHANGED
|
@@ -81,6 +81,10 @@ const VALID_HTML_ATTRIBUTE_NAME = /^[^\0\t\n\f\r /<=>]+$/;
|
|
|
81
81
|
// https://github.com/fb55/htmlparser2/issues/105
|
|
82
82
|
|
|
83
83
|
function sanitizeHtml(html, options, _recursing) {
|
|
84
|
+
if (html == null) {
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
|
|
84
88
|
let result = '';
|
|
85
89
|
// Used for hot swapping the result variable with an empty string in order to "capture" the text written to it.
|
|
86
90
|
let tempResult = '';
|
|
@@ -146,10 +150,13 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
146
150
|
allowedAttributesMap[tag].push(obj);
|
|
147
151
|
}
|
|
148
152
|
});
|
|
149
|
-
|
|
153
|
+
if (globRegex.length) {
|
|
154
|
+
allowedAttributesGlobMap[tag] = new RegExp('^(' + globRegex.join('|') + ')$');
|
|
155
|
+
}
|
|
150
156
|
});
|
|
151
157
|
}
|
|
152
158
|
const allowedClassesMap = {};
|
|
159
|
+
const allowedClassesGlobMap = {};
|
|
153
160
|
each(options.allowedClasses, function(classes, tag) {
|
|
154
161
|
// Implicitly allows the class attribute
|
|
155
162
|
if (allowedAttributesMap) {
|
|
@@ -159,7 +166,18 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
159
166
|
allowedAttributesMap[tag].push('class');
|
|
160
167
|
}
|
|
161
168
|
|
|
162
|
-
allowedClassesMap[tag] =
|
|
169
|
+
allowedClassesMap[tag] = [];
|
|
170
|
+
const globRegex = [];
|
|
171
|
+
classes.forEach(function(obj) {
|
|
172
|
+
if (typeof obj === 'string' && obj.indexOf('*') >= 0) {
|
|
173
|
+
globRegex.push(escapeStringRegexp(obj).replace(/\\\*/g, '.*'));
|
|
174
|
+
} else {
|
|
175
|
+
allowedClassesMap[tag].push(obj);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
if (globRegex.length) {
|
|
179
|
+
allowedClassesGlobMap[tag] = new RegExp('^(' + globRegex.join('|') + ')$');
|
|
180
|
+
}
|
|
163
181
|
});
|
|
164
182
|
|
|
165
183
|
const transformTagsMap = {};
|
|
@@ -251,6 +269,13 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
251
269
|
result = '';
|
|
252
270
|
}
|
|
253
271
|
result += '<' + name;
|
|
272
|
+
|
|
273
|
+
if (name === 'script') {
|
|
274
|
+
if (options.allowedScriptHostnames || options.allowedScriptDomains) {
|
|
275
|
+
frame.innerText = '';
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
254
279
|
if (!allowedAttributesMap || has(allowedAttributesMap, name) || allowedAttributesMap['*']) {
|
|
255
280
|
each(attribs, function(value, a) {
|
|
256
281
|
if (!VALID_HTML_ATTRIBUTE_NAME.test(a)) {
|
|
@@ -301,6 +326,33 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
301
326
|
return;
|
|
302
327
|
}
|
|
303
328
|
}
|
|
329
|
+
|
|
330
|
+
if (name === 'script' && a === 'src') {
|
|
331
|
+
|
|
332
|
+
let allowed = true;
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
const parsed = new URL(value);
|
|
336
|
+
|
|
337
|
+
if (options.allowedScriptHostnames || options.allowedScriptDomains) {
|
|
338
|
+
const allowedHostname = (options.allowedScriptHostnames || []).find(function (hostname) {
|
|
339
|
+
return hostname === parsed.hostname;
|
|
340
|
+
});
|
|
341
|
+
const allowedDomain = (options.allowedScriptDomains || []).find(function(domain) {
|
|
342
|
+
return parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`);
|
|
343
|
+
});
|
|
344
|
+
allowed = allowedHostname || allowedDomain;
|
|
345
|
+
}
|
|
346
|
+
} catch (e) {
|
|
347
|
+
allowed = false;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!allowed) {
|
|
351
|
+
delete frame.attribs[a];
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
304
356
|
if (name === 'iframe' && a === 'src') {
|
|
305
357
|
let allowed = true;
|
|
306
358
|
try {
|
|
@@ -379,10 +431,17 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
379
431
|
if (a === 'class') {
|
|
380
432
|
const allowedSpecificClasses = allowedClassesMap[name];
|
|
381
433
|
const allowedWildcardClasses = allowedClassesMap['*'];
|
|
434
|
+
const allowedSpecificClassesGlob = allowedClassesGlobMap[name];
|
|
435
|
+
const allowedWildcardClassesGlob = allowedClassesGlobMap['*'];
|
|
436
|
+
const allowedClassesGlobs = [ allowedSpecificClassesGlob, allowedWildcardClassesGlob ].filter(
|
|
437
|
+
function(t) {
|
|
438
|
+
return t;
|
|
439
|
+
}
|
|
440
|
+
);
|
|
382
441
|
if (allowedSpecificClasses && allowedWildcardClasses) {
|
|
383
|
-
value = filterClasses(value, deepmerge(allowedSpecificClasses, allowedWildcardClasses));
|
|
442
|
+
value = filterClasses(value, deepmerge(allowedSpecificClasses, allowedWildcardClasses), allowedClassesGlobs);
|
|
384
443
|
} else {
|
|
385
|
-
value = filterClasses(value, allowedSpecificClasses || allowedWildcardClasses);
|
|
444
|
+
value = filterClasses(value, allowedSpecificClasses || allowedWildcardClasses, allowedClassesGlobs);
|
|
386
445
|
}
|
|
387
446
|
if (!value.length) {
|
|
388
447
|
delete frame.attribs[a];
|
|
@@ -590,7 +649,7 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
590
649
|
}
|
|
591
650
|
|
|
592
651
|
/**
|
|
593
|
-
* Filters user input css properties by
|
|
652
|
+
* Filters user input css properties by allowlisted regex attributes.
|
|
594
653
|
*
|
|
595
654
|
* @param {object} abstractSyntaxTree - Object representation of CSS attributes.
|
|
596
655
|
* @property {array[Declaration]} abstractSyntaxTree.nodes[0] - Each object cointains prop and value key, i.e { prop: 'color', value: 'red' }.
|
|
@@ -643,10 +702,10 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
643
702
|
|
|
644
703
|
/**
|
|
645
704
|
* Filters the existing attributes for the given property. Discards any attributes
|
|
646
|
-
* which don't match the
|
|
705
|
+
* which don't match the allowlist.
|
|
647
706
|
*
|
|
648
707
|
* @param {object} selectedRule - Example: { color: red, font-family: helvetica }
|
|
649
|
-
* @param {array} allowedDeclarationsList - List of declarations which pass
|
|
708
|
+
* @param {array} allowedDeclarationsList - List of declarations which pass the allowlist.
|
|
650
709
|
* @param {object} attributeObject - Object representing the current css property.
|
|
651
710
|
* @property {string} attributeObject.type - Typically 'declaration'.
|
|
652
711
|
* @property {string} attributeObject.prop - The CSS property, i.e 'color'.
|
|
@@ -655,7 +714,7 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
655
714
|
*/
|
|
656
715
|
function filterDeclarations(selectedRule) {
|
|
657
716
|
return function (allowedDeclarationsList, attributeObject) {
|
|
658
|
-
// If this property is
|
|
717
|
+
// If this property is allowlisted...
|
|
659
718
|
if (has(selectedRule, attributeObject.prop)) {
|
|
660
719
|
const matchesRegex = selectedRule[attributeObject.prop].some(function(regularExpression) {
|
|
661
720
|
return regularExpression.test(attributeObject.value);
|
|
@@ -669,14 +728,16 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
669
728
|
};
|
|
670
729
|
}
|
|
671
730
|
|
|
672
|
-
function filterClasses(classes, allowed) {
|
|
731
|
+
function filterClasses(classes, allowed, allowedGlobs) {
|
|
673
732
|
if (!allowed) {
|
|
674
733
|
// The class attribute is allowed without filtering on this tag
|
|
675
734
|
return classes;
|
|
676
735
|
}
|
|
677
736
|
classes = classes.split(/\s+/);
|
|
678
737
|
return classes.filter(function(clss) {
|
|
679
|
-
return allowed.indexOf(clss) !== -1
|
|
738
|
+
return allowed.indexOf(clss) !== -1 || allowedGlobs.some(function(glob) {
|
|
739
|
+
return glob.test(clss);
|
|
740
|
+
});
|
|
680
741
|
}).join(' ');
|
|
681
742
|
}
|
|
682
743
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanitize-html",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Clean up user-submitted HTML, preserving
|
|
3
|
+
"version": "2.5.2",
|
|
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",
|
|
7
7
|
"files": [
|