Skip to content

Commit

Permalink
Tweak Sanitizer and add scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
bennothommo committed Aug 9, 2023
1 parent e052a83 commit 8e10675
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 12 deletions.
38 changes: 26 additions & 12 deletions src/utilities/Sanitizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,37 @@ import Singleton from '../abstracts/Singleton';
* as attributes, i.e. `onload` or `onerror`) or contain the `javascript:` pseudo protocol in their
* values.
*
* This is only a simple sanitizer and does not attempt to be exhaustive. If you're truly paranoid
* about the input, you might want to consider a more robust sanitizer like
* [DOMPurify](https://github.com/cure53/DOMPurify).
*
* @author Ben Thomson <[email protected]>
*/
export default class Sanitizer extends Singleton {
construct() {
// Add to global function for backwards compatibility
window.wnSanitize = (html) => this.sanitize(html);
window.ocSanitize = window.wnSanitize;
}

sanitize(html, bodyOnly) {
/**
* Sanitizes a HTML string.
*
* @param {string} html
* @param {boolean} bodyOnly
* @returns {string}
*/
sanitize(html, bodyOnly = true) {
const parser = new DOMParser();
const dom = parser.parseFromString(html, 'text/html');
const returnBodyOnly = (bodyOnly !== undefined && typeof bodyOnly === 'boolean')
? bodyOnly
: true;

this.sanitizeNode(dom.getRootNode());

return (returnBodyOnly) ? dom.body.innerHTML : dom.innerHTML;
return (bodyOnly) ? dom.body.innerHTML : dom.documentElement.outerHTML;
}

/**
* Sanitizes an individual node.
*
* @param {Node} node
* @returns {void}
*/
sanitizeNode(node) {
if (node.tagName === 'SCRIPT') {
if (['SCRIPT', 'IFRAME', 'OBJECT'].includes(node.tagName)) {
node.remove();
return;
}
Expand All @@ -44,6 +52,12 @@ export default class Sanitizer extends Singleton {
});
}

/**
* Sanitizes the attributes of a node.
*
* @param {Node} node
* @returns {void}
*/
trimAttributes(node) {
if (!node.attributes) {
return;
Expand Down
50 changes: 50 additions & 0 deletions tests/utilities/Sanitizer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import TestInstance from '../../src/main/Snowboard';

describe('Sanitizer utility', () => {
beforeEach(() => {
document.currentScript.dataset.baseUrl = 'https://example.com';
document.currentScript.dataset.assetUrl = 'https://example.com/fixtures/assets/';

window.Snowboard = new TestInstance();
});

it('sanitizes some common XSS vectors', () => {
const html = `<div onclick="javascript:alert(1)">
<script src="/myscript.js"></script>
<script>
document.body.innerHTML = 'Hacked!';
</script>
<p>We're cool. <iframe src="blah.html">But we're not.</iframe></p>
<a href="javascript:alert(2)"></a>
<img src="myimage.png" onload="javascript:alert(3)">
</div>`;

const output = Snowboard.sanitizer().sanitize(html).replace(/\s+/g, ' ');

expect(output).toEqual('<div> <p>We\'re cool. </p> <a></a> <img src="myimage.png"> </div>');
});

it('can return the full html', () => {
const html = `<html>
<head>
<title>Hi</title>
<script src="/myscript.js"></script>
</head>
<body>
<div onclick="javascript:alert(1)">
<script src="/myscript.js"></script>
<script>
document.body.innerHTML = 'Hacked!';
</script>
<p>We're cool. <iframe src="blah.html">But we're not.</iframe></p>
<a href="javascript:alert(2)"></a>
<img src="myimage.png" onload="javascript:alert(3)">
</div>
</body>
</html>`;

const output = Snowboard.sanitizer().sanitize(html, false).replace(/\s+/g, ' ');

expect(output).toEqual('<html><head> <title>Hi</title> </head> <body> <div> <p>We\'re cool. </p> <a></a> <img src=\"myimage.png\"> </div> </body></html>');
});
});

0 comments on commit 8e10675

Please sign in to comment.