Skip to content

Commit

Permalink
Prevent creating script tags from embedding stylesheets (#156)
Browse files Browse the repository at this point in the history
* Prevent security concern from preload media option

* Update yarn.lock

* Validates CSS path before loading the file

* Prevent creating script tags from embedding stylesheets

* Remove launch.json

* Add test case

* Fix review comments
  • Loading branch information
janicklas-ralph committed Feb 23, 2024
1 parent 2e8cbe8 commit 6a75baf
Show file tree
Hide file tree
Showing 5 changed files with 509 additions and 260 deletions.
95 changes: 95 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@
"node_modules",
"dist"
],
"transformIgnorePatterns": [
]
"transformIgnorePatterns": []
},
"prettier": {
"singleQuote": true,
Expand Down Expand Up @@ -82,6 +81,7 @@
"@types/jest": "^26.0.23",
"babel-core": "^6.26.0",
"babel-jest": "^26.3.0",
"cheerio": "^1.0.0-rc.12",
"eslint": "^7.6.0",
"eslint-config-prettier": "^6.11.0",
"eslint-config-standard": "^14.1.1",
Expand Down
4 changes: 4 additions & 0 deletions packages/critters/src/css.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export function serializeStylesheet(ast, options) {
let cssStr = '';

stringify(ast, (result, node, type) => {
if (node?.type === 'decl' && node.value.includes('</style>')) {
return;
}

if (!options.compress) {
cssStr += result;
return;
Expand Down
69 changes: 69 additions & 0 deletions packages/critters/test/security.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import Critters from '../src/index';
import * as cheerio from 'cheerio';

function hasEvilOnload(html) {
const $ = cheerio.load(html, { scriptingEnabled: true });
return $('[onload]').attr('onload').includes(`''-alert(1)-''`);
}

function hasEvilScript(html) {
const $ = cheerio.load(html, { scriptingEnabled: true });
const scripts = Array.from($('script'));
return scripts.some((s) => s.textContent.trim() === 'alert(1)');
}

describe('Critters', () => {
it('should not decode entities', async () => {
const critters = new Critters({});
const html = await critters.process(`
<html>
<body>
&lt;script&gt;alert(1)&lt;/script&gt;
`);
expect(hasEvilScript(html)).toBeFalsy();
});
it('should not create a new script tag from embedding linked stylesheets', async () => {
const critters = new Critters({});
critters.readFile = () =>
`* { background: url('</style><script>alert(1)</script>') }`;
const html = await critters.process(`
<html>
<head>
<link rel=stylesheet href=/file.css>
</head>
<body>
</body>
`);
expect(hasEvilScript(html)).toBeFalsy();
});
it('should not create a new script tag from embedding additional stylesheets', async () => {
const critters = new Critters({
additionalStylesheets: ['/style.css']
});
critters.readFile = () =>
`* { background: url('</style><script>alert(1)</script>') }`;
const html = await critters.process(`
<html>
<head>
</head>
<body>
</body>
`);
expect(hasEvilScript(html)).toBeFalsy();
});

it('should not create a new script tag by ending </script> from href', async () => {
const critters = new Critters({ preload: 'js' });
critters.readFile = () => `* { background: red }`;
const html = await critters.process(`
<html>
<head>
<link rel=stylesheet href="/abc/</script><script>alert(1)</script>/style.css">
</head>
<body>
</body>
`);
expect(hasEvilScript(html)).toBeFalsy();
});
});

0 comments on commit 6a75baf

Please sign in to comment.