Skip to content

Commit

Permalink
Merge pull request #2 from JackuB/JackuB/replace-cloneNode
Browse files Browse the repository at this point in the history
Replace clone node and introduce tests
  • Loading branch information
JackuB authored Feb 9, 2019
2 parents e8f5398 + 641ef14 commit 966d31f
Show file tree
Hide file tree
Showing 20 changed files with 4,569 additions and 22 deletions.
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
; EditorConfig file: http://EditorConfig.org
; Install the "EditorConfig" plugin into your editor to use

root = true

[*]
charset = utf-8
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ jspm_packages/

# dotenv environment variables file
.env
cypress/videos
cypress/plugins
cypress/support
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ index.html
markdown.css
prism.js
sri-issue-chrome.jpg
cypress
docs
6 changes: 4 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ cache:
notifications:
email: false
node_js:
- '8'
- '10'
before_script:
- npm prune
- npm run build
script:
- npm run serve &
- npm test
after_success:
- npm run semantic-release
branches:
Expand Down
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ If we don't want to leave user with an application in nonfunctioning state or at

Script is downloaded, but before its execution, SRI hash is checked against the downloaded resource. If hash check fails, [error event is fired][5] and resource is not executed. This error is not propagated to the `window.onerror` and _afaik_ can't be distinguished from other errors. So we need to catch all of them.

To catch `script/link` errors, we can use [`onerror][6]` handler. But we need to hook it right as the resource is added to the DOM. For that we can use [MutationObserver][7].
To catch `script/link` errors, we can use [`onerror`][6] handler. But we need to hook it right as the resource is added to the DOM. For that we can use [MutationObserver][7].

### Fallback flow

Expand All @@ -33,15 +33,15 @@ Because we are trying to deal with unreliable CDN/network, **all of the followin

### 1) Add [SRI fallback code][8] to the header

Code must be placed before any resource using `integrity` attribute - ideally in `<head>`, since CSS `link` can also utilize integrity check. Its minified version _(~0,4kb gzipped)_ is also on [`npm][9]`:
Code must be placed before any resource using `integrity` attribute - ideally in `<head>`, since CSS `link` can also utilize integrity check. Its minified version _(~0,4kb gzipped)_ is also on [`npm`][9]:

**`npm i subresource-integrity-fallback -S`**

### 2) Add resource fallback data attributes

Then you need to supply `data-sri-fallback` attribute on any resource with `integrity` check. Example use:
```html

```html
<script
src="https://cdn.example.com/app.js"
data-sri-fallback="https://example.com/app.js"
Expand All @@ -54,9 +54,9 @@ It might be a good idea to either serve fallback resources from same server that
### 3) Define a behavior on failure

Last missing piece is the `window.resourceLoadError` function, that will be called when resource with `integrity` check fails to load - as explained above, this reason can come either from normal network problems or resource tampering. Function will get error itself as an argument and boolean indicating whether fallback resource also failed. Function will be called for each failing resource.

```js
// In the
// In the
window.resourceLoadError = function(err, isRetry) {
if (isRetry) {
return letUserKnowAboutPossibleTampering(err);
Expand Down Expand Up @@ -84,4 +84,8 @@ window.resourceLoadError = function(err, isRetry) {
[10]: https://www.w3.org/TR/SRI/#proxies
[11]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP


## Contributing/developing

- Install dependencies: `npm install`
- Build the `/dist` and serve the static files: `npm run serve`
- Test/play with Cypress: `npm run cypress:open`
3 changes: 3 additions & 0 deletions cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"baseUrl": "http://localhost:8080/cypress/fixtures"
}
1 change: 1 addition & 0 deletions cypress/fixtures/compromised.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
document.body.innerText = 'Compromised script loaded';
1 change: 1 addition & 0 deletions cypress/fixtures/fallback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
document.body.innerText = 'Fallback script loaded';
1 change: 1 addition & 0 deletions cypress/fixtures/original.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
document.body.innerText = 'First script loaded';
14 changes: 14 additions & 0 deletions cypress/fixtures/scenario-1.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>SRI test page</title>
<script src="../../dist/sri.min.js"></script>
</head>
<body>
Hello world
</body>
<script src="original.js"></script>
</html>
25 changes: 25 additions & 0 deletions cypress/fixtures/scenario-2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>SRI test page</title>
<script>
window.resourceLoadError = function(err, isRetry) {
if (isRetry) {
return document.body.innerText = 'resourceLoadError - Loading original and fallback failed';
}
return document.body.innerText = 'resourceLoadError - Loading original failed';
}
</script>
<script src="../../dist/sri.min.js"></script>
</head>
<body>
Hello world
</body>
<script
integrity="sha384-OrJIrkeihvBqc462zAEWV6zORyz5ndLEcrgV7E9AGgfWHIQmDY3LOuW6gNdp0BnT"
data-sri-fallback="fallback.js"
src="original.js"></script>
</html>
25 changes: 25 additions & 0 deletions cypress/fixtures/scenario-3.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>SRI test page</title>
<script>
window.resourceLoadError = function(err, isRetry) {
if (isRetry) {
return document.body.innerText = 'resourceLoadError - Loading original and fallback failed';
}
return document.body.innerText = 'resourceLoadError - Loading original failed';
}
</script>
<script src="../../dist/sri.min.js"></script>
</head>
<body>
Hello world
</body>
<script
integrity="sha384-P1xTGaIgVabFWRf0XeD0lA31UGgVPqAkqplIiwzgGvJrEGwODrAihG41Uq+K5XE3"
data-sri-fallback="fallback.js"
src="original.js"></script>
</html>
25 changes: 25 additions & 0 deletions cypress/fixtures/scenario-4.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>SRI test page</title>
<script>
window.resourceLoadError = function(err, isRetry) {
if (isRetry) {
return document.body.innerText = 'resourceLoadError - Loading original and fallback failed';
}
return document.body.innerText = 'resourceLoadError - Loading original failed';
}
</script>
<script src="../../dist/sri.min.js"></script>
</head>
<body>
Hello world
</body>
<script
integrity="sha384-NOPErJIrkeihvBqc462zAEWV6zORyz5ndLEcrgV7E9AGgfWHIQmDY3LOuW6gNdp0BnT"
data-sri-fallback="fallback.js"
src="original.js"></script>
</html>
24 changes: 24 additions & 0 deletions cypress/fixtures/scenario-5.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>SRI test page</title>
<script>
window.resourceLoadError = function(err, isRetry) {
if (isRetry) {
return document.body.innerText = 'resourceLoadError - Loading original and fallback failed';
}
return document.body.innerText = 'resourceLoadError - Loading original failed';
}
</script>
<script src="../../dist/sri.min.js"></script>
</head>
<body>
Hello world
</body>
<script
integrity="sha384-P1xTGaIgVabFWRf0XeD0lA31UGgVPqAkqplIiwzgGvJrEGwODrAihG41Uq+K5XE3"
src="compromised.js"></script>
</html>
27 changes: 27 additions & 0 deletions cypress/fixtures/scenario-6.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>SRI test page</title>
<script>
window.resourceLoadError = function(err, isRetry) {
if (isRetry) {
return document.body.innerText = 'resourceLoadError - Loading original and fallback failed';
}
return document.body.innerText = 'resourceLoadError - Loading original failed';
}
</script>
<script src="../../dist/sri.min.js"></script>
</head>
<body>
Hello world
</body>
<script
integrity="sha384-OrJIrkeihvBqc462zAEWV6zORyz5ndLEcrgV7E9AGgfWHIQmDY3LOuW6gNdp0BnT"
src="original.js"></script>
<script
integrity="sha384-P1xTGaIgVabFWRf0XeD0lA31UGgVPqAkqplIiwzgGvJrEGwODrAihG41Uq+K5XE3"
src="compromised.js"></script>
</html>
73 changes: 73 additions & 0 deletions cypress/integration/sri_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
Scenarios
- fail SRI on first script and load fallback without SRI
*/

describe('Plain JS', function() {
it('successfully loads original script', function() {
cy.visit('scenario-1.html');
cy.contains('body', 'First script loaded');
});
});

describe('JS with valid SRI', function() {
it('successfully loads original script', function() {
cy.visit('scenario-2.html');
cy.contains('body', 'First script loaded');
});
});

describe('JS with invalid SRI, but valid fallback', function() {
it('fails to load original script', function() {
cy.visit('scenario-3.html');
cy.get('body').should('not.contain', 'First script loaded');
});

it('loads the fallback script', function() {
cy.contains('body', 'Fallback script loaded');
});

it('does not execute the resourceLoadError callback', function() {
cy.get('body').should('not.contain', 'resourceLoadError');
});
});

describe('JS with invalid SRI and fallback (invalid hash)', function() {
it('fails to load original script', function() {
cy.visit('scenario-4.html');
cy.get('body').should('not.contain', 'First script loaded');
});

it('fails to load the fallback script', function() {
cy.get('body').should('not.contain', 'Fallback script loaded');
});

it('executes the resourceLoadError callback', function() {
cy.contains('body', 'resourceLoadError');
cy.contains('body', 'Loading original and fallback failed');
});
});

describe('JS with invalid SRI (compromised file)', function() {
it('fails to load original script', function() {
cy.visit('scenario-5.html');
cy.get('body').should('not.contain', 'First script loaded');
});

it('executes the resourceLoadError callback', function() {
cy.contains('body', 'resourceLoadError');
cy.contains('body', 'Loading original failed');
});
});

describe('Multiple JS files with SRI, one is compromised', function() {
it('fails to load original script', function() {
cy.visit('scenario-5.html');
cy.get('body').should('not.contain', 'First script loaded');
});

it('executes the resourceLoadError callback', function() {
cy.contains('body', 'resourceLoadError');
cy.contains('body', 'Loading original failed');
});
});
2 changes: 1 addition & 1 deletion dist/sri.min.js

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

Loading

0 comments on commit 966d31f

Please sign in to comment.