@localnerve/csp-hashes 0.1.3 → 0.1.4
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.
- package/package.json +19 -9
- package/readme.md +10 -5
- package/.eslintignore +0 -5
- package/.eslintrc.json +0 -22
- package/.github/workflows/verify.yml +0 -32
- package/.vscode/launch.json +0 -14
- package/__tests__/fixtures/multiple-scripts-attr.html +0 -17
- package/__tests__/fixtures/multiple-scripts-attr.sha256 +0 -7
- package/__tests__/fixtures/multiple-scripts-attr.sha384 +0 -7
- package/__tests__/fixtures/multiple-scripts-attr.sha512 +0 -7
- package/__tests__/fixtures/multiple-scripts-styles-script.sha256 +0 -7
- package/__tests__/fixtures/multiple-scripts-styles-script.sha384 +0 -7
- package/__tests__/fixtures/multiple-scripts-styles-script.sha512 +0 -7
- package/__tests__/fixtures/multiple-scripts-styles-style.sha256 +0 -6
- package/__tests__/fixtures/multiple-scripts-styles-style.sha384 +0 -6
- package/__tests__/fixtures/multiple-scripts-styles-style.sha512 +0 -6
- package/__tests__/fixtures/multiple-scripts-styles.html +0 -20
- package/__tests__/fixtures/multiple-scripts.html +0 -13
- package/__tests__/fixtures/multiple-scripts.sha256 +0 -5
- package/__tests__/fixtures/multiple-scripts.sha384 +0 -5
- package/__tests__/fixtures/multiple-scripts.sha512 +0 -5
- package/__tests__/fixtures/multiple-style-attr.html +0 -12
- package/__tests__/fixtures/multiple-style-attr.sha256 +0 -6
- package/__tests__/fixtures/multiple-style-attr.sha384 +0 -6
- package/__tests__/fixtures/multiple-style-attr.sha512 +0 -6
- package/__tests__/fixtures/multiple-style.html +0 -9
- package/__tests__/fixtures/multiple-style.sha256 +0 -5
- package/__tests__/fixtures/multiple-style.sha384 +0 -5
- package/__tests__/fixtures/multiple-style.sha512 +0 -5
- package/__tests__/fixtures/script-src.html +0 -7
- package/__tests__/fixtures/script-src.sha256 +0 -0
- package/__tests__/fixtures/script-src.sha384 +0 -0
- package/__tests__/fixtures/script-src.sha512 +0 -0
- package/__tests__/fixtures/single-script.html +0 -7
- package/__tests__/fixtures/single-script.sha256 +0 -4
- package/__tests__/fixtures/single-script.sha384 +0 -4
- package/__tests__/fixtures/single-script.sha512 +0 -4
- package/__tests__/fixtures/single-style.html +0 -8
- package/__tests__/fixtures/single-style.sha256 +0 -4
- package/__tests__/fixtures/single-style.sha384 +0 -4
- package/__tests__/fixtures/single-style.sha512 +0 -4
- package/__tests__/index.test.js +0 -195
- package/babel.config.js +0 -11
- package/index.js +0 -11
- package/jest.config.js +0 -10
- package/lib/index.js +0 -113
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@localnerve/csp-hashes",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Flexible library to
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Flexible library to generate CSP hashes",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"lint": "eslint .",
|
|
@@ -12,17 +12,17 @@
|
|
|
12
12
|
"test:debug": "node --inspect-brk ./node_modules/.bin/jest"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@babel/cli": "^7.
|
|
16
|
-
"@babel/preset-env": "^7.
|
|
17
|
-
"@babel/register": "^7.
|
|
15
|
+
"@babel/cli": "^7.18.9",
|
|
16
|
+
"@babel/preset-env": "^7.18.9",
|
|
17
|
+
"@babel/register": "^7.18.9",
|
|
18
18
|
"coveralls": "^3.1.1",
|
|
19
|
-
"eslint": "^8.
|
|
20
|
-
"jest": "^
|
|
19
|
+
"eslint": "^8.20.0",
|
|
20
|
+
"jest": "^28.1.3",
|
|
21
21
|
"rimraf": "^3.0.2",
|
|
22
22
|
"vinyl": "^2.2.1"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"cheerio": "^1.0.0-rc.
|
|
25
|
+
"cheerio": "^1.0.0-rc.12",
|
|
26
26
|
"through2": "^4.0.2"
|
|
27
27
|
},
|
|
28
28
|
"repository": {
|
|
@@ -34,7 +34,17 @@
|
|
|
34
34
|
"hashes",
|
|
35
35
|
"gulp"
|
|
36
36
|
],
|
|
37
|
-
"author":
|
|
37
|
+
"author": {
|
|
38
|
+
"name": "Alex Grant",
|
|
39
|
+
"email": "alex@localnerve.com",
|
|
40
|
+
"url": "https://localnerve.com"
|
|
41
|
+
},
|
|
42
|
+
"contributors": [
|
|
43
|
+
{
|
|
44
|
+
"name": "Alex Grant",
|
|
45
|
+
"email": "alex@localnerve.com"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
38
48
|
"license": "MIT",
|
|
39
49
|
"bugs": {
|
|
40
50
|
"url": "https://github.com/localnerve/csp-hashes/issues"
|
package/readme.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Flexible build library to generate script and style hashes for CSP headers or meta tags
|
|
4
4
|
|
|
5
|
-
[](https://badge.fury.io/js/@localnerve%2Fcsp-hashes)
|
|
6
6
|

|
|
7
7
|
[](https://coveralls.io/github/localnerve/csp-hashes?branch=main)
|
|
8
8
|
|
|
@@ -18,10 +18,13 @@
|
|
|
18
18
|
+ [MIT License](#license)
|
|
19
19
|
|
|
20
20
|
## Overview
|
|
21
|
-
This library generates script and style inline element and attribute hashes. It is for use in the generation of HTTP content security policy (CSP) headers or to replace/update Meta tags as a website build step.
|
|
21
|
+
This Nodejs library generates script and style inline element and attribute hashes. It is for use in the generation of HTTP content security policy (CSP) headers or to replace/update Meta tags as a website build step. Ready for use with [Gulp](https://github.com/gulpjs/gulp).
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
+ NodeJS 14+
|
|
22
25
|
|
|
23
26
|
## API
|
|
24
|
-
This library exports a single function that takes options and returns a transform stream in object mode
|
|
27
|
+
This library exports a single function that takes options and returns a transform stream in object mode. The transform stream operates on [Vinyl](https://github.com/gulpjs/vinyl) objects or a compatible file object with `path` and `contents` properties. The only required option is a [`callback`](#callback-function) function.
|
|
25
28
|
|
|
26
29
|
```
|
|
27
30
|
Stream hashstream ({
|
|
@@ -71,6 +74,7 @@ The callback hashes object contains all of the inline element and attribute hash
|
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
```
|
|
77
|
+
|
|
74
78
|
The object structure allows you to direct the hashes to any CSP header directive layout you might use. You can use `script-src` or `style-src` alone and concatenate the element and attribute hashes together into one list using the `all` getter property, or you can use `script-src-attr` and `style-src-attr` separately, whatever is a more secure/optimal policy for your situation.
|
|
75
79
|
**NOTE**
|
|
76
80
|
The `hashes` object structure is always the same. If there are no elements or attributes of script or style in the current file, the arrays are just empty (not null).
|
|
@@ -82,6 +86,7 @@ In this example, a build step gets the hashes for every html file under the `dis
|
|
|
82
86
|
|
|
83
87
|
```javascript
|
|
84
88
|
import gulp from 'gulp';
|
|
89
|
+
import path from 'path';
|
|
85
90
|
import hashstream from '@localnerve/csp-hashes';
|
|
86
91
|
import { cspHeaderRules } from './host-header-rules';
|
|
87
92
|
|
|
@@ -90,8 +95,8 @@ export function cspHeaders (settings) {
|
|
|
90
95
|
|
|
91
96
|
return gulp.src(`${dist}/**/*.html`)
|
|
92
97
|
.pipe(hashstream({
|
|
93
|
-
callback: (
|
|
94
|
-
const webPath =
|
|
98
|
+
callback: (p, hashes) => {
|
|
99
|
+
const webPath = p.replace(path.resolve(dist), '');
|
|
95
100
|
cspHeaderRules.updateHashes(webPath, 'script-src', hashes.script.elements.join(' '));
|
|
96
101
|
cspHeaderRules.updateHashes(webPath, 'script-src-attr', hashes.script.attributes.join(' '));
|
|
97
102
|
cspHeaderRules.updateHashes(webPath, 'style-src', hashes.style.elements.join(' '));
|
package/.eslintignore
DELETED
package/.eslintrc.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"root": true,
|
|
3
|
-
"env": {
|
|
4
|
-
"node": true,
|
|
5
|
-
"es2020": true
|
|
6
|
-
},
|
|
7
|
-
"parserOptions": {
|
|
8
|
-
"sourceType": "module",
|
|
9
|
-
"ecmaVersion": 2020
|
|
10
|
-
},
|
|
11
|
-
"extends": [
|
|
12
|
-
"eslint:recommended"
|
|
13
|
-
],
|
|
14
|
-
"rules": {
|
|
15
|
-
"indent": [2, 2, {
|
|
16
|
-
"SwitchCase": 1,
|
|
17
|
-
"MemberExpression": 1
|
|
18
|
-
}],
|
|
19
|
-
"quotes": [2, "single"],
|
|
20
|
-
"dot-notation": [2, {"allowKeywords": true}]
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
name: Verify
|
|
2
|
-
on:
|
|
3
|
-
push:
|
|
4
|
-
branches: [ main ]
|
|
5
|
-
pull_request:
|
|
6
|
-
branches: [ main ]
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
verify:
|
|
10
|
-
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
|
|
13
|
-
strategy:
|
|
14
|
-
matrix:
|
|
15
|
-
node-version: [14.x, 16.x]
|
|
16
|
-
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
17
|
-
|
|
18
|
-
steps:
|
|
19
|
-
- uses: actions/checkout@v3
|
|
20
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
21
|
-
uses: actions/setup-node@v3.0.0
|
|
22
|
-
with:
|
|
23
|
-
node-version: ${{ matrix.node-version }}
|
|
24
|
-
- run: npm ci
|
|
25
|
-
- name: Run Lint and Test
|
|
26
|
-
run: npm run lint && npm test
|
|
27
|
-
- name: Coverage Upload
|
|
28
|
-
if: ${{ success() }}
|
|
29
|
-
uses: coverallsapp/github-action@master
|
|
30
|
-
with:
|
|
31
|
-
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
32
|
-
path-to-lcov: ./coverage/lcov.info
|
package/.vscode/launch.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
// Use IntelliSense to learn about possible attributes.
|
|
3
|
-
// Hover to view descriptions of existing attributes.
|
|
4
|
-
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
|
-
"version": "0.2.0",
|
|
6
|
-
"configurations": [
|
|
7
|
-
{
|
|
8
|
-
"type": "node",
|
|
9
|
-
"request": "attach",
|
|
10
|
-
"name": "Attach to Tests",
|
|
11
|
-
"port": 9229
|
|
12
|
-
}
|
|
13
|
-
]
|
|
14
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<script>
|
|
5
|
-
console.log("foo");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
</script>
|
|
9
|
-
</head>
|
|
10
|
-
<body>
|
|
11
|
-
<script>"hello world"</script>
|
|
12
|
-
<div class="decoy"></div>
|
|
13
|
-
<button onclick="alert(0);"></button>
|
|
14
|
-
<a href="javascript:void(0)">link</a>
|
|
15
|
-
<div data-attr="decoy"></div>
|
|
16
|
-
</body>
|
|
17
|
-
</html>
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# element hashes
|
|
2
|
-
sha384-O60SQD+smnMjcJ8RHL7sAbSOyPUWgRHBovbIAphGqPN98/Iu8mtniAogp4wIsOhW
|
|
3
|
-
sha384-WmpTK6zbDiu5hx1ojbwG5VsNrqhW+OahJWEgoWt05o63pvZvCdwNIanHJLoyr3SD
|
|
4
|
-
# attribute hashes
|
|
5
|
-
sha384-bukTLq2o+W5IQrLDTC6PHPqfJ4hmAZxNVytFjb4OuGCjwVgz4fWKrwLEuGwMGN3+
|
|
6
|
-
sha384-Hs4TMINoOqtUudRVK7cWbO9tOQB1sxVWZcY9wrHsnyMIvr0urtjvW2Tl48Td9XHX
|
|
7
|
-
#
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# element hashes
|
|
2
|
-
sha512-MqQ+zvBvxknlz+ZyHsCJfMSsAwYLwpr4REOSr7Q6QhvkqbJFvjlbN6mHacwH7sS6GkbgoC5j+DWFzWh6coeP0g==
|
|
3
|
-
sha512-qAJOafTHO8uHcv2M4vRRoaHnkd7h/xuiv+DkboPHj8WwHNcevfKTc4Wt0OqnaGuRpeKnxLXBv4VByLKSvuGZqQ==
|
|
4
|
-
# attribute hashes
|
|
5
|
-
sha512-d9VZk4RVMuB9zbaCdtPmoi0jg3Q/ENmcbczKvg9eF1Km6v4jO658wc17JPo2V9eP59CGLG1Qtnj9KBA3pvFFdw==
|
|
6
|
-
sha512-95nit2a0nErfKAcXliTb4gREVstYj5n71nrjGBiyu9LTOQ0tLgNNIAYImzWBYUP5H7MjJz//7XfNfirAJKoyuA==
|
|
7
|
-
#
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# element hashes
|
|
2
|
-
sha384-O60SQD+smnMjcJ8RHL7sAbSOyPUWgRHBovbIAphGqPN98/Iu8mtniAogp4wIsOhW
|
|
3
|
-
sha384-WmpTK6zbDiu5hx1ojbwG5VsNrqhW+OahJWEgoWt05o63pvZvCdwNIanHJLoyr3SD
|
|
4
|
-
# attribute hashes
|
|
5
|
-
sha384-bukTLq2o+W5IQrLDTC6PHPqfJ4hmAZxNVytFjb4OuGCjwVgz4fWKrwLEuGwMGN3+
|
|
6
|
-
sha384-Hs4TMINoOqtUudRVK7cWbO9tOQB1sxVWZcY9wrHsnyMIvr0urtjvW2Tl48Td9XHX
|
|
7
|
-
#
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# element hashes
|
|
2
|
-
sha512-MqQ+zvBvxknlz+ZyHsCJfMSsAwYLwpr4REOSr7Q6QhvkqbJFvjlbN6mHacwH7sS6GkbgoC5j+DWFzWh6coeP0g==
|
|
3
|
-
sha512-qAJOafTHO8uHcv2M4vRRoaHnkd7h/xuiv+DkboPHj8WwHNcevfKTc4Wt0OqnaGuRpeKnxLXBv4VByLKSvuGZqQ==
|
|
4
|
-
# attribute hashes
|
|
5
|
-
sha512-d9VZk4RVMuB9zbaCdtPmoi0jg3Q/ENmcbczKvg9eF1Km6v4jO658wc17JPo2V9eP59CGLG1Qtnj9KBA3pvFFdw==
|
|
6
|
-
sha512-95nit2a0nErfKAcXliTb4gREVstYj5n71nrjGBiyu9LTOQ0tLgNNIAYImzWBYUP5H7MjJz//7XfNfirAJKoyuA==
|
|
7
|
-
#
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
# element hashes
|
|
2
|
-
sha512-dtOFpumNJPKES8S72UuTAXS6daEMYQKpRpCzlFAto/DHdx7fH0KK8sO9LjLpOjVWrDXE4G+MtisGdWa19wieSg==
|
|
3
|
-
sha512-mZQHDGDpL2tkHm3IWxwlZQUEwzH27l1bGvq4qo/9T/Ef52z0J2TGEEeOgp8INb/fGFHk4NjteTNwDTeuqb9rrw==
|
|
4
|
-
# attribute hashes
|
|
5
|
-
sha512-aOZs3PhtrZbzR6+CPKnKpStcJBq1Y4lw/+SwV5pKL52hpcq5pj+tTLv7x2uegYLFIqZ89KnfhRsrVSrQ3V3XDw==
|
|
6
|
-
#
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<script>
|
|
5
|
-
console.log("foo");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
</script>
|
|
9
|
-
<style>* { display: none; }</style>
|
|
10
|
-
</head>
|
|
11
|
-
<body>
|
|
12
|
-
<style>div { background: red; }</style>
|
|
13
|
-
<script>"hello world"</script>
|
|
14
|
-
<div class="decoy"></div>
|
|
15
|
-
<button onclick="alert(0);"></button>
|
|
16
|
-
<a href="javascript:void(0)">link</a>
|
|
17
|
-
<div data-attr="decoy"></div>
|
|
18
|
-
<div style="position:relative;"></div>
|
|
19
|
-
</body>
|
|
20
|
-
</html>
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
# element hashes
|
|
2
|
-
sha512-dtOFpumNJPKES8S72UuTAXS6daEMYQKpRpCzlFAto/DHdx7fH0KK8sO9LjLpOjVWrDXE4G+MtisGdWa19wieSg==
|
|
3
|
-
sha512-mZQHDGDpL2tkHm3IWxwlZQUEwzH27l1bGvq4qo/9T/Ef52z0J2TGEEeOgp8INb/fGFHk4NjteTNwDTeuqb9rrw==
|
|
4
|
-
# attribute hashes
|
|
5
|
-
sha512-aOZs3PhtrZbzR6+CPKnKpStcJBq1Y4lw/+SwV5pKL52hpcq5pj+tTLv7x2uegYLFIqZ89KnfhRsrVSrQ3V3XDw==
|
|
6
|
-
#
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/__tests__/index.test.js
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test entry
|
|
3
|
-
*/
|
|
4
|
-
/* eslint-env jest */
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const hashstream = require('./lib').default;
|
|
8
|
-
const Vinyl = require('vinyl');
|
|
9
|
-
|
|
10
|
-
require('@babel/register');
|
|
11
|
-
|
|
12
|
-
function fixtures (glob) {
|
|
13
|
-
return path.join(__dirname, 'fixtures', glob);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function parseHashFixture (fixtureFilename) {
|
|
17
|
-
const result = {
|
|
18
|
-
elements: [],
|
|
19
|
-
attributes: [],
|
|
20
|
-
get all () {
|
|
21
|
-
return this.elements.concat(this.attributes);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
const re = /#\s*element hashes\s*(?<elements>[^#]+)#\s*attribute hashes\s*(?<attributes>[^#]+)\s*/im;
|
|
27
|
-
const m = fs.readFileSync(fixtureFilename, { encoding: 'utf8' }).match(re);
|
|
28
|
-
const elements = m?.groups?.elements;
|
|
29
|
-
const attributes = m?.groups?.attributes;
|
|
30
|
-
if (elements) {
|
|
31
|
-
result.elements.push(...elements.replace(/\s+/g, ' ').split(/\s+/).filter(h => h.length > 0));
|
|
32
|
-
}
|
|
33
|
-
if (attributes) {
|
|
34
|
-
result.attributes.push(...attributes.replace(/\s+/g, ' ').split(/\s+/).filter(h => h.length > 0));
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
catch (e) { /* ignore */ }
|
|
38
|
-
|
|
39
|
-
return result;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function onStreamError (err) {
|
|
43
|
-
throw new Error(err.message);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function onStreamFinish (expectedHashes, actualHashes, done) {
|
|
47
|
-
Object.keys(expectedHashes).forEach(what => {
|
|
48
|
-
expect(actualHashes[what].all.join(' ')).toEqual(expectedHashes[what].all.join(' '));
|
|
49
|
-
Object.keys(expectedHashes[what]).forEach(which => {
|
|
50
|
-
expect(actualHashes[what][which].length).toEqual(expectedHashes[what][which].length);
|
|
51
|
-
for (let i = 0; i < expectedHashes[what][which].length; ++i) {
|
|
52
|
-
expect(actualHashes[what][which][i]).toEqual(expectedHashes[what][which][i]);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
done();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function run (name, algo, replace, {
|
|
60
|
-
hashFixtureScript = 'none',
|
|
61
|
-
hashFixtureStyle = 'none'
|
|
62
|
-
} = {}, done) {
|
|
63
|
-
const srcFile = new Vinyl({
|
|
64
|
-
path: fixtures(`${name}.html`),
|
|
65
|
-
cwd: 'test/',
|
|
66
|
-
base: fixtures(''),
|
|
67
|
-
contents: fs.readFileSync(fixtures(`${name}.html`))
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const scriptFixture = fixtures(`${hashFixtureScript}.${algo}`);
|
|
71
|
-
const styleFixture = fixtures(`${hashFixtureStyle}.${algo}`);
|
|
72
|
-
const expectedHashes = {
|
|
73
|
-
script: parseHashFixture(scriptFixture),
|
|
74
|
-
style: parseHashFixture(styleFixture)
|
|
75
|
-
};
|
|
76
|
-
const actualHashes = {
|
|
77
|
-
script: {
|
|
78
|
-
elements: [],
|
|
79
|
-
attributes: []
|
|
80
|
-
},
|
|
81
|
-
style: {
|
|
82
|
-
elements: [],
|
|
83
|
-
attributes: []
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const stream = hashstream({
|
|
88
|
-
algo,
|
|
89
|
-
replace,
|
|
90
|
-
callback: (path, hashes, contents) => {
|
|
91
|
-
expect(path).toEqual(fixtures(`${name}.html`));
|
|
92
|
-
if (replace) {
|
|
93
|
-
expect(contents).toEqual(srcFile.contents.toString());
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
Object.keys(hashes).forEach(what => {
|
|
97
|
-
Object.defineProperty(actualHashes[what], 'all', {
|
|
98
|
-
get: Object.getOwnPropertyDescriptor(hashes[what], 'all').get
|
|
99
|
-
});
|
|
100
|
-
Object.keys(hashes[what]).forEach(which => {
|
|
101
|
-
actualHashes[what][which].push(...hashes[what][which].map(x => x.replace(/'/g, '')));
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
return contents;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
stream.on('error', onStreamError);
|
|
110
|
-
stream.on('finish', onStreamFinish.bind(null, expectedHashes, actualHashes, done));
|
|
111
|
-
|
|
112
|
-
stream.write(srcFile);
|
|
113
|
-
stream.end();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
describe('should hash scripts correctly', () => {
|
|
117
|
-
const name = 'single-script';
|
|
118
|
-
const hashFixtureScript = name;
|
|
119
|
-
it('#sha256', done => { run(name, 'sha256', false, { hashFixtureScript }, done); });
|
|
120
|
-
it('#sha384', done => { run(name, 'sha384', false, { hashFixtureScript }, done); });
|
|
121
|
-
it('#sha512', done => { run(name, 'sha512', false, { hashFixtureScript }, done); });
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe('should hash styles correctly', () => {
|
|
125
|
-
const name = 'single-style';
|
|
126
|
-
const hashFixtureStyle = name;
|
|
127
|
-
it('#sha256', done => { run(name, 'sha256', false, { hashFixtureStyle }, done); });
|
|
128
|
-
it('#sha384', done => { run(name, 'sha384', false, { hashFixtureStyle }, done); });
|
|
129
|
-
it('#sha512', done => { run(name, 'sha512', false, { hashFixtureStyle }, done); });
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
describe('should hash multiple script tags', () => {
|
|
133
|
-
const name = 'multiple-scripts';
|
|
134
|
-
const hashFixtureScript = name;
|
|
135
|
-
it('#sha256', done => { run(name, 'sha256', false, { hashFixtureScript }, done); });
|
|
136
|
-
it('#sha384', done => { run(name, 'sha384', false, { hashFixtureScript }, done); });
|
|
137
|
-
it('#sha512', done => { run(name, 'sha512', false, { hashFixtureScript }, done); });
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe('should hash multiple style tags', () => {
|
|
141
|
-
const name = 'multiple-style';
|
|
142
|
-
const hashFixtureStyle = name;
|
|
143
|
-
it('#sha256', done => { run(name, 'sha256', false, { hashFixtureStyle }, done); });
|
|
144
|
-
it('#sha384', done => { run(name, 'sha384', false, { hashFixtureStyle }, done); });
|
|
145
|
-
it('#sha512', done => { run(name, 'sha512', false, { hashFixtureStyle }, done); });
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
describe('should ignore scripts with src attribute', () => {
|
|
149
|
-
const name = 'script-src';
|
|
150
|
-
const hashFixtureScript = name;
|
|
151
|
-
it('#sha256', done => { run(name, 'sha256', false, { hashFixtureScript }, done); });
|
|
152
|
-
it('#sha384', done => { run(name, 'sha384', false, { hashFixtureScript }, done); });
|
|
153
|
-
it('#sha512', done => { run(name, 'sha512', false, { hashFixtureScript }, done); });
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it('should throw an exception on invalid algo', () => {
|
|
157
|
-
expect(() => hashstream({ algo: 'invalid' })).toThrow();
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('should throw an exception on invalid callbacks', () => {
|
|
161
|
-
expect(() => hashstream({})).toThrow();
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
describe('should hash multiple script tags and attributes', () => {
|
|
165
|
-
const name = 'multiple-scripts-attr';
|
|
166
|
-
const hashFixtureScript = name;
|
|
167
|
-
it('#sha256', done => { run(name, 'sha256', false, { hashFixtureScript }, done); });
|
|
168
|
-
it('#sha384', done => { run(name, 'sha384', false, { hashFixtureScript }, done); });
|
|
169
|
-
it('#sha512', done => { run(name, 'sha512', false, { hashFixtureScript }, done); });
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
describe('should hash multiple style tags and attributes', () => {
|
|
173
|
-
const name = 'multiple-style-attr';
|
|
174
|
-
const hashFixtureStyle = name;
|
|
175
|
-
it('#sha256', done => { run(name, 'sha256', false, { hashFixtureStyle }, done); });
|
|
176
|
-
it('#sha384', done => { run(name, 'sha384', false, { hashFixtureStyle }, done); });
|
|
177
|
-
it('#sha512', done => { run(name, 'sha512', false, { hashFixtureStyle }, done); });
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
describe('should hash multiple style tags and attributes (REPLACE OPTION)', () => {
|
|
181
|
-
const name = 'multiple-style-attr';
|
|
182
|
-
const hashFixtureStyle = name;
|
|
183
|
-
it('#sha256', done => { run(name, 'sha256', true, { hashFixtureStyle }, done); });
|
|
184
|
-
it('#sha384', done => { run(name, 'sha384', true, { hashFixtureStyle }, done); });
|
|
185
|
-
it('#sha512', done => { run(name, 'sha512', true, { hashFixtureStyle }, done); });
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe('should hash multiple scripts and styles, elements and attributes', () => {
|
|
189
|
-
const name = 'multiple-scripts-styles';
|
|
190
|
-
const hashFixtureScript = `${name}-script`;
|
|
191
|
-
const hashFixtureStyle = `${name}-style`;
|
|
192
|
-
it('#sha256', done => { run (name, 'sha256', false, { hashFixtureScript, hashFixtureStyle }, done); });
|
|
193
|
-
it('#sha384', done => { run (name, 'sha384', false, { hashFixtureScript, hashFixtureStyle }, done); });
|
|
194
|
-
it('#sha512', done => { run (name, 'sha512', false, { hashFixtureScript, hashFixtureStyle }, done); });
|
|
195
|
-
});
|
package/babel.config.js
DELETED
package/index.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CSP Hashes.
|
|
3
|
-
*
|
|
4
|
-
* Return a Vinyl transform object stream to process html files for
|
|
5
|
-
* generating the required CSP hashes for inline and attribute scripts, styles.
|
|
6
|
-
*
|
|
7
|
-
* Copyright (c) 2022 Alex Grant (@localnerve), LocalNerve LLC
|
|
8
|
-
* Licensed under the MIT license.
|
|
9
|
-
*/
|
|
10
|
-
/* eslint-env node */
|
|
11
|
-
export { default } from './lib/index';
|
package/jest.config.js
DELETED
package/lib/index.js
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CSP Hashes.
|
|
3
|
-
*
|
|
4
|
-
* Return a Vinyl transform object stream to process html files for
|
|
5
|
-
* generating the required CSP hashes for inline and attribute scripts, styles.
|
|
6
|
-
*
|
|
7
|
-
* Copyright (c) 2022 Alex Grant (@localnerve), LocalNerve LLC
|
|
8
|
-
* Licensed under the MIT license.
|
|
9
|
-
*/
|
|
10
|
-
import cheerio from 'cheerio';
|
|
11
|
-
import through2 from 'through2';
|
|
12
|
-
import crypto from 'crypto';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Collect all CSP Hashes and fill the given `hashes` structure.
|
|
16
|
-
*
|
|
17
|
-
* @param {Function} hashFn - Creates and formats a csp hash
|
|
18
|
-
* @param {Buffer} html - The html content
|
|
19
|
-
* @param {Object} hashes - The hash structure to fill
|
|
20
|
-
*/
|
|
21
|
-
function collectHashes (hashFn, html, hashes) {
|
|
22
|
-
const $ = cheerio.load(html);
|
|
23
|
-
|
|
24
|
-
Object.keys(hashes).forEach(what => {
|
|
25
|
-
hashes[what].elements = $(`${what}:not([src])`).map(
|
|
26
|
-
(i, el) => hashFn($(el).html())
|
|
27
|
-
).toArray();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
hashes.style.attributes.push(
|
|
31
|
-
...$('[style]').map((i, el) => hashFn($(el).attr('style'))).toArray()
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
const eventHandlerRe = /^on/i;
|
|
35
|
-
const jsUrlRe = /^javascript:/i;
|
|
36
|
-
|
|
37
|
-
$('*').each(function (i, el) {
|
|
38
|
-
for (const attrName in el.attribs) {
|
|
39
|
-
if (eventHandlerRe.test(attrName)) {
|
|
40
|
-
hashes.script.attributes.push(
|
|
41
|
-
hashFn(el.attribs[attrName])
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
if (jsUrlRe.test(el.attribs[attrName])) {
|
|
45
|
-
hashes.script.attributes.push(
|
|
46
|
-
hashFn(el.attribs[attrName].split(jsUrlRe)[1])
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* hashstream
|
|
55
|
-
* Accepts the processing options and returns the Vinyl transform object stream.
|
|
56
|
-
*
|
|
57
|
-
* @param {Object} options
|
|
58
|
-
* @param {Function} options.callback - Function to call to process the csp hashes.
|
|
59
|
-
* @param {String} [options.algo] - hash algorithm, default sha256. Can be sha384, sha512.
|
|
60
|
-
* @param {Boolean} [options.replace] - True if callback is used for meta html replacements, defaults to false.
|
|
61
|
-
* @returns Transform object stream to process Vinyl objects.
|
|
62
|
-
*/
|
|
63
|
-
export default function hashstream ({
|
|
64
|
-
algo = 'sha256',
|
|
65
|
-
replace = false,
|
|
66
|
-
callback = null
|
|
67
|
-
} = {}) {
|
|
68
|
-
|
|
69
|
-
if (!/^sha(256|384|512)$/.test(algo)) {
|
|
70
|
-
throw new Error('algo option must be one of "sha256", "sha384", or "sha512" only.');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (typeof callback !== 'function') {
|
|
74
|
-
throw new Error('callback option must be a valid function.');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const createHash = r => crypto.createHash(algo).update(r).digest('base64');
|
|
78
|
-
const formatHash = h => `'${algo}-${h}'`;
|
|
79
|
-
const makeCSPHash = s => formatHash(createHash(s));
|
|
80
|
-
|
|
81
|
-
return through2.obj((vinyl, enc, done) => {
|
|
82
|
-
const path = vinyl.path;
|
|
83
|
-
const content = vinyl.contents;
|
|
84
|
-
|
|
85
|
-
const hashes = {
|
|
86
|
-
script: {
|
|
87
|
-
elements: [],
|
|
88
|
-
attributes: [],
|
|
89
|
-
get all () {
|
|
90
|
-
return this.elements.concat(this.attributes);
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
style: {
|
|
94
|
-
elements: [],
|
|
95
|
-
attributes: [],
|
|
96
|
-
get all () {
|
|
97
|
-
return this.elements.concat(this.attributes);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
collectHashes(makeCSPHash, content, hashes);
|
|
103
|
-
|
|
104
|
-
if (replace) {
|
|
105
|
-
const s = callback(path, hashes, content.toString());
|
|
106
|
-
vinyl.contents = Buffer.from(s, enc);
|
|
107
|
-
} else {
|
|
108
|
-
callback(path, hashes);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
done(null, vinyl);
|
|
112
|
-
});
|
|
113
|
-
}
|