sanitize-html 2.4.0 → 2.5.3
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 +18 -0
- package/README.md +30 -3
- package/index.js +46 -9
- package/package.json +4 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.5.3 (2021-11-02):
|
|
4
|
+
|
|
5
|
+
- Fixed bug introduced by klona 2.0.5, by removing klona entirely.
|
|
6
|
+
|
|
7
|
+
## 2.5.2 (2021-10-13):
|
|
8
|
+
|
|
9
|
+
- 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.
|
|
10
|
+
- Documented that all text content is escaped. Thanks to Siddharth Singh.
|
|
11
|
+
|
|
12
|
+
## 2.5.1 (2021-09-14):
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
15
|
+
## 2.5.0 (2021-09-08):
|
|
16
|
+
|
|
17
|
+
- New `allowedScriptHostnames` option, it enables you to specify which hostnames are allowed in a script tag.
|
|
18
|
+
- 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.
|
|
19
|
+
- Updates whitelist to allowlist.
|
|
20
|
+
|
|
3
21
|
## 2.4.0 (2021-05-19):
|
|
4
22
|
- Added support for class names with wildcards in `allowedClasses`. Thanks to [zhangbenber](https://github.com/zhangbenber) for the contribution.
|
|
5
23
|
|
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ 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
|
|
|
@@ -29,7 +30,7 @@ sanitize-html is intended for use with Node.js and supports Node 10+. All of its
|
|
|
29
30
|
### Regarding TypeScript
|
|
30
31
|
|
|
31
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.
|
|
32
|
-
```bash
|
|
33
|
+
```bash
|
|
33
34
|
npm install -D @types/sanitize-html
|
|
34
35
|
```
|
|
35
36
|
If `esModuleInterop=true` is not set in your `tsconfig.json` file, you have to import it with:
|
|
@@ -51,7 +52,7 @@ But, perhaps you'd like to display sanitized HTML immediately in the browser for
|
|
|
51
52
|
* Install the package:
|
|
52
53
|
|
|
53
54
|
```bash
|
|
54
|
-
npm install sanitize-html
|
|
55
|
+
npm install sanitize-html
|
|
55
56
|
```
|
|
56
57
|
or
|
|
57
58
|
```
|
|
@@ -247,7 +248,7 @@ allowedClasses: {
|
|
|
247
248
|
|
|
248
249
|
### Allowed CSS Styles
|
|
249
250
|
|
|
250
|
-
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.
|
|
251
252
|
|
|
252
253
|
**You must also use `allowedAttributes`** to activate the `style` attribute for the relevant elements. Otherwise this feature will never come into play.
|
|
253
254
|
|
|
@@ -516,6 +517,32 @@ const clean = sanitizeHtml('<p><iframe src="https://us02web.zoom.us/embed/12345"
|
|
|
516
517
|
});
|
|
517
518
|
```
|
|
518
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
|
+
|
|
519
546
|
### Allowed URL schemes
|
|
520
547
|
|
|
521
548
|
By default, we allow the following URL schemes in cases where `href`, `src`, etc. are allowed:
|
package/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const htmlparser = require('htmlparser2');
|
|
2
2
|
const escapeStringRegexp = require('escape-string-regexp');
|
|
3
|
-
const { klona } = require('klona');
|
|
4
3
|
const { isPlainObject } = require('is-plain-object');
|
|
5
4
|
const deepmerge = require('deepmerge');
|
|
6
5
|
const parseSrcset = require('parse-srcset');
|
|
@@ -81,6 +80,10 @@ const VALID_HTML_ATTRIBUTE_NAME = /^[^\0\t\n\f\r /<=>]+$/;
|
|
|
81
80
|
// https://github.com/fb55/htmlparser2/issues/105
|
|
82
81
|
|
|
83
82
|
function sanitizeHtml(html, options, _recursing) {
|
|
83
|
+
if (html == null) {
|
|
84
|
+
return '';
|
|
85
|
+
}
|
|
86
|
+
|
|
84
87
|
let result = '';
|
|
85
88
|
// Used for hot swapping the result variable with an empty string in order to "capture" the text written to it.
|
|
86
89
|
let tempResult = '';
|
|
@@ -265,6 +268,13 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
265
268
|
result = '';
|
|
266
269
|
}
|
|
267
270
|
result += '<' + name;
|
|
271
|
+
|
|
272
|
+
if (name === 'script') {
|
|
273
|
+
if (options.allowedScriptHostnames || options.allowedScriptDomains) {
|
|
274
|
+
frame.innerText = '';
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
268
278
|
if (!allowedAttributesMap || has(allowedAttributesMap, name) || allowedAttributesMap['*']) {
|
|
269
279
|
each(attribs, function(value, a) {
|
|
270
280
|
if (!VALID_HTML_ATTRIBUTE_NAME.test(a)) {
|
|
@@ -315,6 +325,33 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
315
325
|
return;
|
|
316
326
|
}
|
|
317
327
|
}
|
|
328
|
+
|
|
329
|
+
if (name === 'script' && a === 'src') {
|
|
330
|
+
|
|
331
|
+
let allowed = true;
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
const parsed = new URL(value);
|
|
335
|
+
|
|
336
|
+
if (options.allowedScriptHostnames || options.allowedScriptDomains) {
|
|
337
|
+
const allowedHostname = (options.allowedScriptHostnames || []).find(function (hostname) {
|
|
338
|
+
return hostname === parsed.hostname;
|
|
339
|
+
});
|
|
340
|
+
const allowedDomain = (options.allowedScriptDomains || []).find(function(domain) {
|
|
341
|
+
return parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`);
|
|
342
|
+
});
|
|
343
|
+
allowed = allowedHostname || allowedDomain;
|
|
344
|
+
}
|
|
345
|
+
} catch (e) {
|
|
346
|
+
allowed = false;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!allowed) {
|
|
350
|
+
delete frame.attribs[a];
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
318
355
|
if (name === 'iframe' && a === 'src') {
|
|
319
356
|
let allowed = true;
|
|
320
357
|
try {
|
|
@@ -611,19 +648,19 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
611
648
|
}
|
|
612
649
|
|
|
613
650
|
/**
|
|
614
|
-
* Filters user input css properties by
|
|
651
|
+
* Filters user input css properties by allowlisted regex attributes.
|
|
652
|
+
* Modifies the abstractSyntaxTree object.
|
|
615
653
|
*
|
|
616
654
|
* @param {object} abstractSyntaxTree - Object representation of CSS attributes.
|
|
617
655
|
* @property {array[Declaration]} abstractSyntaxTree.nodes[0] - Each object cointains prop and value key, i.e { prop: 'color', value: 'red' }.
|
|
618
656
|
* @param {object} allowedStyles - Keys are properties (i.e color), value is list of permitted regex rules (i.e /green/i).
|
|
619
|
-
* @return {object} -
|
|
657
|
+
* @return {object} - The modified tree.
|
|
620
658
|
*/
|
|
621
659
|
function filterCss(abstractSyntaxTree, allowedStyles) {
|
|
622
660
|
if (!allowedStyles) {
|
|
623
661
|
return abstractSyntaxTree;
|
|
624
662
|
}
|
|
625
663
|
|
|
626
|
-
const filteredAST = klona(abstractSyntaxTree);
|
|
627
664
|
const astRules = abstractSyntaxTree.nodes[0];
|
|
628
665
|
let selectedRule;
|
|
629
666
|
|
|
@@ -638,10 +675,10 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
638
675
|
}
|
|
639
676
|
|
|
640
677
|
if (selectedRule) {
|
|
641
|
-
|
|
678
|
+
abstractSyntaxTree.nodes[0].nodes = astRules.nodes.reduce(filterDeclarations(selectedRule), []);
|
|
642
679
|
}
|
|
643
680
|
|
|
644
|
-
return
|
|
681
|
+
return abstractSyntaxTree;
|
|
645
682
|
}
|
|
646
683
|
|
|
647
684
|
/**
|
|
@@ -664,10 +701,10 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
664
701
|
|
|
665
702
|
/**
|
|
666
703
|
* Filters the existing attributes for the given property. Discards any attributes
|
|
667
|
-
* which don't match the
|
|
704
|
+
* which don't match the allowlist.
|
|
668
705
|
*
|
|
669
706
|
* @param {object} selectedRule - Example: { color: red, font-family: helvetica }
|
|
670
|
-
* @param {array} allowedDeclarationsList - List of declarations which pass
|
|
707
|
+
* @param {array} allowedDeclarationsList - List of declarations which pass the allowlist.
|
|
671
708
|
* @param {object} attributeObject - Object representing the current css property.
|
|
672
709
|
* @property {string} attributeObject.type - Typically 'declaration'.
|
|
673
710
|
* @property {string} attributeObject.prop - The CSS property, i.e 'color'.
|
|
@@ -676,7 +713,7 @@ function sanitizeHtml(html, options, _recursing) {
|
|
|
676
713
|
*/
|
|
677
714
|
function filterDeclarations(selectedRule) {
|
|
678
715
|
return function (allowedDeclarationsList, attributeObject) {
|
|
679
|
-
// If this property is
|
|
716
|
+
// If this property is allowlisted...
|
|
680
717
|
if (has(selectedRule, attributeObject.prop)) {
|
|
681
718
|
const matchesRegex = selectedRule[attributeObject.prop].some(function(regularExpression) {
|
|
682
719
|
return regularExpression.test(attributeObject.value);
|
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.3",
|
|
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": [
|
|
@@ -27,15 +27,14 @@
|
|
|
27
27
|
"escape-string-regexp": "^4.0.0",
|
|
28
28
|
"htmlparser2": "^6.0.0",
|
|
29
29
|
"is-plain-object": "^5.0.0",
|
|
30
|
-
"klona": "^2.0.3",
|
|
31
30
|
"parse-srcset": "^1.0.2",
|
|
32
|
-
"postcss": "^8.
|
|
31
|
+
"postcss": "^8.3.11"
|
|
33
32
|
},
|
|
34
33
|
"devDependencies": {
|
|
35
34
|
"eslint": "^7.3.1",
|
|
36
35
|
"eslint-config-apostrophe": "^3.4.0",
|
|
37
36
|
"eslint-config-standard": "^14.1.1",
|
|
38
|
-
"eslint-plugin-import": "^2.
|
|
37
|
+
"eslint-plugin-import": "^2.25.2",
|
|
39
38
|
"eslint-plugin-node": "^11.1.0",
|
|
40
39
|
"eslint-plugin-promise": "^4.2.1",
|
|
41
40
|
"eslint-plugin-standard": "^4.0.1",
|