sanitize-html 2.17.3 → 2.17.5-alpha.1

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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/index.js +18 -42
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -695,13 +695,13 @@ Normally, with a few exceptions, if a tag is not allowed, all of the text within
695
695
 
696
696
  The exceptions are:
697
697
 
698
- `style`, `script`, `textarea`, `option`
698
+ `style`, `script`, `textarea`, `option`, `xmp`
699
699
 
700
700
  If you wish to replace this list, for instance to discard whatever is found
701
701
  inside a `noscript` tag, use the `nonTextTags` option:
702
702
 
703
703
  ```js
704
- nonTextTags: [ 'style', 'script', 'textarea', 'option', 'noscript' ]
704
+ nonTextTags: [ 'style', 'script', 'textarea', 'option', 'xmp', 'noscript' ]
705
705
  ```
706
706
 
707
707
  Note that if you use this option you are responsible for stating the entire list. This gives you the power to retain the content of `textarea`, if you want to.
package/index.js CHANGED
@@ -4,6 +4,7 @@ const { isPlainObject } = require('is-plain-object');
4
4
  const deepmerge = require('deepmerge');
5
5
  const parseSrcset = require('parse-srcset');
6
6
  const { parse: postcssParse } = require('postcss');
7
+ const { naughtyHref: launderNaughtyHref } = require('launder');
7
8
  // Tags that can conceivably represent stand-alone media.
8
9
  const mediaTags = [
9
10
  'img', 'audio', 'video', 'picture', 'svg',
@@ -135,11 +136,16 @@ function sanitizeHtml(html, options, _recursing) {
135
136
  // the text when the tag is disallowed makes sense for other reasons.
136
137
  // If we are not allowing these tags, we should drop their content too.
137
138
  // For other tags you would drop the tag but keep its content.
139
+ // `xmp` is included because htmlparser2 treats it as a raw-text element,
140
+ // so markup inside is parsed as text on input but would otherwise be
141
+ // re-emitted unescaped via the `ontext` branch below, allowing XSS bypass
142
+ // when `xmp` is disallowed (which is the default).
138
143
  const nonTextTagsArray = options.nonTextTags || [
139
144
  'script',
140
145
  'style',
141
146
  'textarea',
142
- 'option'
147
+ 'option',
148
+ 'xmp'
143
149
  ];
144
150
  let allowedAttributesMap;
145
151
  let allowedAttributesGlobMap;
@@ -560,13 +566,13 @@ function sanitizeHtml(html, options, _recursing) {
560
566
 
561
567
  if (options.disallowedTagsMode === 'completelyDiscard' && !tagAllowed(tag)) {
562
568
  text = '';
563
- } else if ((options.disallowedTagsMode === 'discard' || options.disallowedTagsMode === 'completelyDiscard') && ((tag === 'script') || (tag === 'style'))) {
569
+ } else if (tag && tagAllowed(tag) && (options.disallowedTagsMode === 'discard' || options.disallowedTagsMode === 'completelyDiscard') && ((tag === 'script') || (tag === 'style'))) {
564
570
  // htmlparser2 gives us these as-is. Escaping them ruins the content. Allowing
565
571
  // script tags is, by definition, game over for XSS protection, so if that's
566
572
  // your concern, don't allow them. The same is essentially true for style tags
567
573
  // which have their own collection of XSS vectors.
568
574
  result += text;
569
- } else if ((options.disallowedTagsMode === 'discard' || options.disallowedTagsMode === 'completelyDiscard') && (tag === 'textarea' || tag === 'xmp')) {
575
+ } else if (tag && tagAllowed(tag) && (options.disallowedTagsMode === 'discard' || options.disallowedTagsMode === 'completelyDiscard') && (tag === 'textarea' || tag === 'xmp')) {
570
576
  // htmlparser2 treats <textarea> and <xmp> as raw text elements and
571
577
  // does NOT decode entities inside them. The text is already properly
572
578
  // encoded, so pass it through without additional escaping to avoid
@@ -726,45 +732,15 @@ function sanitizeHtml(html, options, _recursing) {
726
732
  }
727
733
 
728
734
  function naughtyHref(name, href) {
729
- // Browsers ignore character codes of 32 (space) and below in a surprising
730
- // number of situations. Start reading here:
731
- // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet#Embedded_tab
732
- // eslint-disable-next-line no-control-regex
733
- href = href.replace(/[\x00-\x20]+/g, '');
734
- // Clobber any comments in URLs, which the browser might
735
- // interpret inside an XML data island, allowing
736
- // a javascript: URL to be snuck through
737
- while (true) {
738
- const firstIndex = href.indexOf('<!--');
739
- if (firstIndex === -1) {
740
- break;
741
- }
742
- const lastIndex = href.indexOf('-->', firstIndex + 4);
743
- if (lastIndex === -1) {
744
- break;
745
- }
746
- href = href.substring(0, firstIndex) + href.substring(lastIndex + 3);
747
- }
748
- // Case insensitive so we don't get faked out by JAVASCRIPT #1
749
- // Allow more characters after the first so we don't get faked
750
- // out by certain schemes browsers accept
751
- const matches = href.match(/^([a-zA-Z][a-zA-Z0-9.\-+]*):/);
752
- if (!matches) {
753
- // Protocol-relative URL starting with any combination of '/' and '\'
754
- if (href.match(/^[/\\]{2}/)) {
755
- return !options.allowProtocolRelative;
756
- }
757
-
758
- // No scheme
759
- return false;
760
- }
761
- const scheme = matches[1].toLowerCase();
762
-
763
- if (has(options.allowedSchemesByTag, name)) {
764
- return options.allowedSchemesByTag[name].indexOf(scheme) === -1;
765
- }
766
-
767
- return !options.allowedSchemes || options.allowedSchemes.indexOf(scheme) === -1;
735
+ // Resolve the per-tag scheme allowlist if one is configured for
736
+ // this tag, otherwise fall back to the global allowedSchemes.
737
+ const allowedSchemes = has(options.allowedSchemesByTag, name)
738
+ ? options.allowedSchemesByTag[name]
739
+ : (options.allowedSchemes || []);
740
+ return launderNaughtyHref(href, {
741
+ allowedSchemes,
742
+ allowProtocolRelative: options.allowProtocolRelative
743
+ });
768
744
  }
769
745
 
770
746
  function parseUrl(value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanitize-html",
3
- "version": "2.17.3",
3
+ "version": "2.17.5-alpha.1",
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",
@@ -27,7 +27,8 @@
27
27
  "htmlparser2": "^10.1.0",
28
28
  "is-plain-object": "^5.0.0",
29
29
  "parse-srcset": "^1.0.2",
30
- "postcss": "^8.3.11"
30
+ "postcss": "^8.3.11",
31
+ "launder": "^1.7.2-alpha.1"
31
32
  },
32
33
  "devDependencies": {
33
34
  "eslint": "^9.39.1",