Repository: apostrophecms/sanitize-html
Pull request #414 (2020-09-16 12:02:36)

nestingLimit option
There is also an eslint formatting update here, but it should be easier to read with github's "ignore whitespace changes" feature.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6d9a16c..c84e6cb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
 # Changelog
 
+## 2.0.0-rc.4 (2020-09-23):
+- `nestingLimit` option added.
+
 ## 2.0.0-rc.3 (2020-09-09):
 - Upgrade `is-plain-object` package with named export. Thanks to [Bogdan Chadkin](https://github.com/TrySound) for the contribution.
 
diff --git a/README.md b/README.md
index 826a4fa..cbb2003 100644
--- a/README.md
+++ b/README.md
@@ -543,6 +543,16 @@ This will transform `<disallowed>content</disallowed>` to `&lt;disallowed&gt;con
 
 Valid values are: `'discard'` (default), `'escape'` (escape the tag) and `'recursiveEscape'` (to escape the tag and all its content).
 
+### Restricting deep nesting
+
+You can limit the depth of HTML tags in the document with the `nestingLimit` option:
+
+```javascript
+nestingLimit: 6
+```
+
+This will prevent the user from nesting tags more than 6 levels deep. Tags deeper than that are stripped out exactly as if they were disallowed. Note that this means text is preserved in the usual ways where appropriate.
+
 ## About ApostropheCMS
 
 `sanitize-html` was created at [P'unk Avenue](https://punkave.com) for use in [ApostropheCMS](https://apostrophecms.com), an open-source content management system built on Node.js. If you like `sanitize-html` you should definitely check out ApostropheCMS.
diff --git a/index.js b/index.js
index f03fd3a..c14c280 100644
--- a/index.js
+++ b/index.js
@@ -12,7 +12,7 @@ const mediaTags = [
   'object', 'map', 'iframe', 'embed'
 ];
 // Tags that are inherently vulnerable to being used in XSS attacks.
-const vulnerableTags = ['script', 'style'];
+const vulnerableTags = [ 'script', 'style' ];
 
 function each(obj, cb) {
   if (obj) {
@@ -224,7 +224,7 @@ function sanitizeHtml(html, options, _recursing) {
         }
       }
 
-      if ((options.allowedTags && options.allowedTags.indexOf(name) === -1) || (options.disallowedTagsMode === 'recursiveEscape' && !isEmptyObject(skipMap))) {
+      if ((options.allowedTags && options.allowedTags.indexOf(name) === -1) || (options.disallowedTagsMode === 'recursiveEscape' && !isEmptyObject(skipMap)) || (options.nestingLimit != null && depth >= options.nestingLimit)) {
         skip = true;
         skipMap[depth] = true;
         if (options.disallowedTagsMode === 'discard') {
@@ -680,18 +680,18 @@ sanitizeHtml.defaults = {
   ],
   disallowedTagsMode: 'discard',
   allowedAttributes: {
-    a: ['href', 'name', 'target'],
+    a: [ 'href', 'name', 'target' ],
     // We don't currently allow img itself by default, but this
     // would make sense if we did. You could add srcset here,
     // and if you do the URL is checked for safety
-    img: ['src']
+    img: [ 'src' ]
   },
   // Lots of these won't come up by default because we don't allow them
-  selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'],
+  selfClosing: [ 'img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta' ],
   // URL schemes we permit
-  allowedSchemes: ['http', 'https', 'ftp', 'mailto'],
+  allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ],
   allowedSchemesByTag: {},
-  allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'],
+  allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ],
   allowProtocolRelative: true,
   enforceHtmlBoundary: false
 };
diff --git a/package.json b/package.json
index 62b15f8..ffc79fe 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "sanitize-html",
-  "version": "2.0.0-rc.2",
+  "version": "2.0.0-rc.3",
   "description": "Clean up user-submitted HTML, preserving whitelisted elements and whitelisted attributes on a per-element basis",
   "sideEffects": false,
   "main": "index.js",


Test patch:
diff --git a/test/test.js b/test/test.js
index f0c9731..04fd8ce 100644
--- a/test/test.js
+++ b/test/test.js
@@ -50,17 +50,17 @@ describe('sanitizeHtml', function() {
     assert.equal(sanitizeHtml('<div><wiggly>Hello</wiggly></div>', { disallowedTagsMode: 'escape' }), '<div>&lt;wiggly&gt;Hello&lt;/wiggly&gt;</div>');
   });
   it('should accept a custom list of allowed tags', function() {
-    assert.equal(sanitizeHtml('<blue><red><green>Cheese</green></red></blue>', { allowedTags: ['blue', 'green'] }), '<blue><green>Cheese</green></blue>');
+    assert.equal(sanitizeHtml('<blue><red><green>Cheese</green></red></blue>', { allowedTags: [ 'blue', 'green' ] }), '<blue><green>Cheese</green></blue>');
   });
   it('should reject attributes not whitelisted', function() {
     assert.equal(sanitizeHtml('<a href="foo.html" whizbang="whangle">foo</a>'), '<a href="foo.html">foo</a>');
   });
   it('should accept a custom list of allowed attributes per element', function() {
-    assert.equal(sanitizeHtml('<a href="foo.html" whizbang="whangle">foo</a>', { allowedAttributes: { a: ['href', 'whizbang'] } }), '<a href="foo.html" whizbang="whangle">foo</a>');
+    assert.equal(sanitizeHtml('<a href="foo.html" whizbang="whangle">foo</a>', { allowedAttributes: { a: [ 'href', 'whizbang' ] } }), '<a href="foo.html" whizbang="whangle">foo</a>');
   });
   it('should clean up unclosed img tags and p tags', function() {
     assert.equal(sanitizeHtml('<img src="foo.jpg"><p>Whee<p>Again<p>Wow<b>cool</b>', {
-      allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img'])
+      allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'img' ])
     }), '<img src="foo.jpg" /><p>Whee</p><p>Again</p><p>Wow<b>cool</b></p>');
   });
   it('should reject hrefs that are not relative, ftp, http, https or mailto', function() {
@@ -88,17 +88,17 @@ describe('sanitizeHtml', function() {
     assert.equal(sanitizeHtml('<fibble>Nifty</fibble><p>Paragraph</p>'), 'Nifty<p>Paragraph</p>');
   });
   it('should discard the content of fibble elements if specified for nonTextTags', function() {
-    assert.equal(sanitizeHtml('<fibble>Nifty</fibble><p>Paragraph</p>', { nonTextTags: ['fibble'] }), '<p>Paragraph</p>');
+    assert.equal(sanitizeHtml('<fibble>Nifty</fibble><p>Paragraph</p>', { nonTextTags: [ 'fibble' ] }), '<p>Paragraph</p>');
   });
   it('should retain allowed tags within a fibble element if fibble is not specified for nonTextTags', function() {
     assert.equal(sanitizeHtml('<fibble>Ni<em>f</em>ty</fibble><p>Paragraph</p>', {}), 'Ni<em>f</em>ty<p>Paragraph</p>');
   });
   it('should discard allowed tags within a fibble element if fibble is specified for nonTextTags', function() {
-    assert.equal(sanitizeHtml('<fibble>Ni<em>f</em>ty</fibble><p>Paragraph</p>', { nonTextTags: ['fibble'] }), '<p>Paragraph</p>');
+    assert.equal(sanitizeHtml('<fibble>Ni<em>f</em>ty</fibble><p>Paragraph</p>', { nonTextTags: [ 'fibble' ] }), '<p>Paragraph</p>');
   });
   it('should preserve textarea content if textareas are allowed', function() {
     assert.equal(sanitizeHtml('<textarea>Nifty</textarea><p>Paragraph</p>', {
-      allowedTags: ['textarea', 'p']
+      allowedTags: [ 'textarea', 'p' ]
     }), '<textarea>Nifty</textarea><p>Paragraph</p>');
   });
   it('should preserve entities as such', function() {
@@ -141,19 +141,19 @@ describe('sanitizeHtml', function() {
   it('should replace ol to ul and add class attribute with foo value', function() {
     assert.equal(sanitizeHtml('<ol><li>Hello world</li></ol>', {
       transformTags: { ol: sanitizeHtml.simpleTransform('ul', { class: 'foo' }) },
-      allowedAttributes: { ul: ['class'] }
+      allowedAttributes: { ul: [ 'class' ] }
     }), '<ul class="foo"><li>Hello world</li></ul>');
   });
   it('should replace ol to ul, left attributes foo and bar untouched, remove baz attribute and add class attributte with foo value', function() {
     assert.equal(sanitizeHtml('<ol foo="foo" bar="bar" baz="baz"><li>Hello world</li></ol>', {
       transformTags: { ol: sanitizeHtml.simpleTransform('ul', { class: 'foo' }) },
-      allowedAttributes: { ul: ['foo', 'bar', 'class'] }
+      allowedAttributes: { ul: [ 'foo', 'bar', 'class' ] }
     }), '<ul foo="foo" bar="bar" class="foo"><li>Hello world</li></ul>');
   });
   it('should replace ol to ul and replace all attributes to class attribute with foo value', function() {
     assert.equal(sanitizeHtml('<ol foo="foo" bar="bar" baz="baz"><li>Hello world</li></ol>', {
       transformTags: { ol: sanitizeHtml.simpleTransform('ul', { class: 'foo' }, false) },
-      allowedAttributes: { ul: ['foo', 'bar', 'class'] }
+      allowedAttributes: { ul: [ 'foo', 'bar', 'class' ] }
     }), '<ul class="foo"><li>Hello world</li></ul>');
   });
   it('should replace ol to ul and add attribute class with foo value and attribute bar with bar value', function() {
@@ -168,7 +168,7 @@ describe('sanitizeHtml', function() {
           };
         }
       },
-      allowedAttributes: { ul: ['bar', 'class'] }
+      allowedAttributes: { ul: [ 'bar', 'class' ] }
     }), '<ul class="foo" bar="bar"><li>Hello world</li></ul>');
   });
 
@@ -290,11 +290,10 @@ describe('sanitizeHtml', function() {
     const markup = '<a href="http://www.linux.org"><img /><video></video></a>';
     const sansVideo = '<a href="http://www.linux.org"><img /></a>';
     const sanitizedMarkup = sanitizeHtml(markup, {
-      allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
+      allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'img' ]),
       exclusiveFilter: function(frame) {
         if (frame.tag === 'a') {
           // eslint-disable-next-line no-console
-          console.log(frame);
           assert(frame.mediaChildren.length === 1);
         }
 
@@ -322,7 +321,7 @@ describe('sanitizeHtml', function() {
         // teeny-tiny valid transparent GIF in a data URL
         '<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />',
         {
-          allowedTags: ['img']
+          allowedTags: [ 'img' ]
         }
       ),
       '<img />'
@@ -334,8 +333,8 @@ describe('sanitizeHtml', function() {
         // teeny-tiny valid transparent GIF in a data URL
         '<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />',
         {
-          allowedTags: ['img', 'p'],
-          allowedSchemes: ['data', 'http']
+          allowedTags: [ 'img', 'p' ],
+          allowedSchemes: [ 'data', 'http' ]
         }
       ),
       '<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" />'
@@ -346,9 +345,9 @@ describe('sanitizeHtml', function() {
       sanitizeHtml(
         '<p class="nifty simple dippy">whee</p>',
         {
-          allowedTags: ['p'],
+          allowedTags: [ 'p' ],
           allowedClasses: {
-            p: ['nifty']
+            p: [ 'nifty' ]
           }
         }
       ),
@@ -361,11 +360,11 @@ describe('sanitizeHtml', function() {
         // teeny-tiny valid transparent GIF in a data URL
         '<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /><a href="https://www.example.com"></a>',
         {
-          allowedTags: ['img', 'a'],
-          allowedSchemes: ['http'],
+          allowedTags: [ 'img', 'a' ],
+          allowedSchemes: [ 'http' ],
           allowedSchemesByTag: {
-            img: ['data'],
-            a: ['https']
+            img: [ 'data' ],
+            a: [ 'https' ]
           }
         }
       ),
@@ -376,11 +375,11 @@ describe('sanitizeHtml', function() {
         // teeny-tiny valid transparent GIF in a data URL
         '<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /><a href="https://www.example.com"></a>',
         {
-          allowedTags: ['img', 'a'],
-          allowedSchemes: ['http'],
+          allowedTags: [ 'img', 'a' ],
+          allowedSchemes: [ 'http' ],
           allowedSchemesByTag: {
             img: [],
-            a: ['https']
+            a: [ 'https' ]
           }
         }
       ),
@@ -392,9 +391,9 @@ describe('sanitizeHtml', function() {
       sanitizeHtml(
         '<p class="">whee</p>',
         {
-          allowedTags: ['p'],
+          allowedTags: [ 'p' ],
           allowedClasses: {
-            p: ['nifty']
+            p: [ 'nifty' ]
           }
         }
       ),
@@ -414,9 +413,9 @@ describe('sanitizeHtml', function() {
       sanitizeHtml(
         '<IMG SRC= onmouseover="alert(\'XSS\');">',
         {
-          allowedTags: ['img'],
+          allowedTags: [ 'img' ],
           allowedAttributes: {
-            img: ['src']
+            img: [ 'src' ]
           }
         }
       ),
@@ -434,7 +433,7 @@ describe('sanitizeHtml', function() {
     sanitizeHtml(
       '<style></style>',
       {
-        allowedTags: ['style']
+        allowedTags: [ 'style' ]
       }
     );
 
@@ -450,7 +449,7 @@ describe('sanitizeHtml', function() {
       '<style></style>',
       {
         allowVulnerableTags: true,
-        allowedTags: ['style']
+        allowedTags: [ 'style' ]
       }
     );
 
@@ -464,9 +463,9 @@ describe('sanitizeHtml', function() {
       sanitizeHtml(
         '<table bgcolor="1" align="left" notlisted="0"><img src="1.gif" align="center" alt="not listed too"/></table>',
         {
-          allowedTags: ['table', 'img'],
+          allowedTags: [ 'table', 'img' ],
           allowedAttributes: {
-            '*': ['bgcolor', 'align', 'src']
+            '*': [ 'bgcolor', 'align', 'src' ]
           }
         }
       ),
@@ -478,8 +477,8 @@ describe('sanitizeHtml', function() {
       sanitizeHtml(
         '<a href="test.html">test</a>',
         {
-          allowedTags: ['a'],
-          allowedAttributes: { a: ['href', 'target'] },
+          allowedTags: [ 'a' ],
+          allowedAttributes: { a: [ 'href', 'target' ] },
           transformTags: {
             a: function (tagName, attribs) {
               if (!attribs.href) {
@@ -507,8 +506,8 @@ describe('sanitizeHtml', function() {
       sanitizeHtml(
         '<a href="test.html">blah</a>',
         {
-          allowedTags: ['a'],
-          allowedAttributes: { a: ['href', 'target'] },
+          allowedTags: [ 'a' ],
+          allowedAttributes: { a: [ 'href', 'target' ] },
           transformTags: {
             a: function (tagName, attribs) {
               if (!attribs.href) {
@@ -536,8 +535,8 @@ describe('sanitizeHtml', function() {
       sanitizeHtml(
         '<p>Text</p>',
         {
-          allowedTags: ['p'],
-          allowedAttributes: { p: ['style'] },
+          allowedTags: [ 'p' ],
+          allowedAttributes: { p: [ 'style' ] },
           transformTags: {
             '*': function (tagName, attribs) {
               return {
@@ -568,34 +567,34 @@ describe('sanitizeHtml', function() {
   it('should allow attributes to be specified as globs', function() {
     assert.equal(
       sanitizeHtml('<a data-target="#test" data-foo="hello">click me</a>', {
-        allowedTags: ['a'],
-        allowedAttributes: { a: ['data-*'] }
+        allowedTags: [ 'a' ],
+        allowedAttributes: { a: [ 'data-*' ] }
       }), '<a data-target="#test" data-foo="hello">click me</a>'
     );
     assert.equal(
       sanitizeHtml('<a data-target="#test" data-my-foo="hello">click me</a>', {
-        allowedTags: ['a'],
-        allowedAttributes: { a: ['data-*-foo'] }
+        allowedTags: [ 'a' ],
+        allowedAttributes: { a: [ 'data-*-foo' ] }
       }), '<a data-my-foo="hello">click me</a>'
     );
   });
   it('should quote regex chars in attributes specified as globs', function() {
     assert.equal(
       sanitizeHtml('<a data-b.c="#test" data-bcc="remove this">click me</a>', {
-        allowedTags: ['a'],
-        allowedAttributes: { a: ['data-b.*'] }
+        allowedTags: [ 'a' ],
+        allowedAttributes: { a: [ 'data-b.*' ] }
       }), '<a data-b.c="#test">click me</a>'
     );
   });
   it('should not escape inner content of script and style tags (when allowed)', function() {
     assert.equal(
       sanitizeHtml('<div>"normal text"</div><script>"this is code"</script>', {
-        allowedTags: ['script']
+        allowedTags: [ 'script' ]
       }), '"normal text"<script>"this is code"</script>'
     );
     assert.equal(
       sanitizeHtml('<div>"normal text"</div><style>body { background-image: url("image.test"); }</style>', {
-        allowedTags: ['style']
+        allowedTags: [ 'style' ]
       }), '"normal text"<style>body { background-image: url("image.test"); }</style>'
     );
   });
@@ -603,7 +602,7 @@ describe('sanitizeHtml', function() {
     assert.equal(
       sanitizeHtml('<script>alert("&quot;This is cool but just ironically so I quoted it&quot;")</script>',
         {
-          allowedTags: ['script']
+          allowedTags: [ 'script' ]
         }
       ),
       '<script>alert("&quot;This is cool but just ironically so I quoted it&quot;")</script>'
@@ -657,7 +656,7 @@ describe('sanitizeHtml', function() {
   it('should correctly maintain escaping when allowing a nonTextTags tag other than script or style', function() {
     assert.equal(
       sanitizeHtml('!<textarea>&lt;/textarea&gt;&lt;svg/onload=prompt`xs`&gt;</textarea>!',
-        { allowedTags: ['textarea'] }
+        { allowedTags: [ 'textarea' ] }
       ), '!<textarea>&lt;/textarea&gt;&lt;svg/onload=prompt`xs`&gt;</textarea>!'
     );
   });
@@ -694,7 +693,7 @@ describe('sanitizeHtml', function() {
   it('should discard srcset by default', function() {
     assert.equal(
       sanitizeHtml('<img src="fallback.jpg" srcset="foo.jpg 100w 2x, bar.jpg 200w 1x" />', {
-        allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img'])
+        allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'img' ])
       }),
       '<img src="fallback.jpg" />'
     );
@@ -702,15 +701,15 @@ describe('sanitizeHtml', function() {
   it('should accept srcset if allowed', function() {
     assert.equal(
       sanitizeHtml('<img src="fallback.jpg" srcset="foo.jpg 100w, bar.jpg 200w" />', {
-        allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
-        allowedAttributes: { img: ['src', 'srcset'] }
+        allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'img' ]),
+        allowedAttributes: { img: [ 'src', 'srcset' ] }
       }),
       '<img src="fallback.jpg" srcset="foo.jpg 100w, bar.jpg 200w" />'
     );
     assert.equal(
       sanitizeHtml('<img src="fallback.jpg" srcset="foo.jpg 2x, bar.jpg 1x" />', {
-        allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
-        allowedAttributes: { img: ['src', 'srcset'] }
+        allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'img' ]),
+        allowedAttributes: { img: [ 'src', 'srcset' ] }
       }),
       '<img src="fallback.jpg" srcset="foo.jpg 2x, bar.jpg 1x" />'
     );
@@ -718,8 +717,8 @@ describe('sanitizeHtml', function() {
   it('should drop bogus srcset', function() {
     assert.equal(
       sanitizeHtml('<img src="fallback.jpg" srcset="foo.jpg 100w, bar.jpg 200w, javascript:alert(1) 100w" />', {
-        allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
-        allowedAttributes: { img: ['src', 'srcset'] }
+        allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'img' ]),
+        allowedAttributes: { img: [ 'src', 'srcset' ] }
       }),
       '<img src="fallback.jpg" srcset="foo.jpg 100w, bar.jpg 200w" />'
     );
@@ -735,8 +734,8 @@ describe('sanitizeHtml', function() {
     const got = sanitizeHtml(
       input,
       {
-        allowedTags: ['u'],
-        allowedAttributes: { '*': ['class'] },
+        allowedTags: [ 'u' ],
+        allowedAttributes: { '*': [ 'class' ] },
         transformTags: {
           input: function (tagName, attribs) {
             return {
@@ -752,8 +751,8 @@ describe('sanitizeHtml', function() {
   it('drop attribute names with meta-characters', function() {
     assert.equal(
       sanitizeHtml('<span data-<script>alert(1)//>', {
-        allowedTags: ['span'],
-        allowedAttributes: { span: ['data-*'] }
+        allowedTags: [ 'span' ],
+        allowedAttributes: { span: [ 'data-*' ] }
       }),
       '<span>alert(1)//&gt;</span>'
     );
@@ -765,17 +764,17 @@ describe('sanitizeHtml', function() {
       sanitizeHtml(sanitizeString, {
         allowedTags: false,
         allowedAttributes: {
-          '*': ['dir'],
-          p: ['dir', 'style'],
-          li: ['style'],
-          span: ['style']
+          '*': [ 'dir' ],
+          p: [ 'dir', 'style' ],
+          li: [ 'style' ],
+          span: [ 'style' ]
         },
         allowedStyles: {
           '*': {
             // Matches hex
-            color: [/#(0x)?[0-9a-f]+/i],
-            'text-align': [/left/, /right/, /center/, /justify/, /initial/, /inherit/],
-            'font-size': [/36px/]
+            color: [ /#(0x)?[0-9a-f]+/i ],
+            'text-align': [ /left/, /right/, /center/, /justify/, /initial/, /inherit/ ],
+            'font-size': [ /36px/ ]
           }
         }
       }).replace(/ /g, ''), expected.replace(/ /g, '')
@@ -795,12 +794,12 @@ describe('sanitizeHtml', function() {
       sanitizeHtml('<span style=\'color: blue; text-align: justify\'></span>', {
         allowedTags: false,
         allowedAttributes: {
-          span: ['style']
+          span: [ 'style' ]
         },
         allowedStyles: {
           span: {
-            color: [/blue/],
-            'text-align': [/left/]
+            color: [ /blue/ ],
+            'text-align': [ /left/ ]
           }
         }
       }), '<span style="color:blue"></span>'
@@ -811,16 +810,16 @@ describe('sanitizeHtml', function() {
       sanitizeHtml('<span style=\'color: yellow; text-align: center; font-family: helvetica\'></span>', {
         allowedTags: false,
         allowedAttributes: {
-          span: ['style']
+          span: [ 'style' ]
         },
         allowedStyles: {
           '*': {
-            color: [/yellow/],
-            'text-align': [/center/]
+            color: [ /yellow/ ],
+            'text-align': [ /center/ ]
           },
           span: {
-            color: [/green/],
-            'font-family': [/helvetica/]
+            color: [ /green/ ],
+            'font-family': [ /helvetica/ ]
           }
         }
       }), '<span style="color:yellow;text-align:center;font-family:helvetica"></span>'
@@ -829,50 +828,50 @@ describe('sanitizeHtml', function() {
   it('Should allow hostnames in an iframe that are whitelisted', function() {
     assert.equal(
       sanitizeHtml('<iframe src="https://www.youtube.com/embed/c2IlcS7AHxM"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
-        allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com']
+        allowedIframeHostnames: [ 'www.youtube.com', 'player.vimeo.com' ]
       }), '<iframe src="https://www.youtube.com/embed/c2IlcS7AHxM"></iframe>'
     );
   });
   it('Should remove iframe src urls that are not included in whitelisted hostnames', function() {
     assert.equal(
       sanitizeHtml('<iframe src="https://www.embed.vevo.com/USUV71704255"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
-        allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com']
+        allowedIframeHostnames: [ 'www.youtube.com', 'player.vimeo.com' ]
       }), '<iframe></iframe>'
     );
   });
   it('Should not allow iframe urls that do not have proper hostname', function() {
     assert.equal(
       sanitizeHtml('<iframe src="https://www.vimeo.com/embed/c2IlcS7AHxM"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
-        allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com']
+        allowedIframeHostnames: [ 'www.youtube.com', 'player.vimeo.com' ]
       }), '<iframe></iframe>'
     );
   });
   it('Should allow iframe through if no hostname option is set', function() {
     assert.equal(
       sanitizeHtml('<iframe src="https://www.vimeo.com/embed/c2IlcS7AHxM"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         }
       }), '<iframe src="https://www.vimeo.com/embed/c2IlcS7AHxM"></iframe>'
     );
@@ -880,39 +879,39 @@ describe('sanitizeHtml', function() {
   it('Should allow domains in an iframe that are whitelisted', function() {
     assert.equal(
       sanitizeHtml('<iframe src="https://www.foo.us02web.zoom.us/embed/c2IlcS7AHxM"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
-        allowedIframeDomains: ['zoom.us']
+        allowedIframeDomains: [ 'zoom.us' ]
       }), '<iframe src="https://www.foo.us02web.zoom.us/embed/c2IlcS7AHxM"></iframe>'
     );
   });
   it('Should allow second-level domains in an iframe that are whitelisted', function() {
     assert.equal(
       sanitizeHtml('<iframe src="https://zoom.us/embed/c2IlcS7AHxM"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
-        allowedIframeDomains: ['zoom.us']
+        allowedIframeDomains: [ 'zoom.us' ]
       }), '<iframe src="https://zoom.us/embed/c2IlcS7AHxM"></iframe>'
     );
   });
   it('Should remove iframe src urls that are not included in whitelisted domains', function() {
     assert.equal(
       sanitizeHtml('<iframe src="https://www.prefix.us02web.zoom.us/embed/c2IlcS7AHxM"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
-        allowedIframeDomains: ['vimeo.com']
+        allowedIframeDomains: [ 'vimeo.com' ]
       }), '<iframe></iframe>'
     );
   });
@@ -920,13 +919,13 @@ describe('sanitizeHtml', function() {
      ' but not preceeded with a dot', function() {
     assert.equal(
       sanitizeHtml('<iframe src="https://www.zoomzoom.us/embed/c2IlcS7AHxM"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
-        allowedIframeDomains: ['zoom.us']
+        allowedIframeDomains: [ 'zoom.us' ]
       }), '<iframe></iframe>'
     );
   });
@@ -934,14 +933,14 @@ describe('sanitizeHtml', function() {
      'and are not whitelisted in allowedIframeDomains', function() {
     assert.equal(
       sanitizeHtml('<iframe src="https://www.youtube.com/embed/c2IlcS7AHxM"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
-        allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com'],
-        allowedIframeDomains: ['zoom.us']
+        allowedIframeHostnames: [ 'www.youtube.com', 'player.vimeo.com' ],
+        allowedIframeDomains: [ 'zoom.us' ]
       }), '<iframe src="https://www.youtube.com/embed/c2IlcS7AHxM"></iframe>'
     );
   });
@@ -949,25 +948,25 @@ describe('sanitizeHtml', function() {
      'and are whitelisted in allowedIframeDomains', function() {
     assert.equal(
       sanitizeHtml('<iframe src="https://www.us02web.zoom.us/embed/c2IlcS7AHxM"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
-        allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com'],
-        allowedIframeDomains: ['zoom.us']
+        allowedIframeHostnames: [ 'www.youtube.com', 'player.vimeo.com' ],
+        allowedIframeDomains: [ 'zoom.us' ]
       }), '<iframe src="https://www.us02web.zoom.us/embed/c2IlcS7AHxM"></iframe>'
     );
   });
   it('Should allow relative URLs for iframes by default', function() {
     assert.equal(
       sanitizeHtml('<iframe src="/foo"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         }
       }), '<iframe src="/foo"></iframe>'
     );
@@ -975,11 +974,11 @@ describe('sanitizeHtml', function() {
   it('Should allow relative URLs for iframes', function() {
     assert.equal(
       sanitizeHtml('<iframe src="/foo"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
         allowIframeRelativeUrls: true
       }), '<iframe src="/foo"></iframe>'
@@ -988,11 +987,11 @@ describe('sanitizeHtml', function() {
   it('Should remove relative URLs for iframes', function() {
     assert.equal(
       sanitizeHtml('<iframe src="/foo"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
         allowIframeRelativeUrls: false
       }), '<iframe></iframe>'
@@ -1001,66 +1000,66 @@ describe('sanitizeHtml', function() {
   it('Should remove relative URLs for iframes when whitelisted hostnames specified', function() {
     assert.equal(
       sanitizeHtml('<iframe src="/foo"></iframe><iframe src="https://www.youtube.com/embed/c2IlcS7AHxM"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
-        allowedIframeHostnames: ['www.youtube.com']
+        allowedIframeHostnames: [ 'www.youtube.com' ]
       }), '<iframe></iframe><iframe src="https://www.youtube.com/embed/c2IlcS7AHxM"></iframe>'
     );
   });
   it('Should allow relative and whitelisted hostname URLs for iframes', function() {
     assert.equal(
       sanitizeHtml('<iframe src="/foo"></iframe><iframe src="https://www.youtube.com/embed/c2IlcS7AHxM"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
         allowIframeRelativeUrls: true,
-        allowedIframeHostnames: ['www.youtube.com']
+        allowedIframeHostnames: [ 'www.youtube.com' ]
       }), '<iframe src="/foo"></iframe><iframe src="https://www.youtube.com/embed/c2IlcS7AHxM"></iframe>'
     );
   });
   it('Should allow protocol-relative URLs for the right domain for iframes', function() {
     assert.equal(
       sanitizeHtml('<iframe src="//www.youtube.com/embed/c2IlcS7AHxM"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
-        allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com']
+        allowedIframeHostnames: [ 'www.youtube.com', 'player.vimeo.com' ]
       }), '<iframe src="//www.youtube.com/embed/c2IlcS7AHxM"></iframe>'
     );
   });
   it('Should not allow protocol-relative iframe urls that do not have proper hostname', function() {
     assert.equal(
       sanitizeHtml('<iframe src="//www.vimeo.com/embed/c2IlcS7AHxM"></iframe>', {
-        allowedTags: ['p', 'iframe', 'a', 'img', 'i'],
+        allowedTags: [ 'p', 'iframe', 'a', 'img', 'i' ],
         allowedAttributes: {
-          iframe: ['src', 'href'],
-          a: ['src', 'href'],
-          img: ['src']
+          iframe: [ 'src', 'href' ],
+          a: [ 'src', 'href' ],
+          img: [ 'src' ]
         },
-        allowedIframeHostnames: ['www.youtube.com', 'player.vimeo.com']
+        allowedIframeHostnames: [ 'www.youtube.com', 'player.vimeo.com' ]
       }), '<iframe></iframe>'
     );
   });
   it('Should only allow attributes to have any combination of specific values', function() {
     assert.equal(
       sanitizeHtml('<iframe name="IFRAME" allowfullscreen="true" sandbox="allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts allow-top-navigation"></iframe>', {
-        allowedTags: sanitizeHtml.defaults.allowedTags.concat(['iframe']),
+        allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'iframe' ]),
         allowedAttributes: {
           iframe: [
             {
               name: 'sandbox',
               multiple: true,
-              values: ['allow-popups', 'allow-same-origin', 'allow-scripts']
+              values: [ 'allow-popups', 'allow-same-origin', 'allow-scripts' ]
             },
             'allowfullscreen'
           ]
@@ -1070,13 +1069,13 @@ describe('sanitizeHtml', function() {
   it('Should only allow attributes that match a specific value', function() {
     assert.equal(
       sanitizeHtml('<iframe sandbox="allow-popups allow-modals"></iframe><iframe sandbox="allow-popups"></iframe><iframe sandbox="allow-scripts"></iframe>', {
-        allowedTags: sanitizeHtml.defaults.allowedTags.concat(['iframe']),
+        allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'iframe' ]),
         allowedAttributes: {
           iframe: [
             {
               name: 'sandbox',
               multiple: false,
-              values: ['allow-popups', 'allow-same-origin', 'allow-scripts']
+              values: [ 'allow-popups', 'allow-same-origin', 'allow-scripts' ]
             }
           ]
         }
@@ -1086,21 +1085,21 @@ describe('sanitizeHtml', function() {
   it('Should not allow cite urls that do not have an allowed scheme', function() {
     assert.equal(
       sanitizeHtml('<q cite="http://www.google.com">HTTP</q><q cite="https://www.google.com">HTTPS</q><q cite="mailto://www.google.com">MAILTO</q><q cite="tel://www.google.com">TEL</q><q cite="ftp://www.google.com">FTP</q><q cite="data://www.google.com">DATA</q><q cite="ldap://www.google.com">LDAP</q><q cite="acrobat://www.google.com">ACROBAT</q><q cite="vbscript://www.google.com">VBSCRIPT</q><q cite="file://www.google.com">FILE</q><q cite="rlogin://www.google.com">RLOGIN</q><q cite="webcal://www.google.com">WEBCAL</q><q cite="javascript://www.google.com">JAVASCRIPT</q><q cite="mms://www.google.com">MMS</q>', {
-        allowedTags: sanitizeHtml.defaults.allowedTags.concat(['q']),
-        allowedAttributes: { q: ['cite'] },
-        allowedSchemes: sanitizeHtml.defaults.allowedSchemes.concat(['tel'])
+        allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 'q' ]),
+        allowedAttributes: { q: [ 'cite' ] },
+        allowedSchemes: sanitizeHtml.defaults.allowedSchemes.concat([ 'tel' ])
       }), '<q cite="http://www.google.com">HTTP</q><q cite="https://www.google.com">HTTPS</q><q cite="mailto://www.google.com">MAILTO</q><q cite="tel://www.google.com">TEL</q><q cite="ftp://www.google.com">FTP</q><q>DATA</q><q>LDAP</q><q>ACROBAT</q><q>VBSCRIPT</q><q>FILE</q><q>RLOGIN</q><q>WEBCAL</q><q>JAVASCRIPT</q><q>MMS</q>');
   });
   it('Should encode &, <, > and where necessary, "', function() {
     assert.equal(sanitizeHtml('"< & >" <span class="&#34;test&#34;">cool</span>', {
-      allowedTags: ['span'],
+      allowedTags: [ 'span' ],
       allowedAttributes: {
-        span: ['class']
+        span: [ 'class' ]
       }
     }), '"&lt; &amp; &gt;" <span class="&quot;test&quot;">cool</span>');
   });
   it('Should not pass through &0; unescaped if decodeEntities is true (the default)', function() {
-    assert.equal(sanitizeHtml('<img src="<0&0;0.2&" />', { allowedTags: ['img'] }), '<img src="&lt;0&amp;0;0.2&amp;" />');
+    assert.equal(sanitizeHtml('<img src="<0&0;0.2&" />', { allowedTags: [ 'img' ] }), '<img src="&lt;0&amp;0;0.2&amp;" />');
   });
   it('Should not double encode ampersands on HTML entities if decodeEntities is false (TODO more tests, this is too loose to rely upon)', function() {
     const textIn = 'This &amp; & that &reg; &#x0000A; &#10; &plusmn; OK?';
@@ -1158,4 +1157,18 @@ describe('sanitizeHtml', function() {
       '<div>&lt;wiggly&gt;Hello<p>World</p>&lt;tiggly&gt;JS&lt;/tiggly&gt;&lt;/wiggly&gt;</div>'
     );
   });
+  it('allows markup of depth 6 with a nestingLimit of depth 6', function() {
+    assert.equal(
+      sanitizeHtml('<div><div><div><div><div><div></div></div></div></div></div></div>', { nestingLimit: 6 }),
+      '<div><div><div><div><div><div></div></div></div></div></div></div>'
+    );
+  });
+  it('disallows markup of depth 7 with a nestingLimit of depth 6', function() {
+    assert.equal(
+      // 7 divs here
+      sanitizeHtml('<div><div><div><div><div><div><div>nested text</div></div></div></div></div></div></div>', { nestingLimit: 6 }),
+      // only 6 kept
+      '<div><div><div><div><div><div>nested text</div></div></div></div></div></div>'
+    );
+  });
 });