@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.
Files changed (46) hide show
  1. package/package.json +19 -9
  2. package/readme.md +10 -5
  3. package/.eslintignore +0 -5
  4. package/.eslintrc.json +0 -22
  5. package/.github/workflows/verify.yml +0 -32
  6. package/.vscode/launch.json +0 -14
  7. package/__tests__/fixtures/multiple-scripts-attr.html +0 -17
  8. package/__tests__/fixtures/multiple-scripts-attr.sha256 +0 -7
  9. package/__tests__/fixtures/multiple-scripts-attr.sha384 +0 -7
  10. package/__tests__/fixtures/multiple-scripts-attr.sha512 +0 -7
  11. package/__tests__/fixtures/multiple-scripts-styles-script.sha256 +0 -7
  12. package/__tests__/fixtures/multiple-scripts-styles-script.sha384 +0 -7
  13. package/__tests__/fixtures/multiple-scripts-styles-script.sha512 +0 -7
  14. package/__tests__/fixtures/multiple-scripts-styles-style.sha256 +0 -6
  15. package/__tests__/fixtures/multiple-scripts-styles-style.sha384 +0 -6
  16. package/__tests__/fixtures/multiple-scripts-styles-style.sha512 +0 -6
  17. package/__tests__/fixtures/multiple-scripts-styles.html +0 -20
  18. package/__tests__/fixtures/multiple-scripts.html +0 -13
  19. package/__tests__/fixtures/multiple-scripts.sha256 +0 -5
  20. package/__tests__/fixtures/multiple-scripts.sha384 +0 -5
  21. package/__tests__/fixtures/multiple-scripts.sha512 +0 -5
  22. package/__tests__/fixtures/multiple-style-attr.html +0 -12
  23. package/__tests__/fixtures/multiple-style-attr.sha256 +0 -6
  24. package/__tests__/fixtures/multiple-style-attr.sha384 +0 -6
  25. package/__tests__/fixtures/multiple-style-attr.sha512 +0 -6
  26. package/__tests__/fixtures/multiple-style.html +0 -9
  27. package/__tests__/fixtures/multiple-style.sha256 +0 -5
  28. package/__tests__/fixtures/multiple-style.sha384 +0 -5
  29. package/__tests__/fixtures/multiple-style.sha512 +0 -5
  30. package/__tests__/fixtures/script-src.html +0 -7
  31. package/__tests__/fixtures/script-src.sha256 +0 -0
  32. package/__tests__/fixtures/script-src.sha384 +0 -0
  33. package/__tests__/fixtures/script-src.sha512 +0 -0
  34. package/__tests__/fixtures/single-script.html +0 -7
  35. package/__tests__/fixtures/single-script.sha256 +0 -4
  36. package/__tests__/fixtures/single-script.sha384 +0 -4
  37. package/__tests__/fixtures/single-script.sha512 +0 -4
  38. package/__tests__/fixtures/single-style.html +0 -8
  39. package/__tests__/fixtures/single-style.sha256 +0 -4
  40. package/__tests__/fixtures/single-style.sha384 +0 -4
  41. package/__tests__/fixtures/single-style.sha512 +0 -4
  42. package/__tests__/index.test.js +0 -195
  43. package/babel.config.js +0 -11
  44. package/index.js +0 -11
  45. package/jest.config.js +0 -10
  46. 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.3",
4
- "description": "Flexible library to handle CSP hashes at build time",
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.17.6",
16
- "@babel/preset-env": "^7.16.11",
17
- "@babel/register": "^7.17.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.11.0",
20
- "jest": "^27.5.1",
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.3",
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": "Alex Grant <alex@localnerve.com>",
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
- [![npm version](https://badge.fury.io/js/%40localnerve%2Fcsp-hashes.svg)](https://badge.fury.io/js/%40localnerve%2Fcsp-hashes)
5
+ [![npm version](https://badge.fury.io/js/@localnerve%2Fcsp-hashes.svg)](https://badge.fury.io/js/@localnerve%2Fcsp-hashes)
6
6
  ![Verify](https://github.com/localnerve/csp-hashes/workflows/Verify/badge.svg)
7
7
  [![Coverage Status](https://coveralls.io/repos/github/localnerve/csp-hashes/badge.svg?branch=main)](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 that operates on [Vinyl](https://github.com/gulpjs/vinyl) objects in [Gulp](https://github.com/gulpjs/gulp). The only required option is a [`callback`](#callback-function) function.
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: (path, hashes) => {
94
- const webPath = path.replace(dist, '');
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
@@ -1,5 +0,0 @@
1
- __tests__/lib
2
- coverage
3
- dist
4
- private
5
- tmp
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
@@ -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
- sha256-rExzpKR08DmzriumM64x74bd6TG79uFw0RlSMdXFzO4=
3
- sha256-nd7+RDWyHZAUOeVG1UoUoXWjSTuf2PvzjZ6m08v3CCY=
4
- # attribute hashes
5
- sha256-d3ii1Pel57UO62xosCMNgTaZJhJa87Gd/X6e7UdlEU8=
6
- sha256-97l24HYIWEdSIQ8PoMHzpxiGCZuyBDXtN19RPKFsOgk=
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,7 +0,0 @@
1
- # element hashes
2
- sha256-rExzpKR08DmzriumM64x74bd6TG79uFw0RlSMdXFzO4=
3
- sha256-nd7+RDWyHZAUOeVG1UoUoXWjSTuf2PvzjZ6m08v3CCY=
4
- # attribute hashes
5
- sha256-d3ii1Pel57UO62xosCMNgTaZJhJa87Gd/X6e7UdlEU8=
6
- sha256-97l24HYIWEdSIQ8PoMHzpxiGCZuyBDXtN19RPKFsOgk=
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
- sha256-ZRXDQAaaiAOOtUbmDL5Ty2JR8Zf1AonXn6DCmchNhvk=
3
- sha256-n5JetA0VmZdQj4zgyixeiOgx9wfnK2oa9/zb8+xvZks=
4
- # attribute hashes
5
- sha256-iYwYhiMcsGmXCUzLEpEzZNz5dINrlkqf1sLbLhEcqGM=
6
- #
@@ -1,6 +0,0 @@
1
- # element hashes
2
- sha384-fR76DAgrmWR4v7rpv4JBvCihgpwA2GRb4b6e4o+ThYYMJ4FQLX3l+EwzySAPxzbN
3
- sha384-vSmTBkQfDGIq3/4cHWOHpAwAHxMhwPY/5vxKe4XCcKVmQaAN0BYDoZeZu5n0qHjS
4
- # attribute hashes
5
- sha384-YHRSKUJbAwb1DUQ5EcSZ+IOjmjTr9Bcrv8E2oHbMS45tF1M/tNHACnjgCr6L+GXc
6
- #
@@ -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,13 +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
- </body>
13
- </html>
@@ -1,5 +0,0 @@
1
- # element hashes
2
- sha256-rExzpKR08DmzriumM64x74bd6TG79uFw0RlSMdXFzO4=
3
- sha256-nd7+RDWyHZAUOeVG1UoUoXWjSTuf2PvzjZ6m08v3CCY=
4
- # attribute hashes
5
- #
@@ -1,5 +0,0 @@
1
- # element hashes
2
- sha384-O60SQD+smnMjcJ8RHL7sAbSOyPUWgRHBovbIAphGqPN98/Iu8mtniAogp4wIsOhW
3
- sha384-WmpTK6zbDiu5hx1ojbwG5VsNrqhW+OahJWEgoWt05o63pvZvCdwNIanHJLoyr3SD
4
- # attribute hashes
5
- #
@@ -1,5 +0,0 @@
1
- # element hashes
2
- sha512-MqQ+zvBvxknlz+ZyHsCJfMSsAwYLwpr4REOSr7Q6QhvkqbJFvjlbN6mHacwH7sS6GkbgoC5j+DWFzWh6coeP0g==
3
- sha512-qAJOafTHO8uHcv2M4vRRoaHnkd7h/xuiv+DkboPHj8WwHNcevfKTc4Wt0OqnaGuRpeKnxLXBv4VByLKSvuGZqQ==
4
- # attribute hashes
5
- #
@@ -1,12 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <style>* { display: none; }</style>
5
- </head>
6
- <body>
7
- <style>div { background: red; }</style>
8
- <div class="decoy"></div>
9
- <div style="position:relative;"></div>
10
- <div data-attr="decoy"></div>
11
- </body>
12
- </html>
@@ -1,6 +0,0 @@
1
- # element hashes
2
- sha256-ZRXDQAaaiAOOtUbmDL5Ty2JR8Zf1AonXn6DCmchNhvk=
3
- sha256-n5JetA0VmZdQj4zgyixeiOgx9wfnK2oa9/zb8+xvZks=
4
- # attribute hashes
5
- sha256-iYwYhiMcsGmXCUzLEpEzZNz5dINrlkqf1sLbLhEcqGM=
6
- #
@@ -1,6 +0,0 @@
1
- # element hashes
2
- sha384-fR76DAgrmWR4v7rpv4JBvCihgpwA2GRb4b6e4o+ThYYMJ4FQLX3l+EwzySAPxzbN
3
- sha384-vSmTBkQfDGIq3/4cHWOHpAwAHxMhwPY/5vxKe4XCcKVmQaAN0BYDoZeZu5n0qHjS
4
- # attribute hashes
5
- sha384-YHRSKUJbAwb1DUQ5EcSZ+IOjmjTr9Bcrv8E2oHbMS45tF1M/tNHACnjgCr6L+GXc
6
- #
@@ -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,9 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <style>* { display: none; }</style>
5
- </head>
6
- <body>
7
- <style>div { background: red; }</style>
8
- </body>
9
- </html>
@@ -1,5 +0,0 @@
1
- # element hashes
2
- sha256-ZRXDQAaaiAOOtUbmDL5Ty2JR8Zf1AonXn6DCmchNhvk=
3
- sha256-n5JetA0VmZdQj4zgyixeiOgx9wfnK2oa9/zb8+xvZks=
4
- # attribute hashes
5
- #
@@ -1,5 +0,0 @@
1
- # element hashes
2
- sha384-fR76DAgrmWR4v7rpv4JBvCihgpwA2GRb4b6e4o+ThYYMJ4FQLX3l+EwzySAPxzbN
3
- sha384-vSmTBkQfDGIq3/4cHWOHpAwAHxMhwPY/5vxKe4XCcKVmQaAN0BYDoZeZu5n0qHjS
4
- # attribute hashes
5
- #
@@ -1,5 +0,0 @@
1
- # element hashes
2
- sha512-dtOFpumNJPKES8S72UuTAXS6daEMYQKpRpCzlFAto/DHdx7fH0KK8sO9LjLpOjVWrDXE4G+MtisGdWa19wieSg==
3
- sha512-mZQHDGDpL2tkHm3IWxwlZQUEwzH27l1bGvq4qo/9T/Ef52z0J2TGEEeOgp8INb/fGFHk4NjteTNwDTeuqb9rrw==
4
- # attribute hashes
5
- #
@@ -1,7 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head></head>
4
- <body>
5
- <script src="test.js"></script>
6
- </body>
7
- </html>
File without changes
File without changes
File without changes
@@ -1,7 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head></head>
4
- <body>
5
- <script>"hello world"</script>
6
- </body>
7
- </html>
@@ -1,4 +0,0 @@
1
- # element hashes
2
- sha256-nd7+RDWyHZAUOeVG1UoUoXWjSTuf2PvzjZ6m08v3CCY=
3
- # attribute hashes
4
- #
@@ -1,4 +0,0 @@
1
- # element hashes
2
- sha384-WmpTK6zbDiu5hx1ojbwG5VsNrqhW+OahJWEgoWt05o63pvZvCdwNIanHJLoyr3SD
3
- # attribute hashes
4
- #
@@ -1,4 +0,0 @@
1
- # element hashes
2
- sha512-qAJOafTHO8uHcv2M4vRRoaHnkd7h/xuiv+DkboPHj8WwHNcevfKTc4Wt0OqnaGuRpeKnxLXBv4VByLKSvuGZqQ==
3
- # attribute hashes
4
- #
@@ -1,8 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <style>* { display: none; }</style>
5
- </head>
6
- <body>
7
- </body>
8
- </html>
@@ -1,4 +0,0 @@
1
- # element hashes
2
- sha256-ZRXDQAaaiAOOtUbmDL5Ty2JR8Zf1AonXn6DCmchNhvk=
3
- # attribute hashes
4
- #
@@ -1,4 +0,0 @@
1
- # element hashes
2
- sha384-fR76DAgrmWR4v7rpv4JBvCihgpwA2GRb4b6e4o+ThYYMJ4FQLX3l+EwzySAPxzbN
3
- # attribute hashes
4
- #
@@ -1,4 +0,0 @@
1
- # element hashes
2
- sha512-dtOFpumNJPKES8S72UuTAXS6daEMYQKpRpCzlFAto/DHdx7fH0KK8sO9LjLpOjVWrDXE4G+MtisGdWa19wieSg==
3
- # attribute hashes
4
- #
@@ -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
@@ -1,11 +0,0 @@
1
- module.exports = {
2
- presets: [
3
- [
4
- '@babel/preset-env', {
5
- targets: {
6
- node: '16'
7
- }
8
- }
9
- ]
10
- ]
11
- };
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
@@ -1,10 +0,0 @@
1
- module.exports = {
2
- collectCoverage: true,
3
- coverageDirectory: 'coverage',
4
- verbose: true,
5
- testEnvironment: 'node',
6
- testPathIgnorePatterns: [
7
- '/node_modules/',
8
- '/tmp'
9
- ]
10
- };
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
- }