@localnerve/csp-hashes 1.0.2 → 2.0.0
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/dist/cjs/index.js +24 -0
- package/dist/cjs/lib/index.js +111 -0
- package/dist/cjs/lib/removeCspMeta.js +35 -0
- package/dist/index.js +11 -18
- package/dist/lib/index.js +64 -43
- package/dist/lib/removeCspMeta.js +32 -0
- package/{dist/index.mjs → index.js} +1 -1
- package/lib/index.js +121 -0
- package/lib/removeCspMeta.js +32 -0
- package/package.json +12 -12
- package/readme.md +27 -2
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "default", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _index.hashstream;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "hashstream", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _index.hashstream;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "removeCspMeta", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _index.removeCspMeta;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
var _index = require("./lib/index.js");
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = exports.hashstream = hashstream;
|
|
7
|
+
Object.defineProperty(exports, "removeCspMeta", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function () {
|
|
10
|
+
return _removeCspMeta.removeCspMeta;
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
var _stream = require("stream");
|
|
14
|
+
var _crypto = _interopRequireDefault(require("crypto"));
|
|
15
|
+
var _cheerio = _interopRequireDefault(require("cheerio"));
|
|
16
|
+
var _removeCspMeta = require("./removeCspMeta.js");
|
|
17
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
18
|
+
/**
|
|
19
|
+
* CSP Hashes.
|
|
20
|
+
*
|
|
21
|
+
* Return a Vinyl transform object stream to process html files for
|
|
22
|
+
* generating the required CSP hashes for inline and attribute scripts, styles.
|
|
23
|
+
*
|
|
24
|
+
* Copyright (c) 2022 Alex Grant (@localnerve), LocalNerve LLC
|
|
25
|
+
* Licensed under the MIT license.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Collect all CSP Hashes and fill the given `hashes` structure.
|
|
30
|
+
*
|
|
31
|
+
* @param {Function} hashFn - Creates and formats a csp hash
|
|
32
|
+
* @param {Buffer} html - The html content
|
|
33
|
+
* @param {Object} hashes - The hash structure to fill
|
|
34
|
+
*/
|
|
35
|
+
function collectHashes(hashFn, html, hashes) {
|
|
36
|
+
const $ = _cheerio.default.load(html);
|
|
37
|
+
Object.keys(hashes).forEach(what => {
|
|
38
|
+
hashes[what].elements = $(`${what}:not([src])`).map((i, el) => hashFn($(el).html())).toArray();
|
|
39
|
+
});
|
|
40
|
+
hashes.style.attributes.push(...$('[style]').map((i, el) => hashFn($(el).attr('style'))).toArray());
|
|
41
|
+
const eventHandlerRe = /^on/i;
|
|
42
|
+
const jsUrlRe = /^javascript:/i;
|
|
43
|
+
$('*').each(function (i, el) {
|
|
44
|
+
for (const attrName in el.attribs) {
|
|
45
|
+
if (eventHandlerRe.test(attrName)) {
|
|
46
|
+
hashes.script.attributes.push(hashFn(el.attribs[attrName]));
|
|
47
|
+
}
|
|
48
|
+
if (jsUrlRe.test(el.attribs[attrName])) {
|
|
49
|
+
hashes.script.attributes.push(hashFn(el.attribs[attrName].split(jsUrlRe)[1]));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* hashstream
|
|
57
|
+
* Accepts the processing options and returns the Vinyl transform object stream.
|
|
58
|
+
*
|
|
59
|
+
* @param {Object} options
|
|
60
|
+
* @param {Function} options.callback - Function to call to process the csp hashes.
|
|
61
|
+
* @param {String} [options.algo] - hash algorithm, default sha256. Can be sha384, sha512.
|
|
62
|
+
* @param {Boolean} [options.replace] - True if callback is used for meta html replacements, defaults to false.
|
|
63
|
+
* @returns Transform object stream to process Vinyl objects.
|
|
64
|
+
*/
|
|
65
|
+
function hashstream({
|
|
66
|
+
algo = 'sha256',
|
|
67
|
+
replace = false,
|
|
68
|
+
callback = null
|
|
69
|
+
} = {}) {
|
|
70
|
+
if (!/^sha(256|384|512)$/.test(algo)) {
|
|
71
|
+
throw new Error('algo option must be one of "sha256", "sha384", or "sha512" only.');
|
|
72
|
+
}
|
|
73
|
+
if (typeof callback !== 'function') {
|
|
74
|
+
throw new Error('callback option must be a valid function.');
|
|
75
|
+
}
|
|
76
|
+
const createHash = r => _crypto.default.createHash(algo).update(r).digest('base64');
|
|
77
|
+
const formatHash = h => `'${algo}-${h}'`;
|
|
78
|
+
const makeCSPHash = s => formatHash(createHash(s));
|
|
79
|
+
const transformObjectStream = new _stream.Transform({
|
|
80
|
+
objectMode: true,
|
|
81
|
+
transform: (vinyl, enc, done) => {
|
|
82
|
+
const path = vinyl.path;
|
|
83
|
+
const content = vinyl.contents;
|
|
84
|
+
const hashes = {
|
|
85
|
+
script: {
|
|
86
|
+
elements: [],
|
|
87
|
+
attributes: [],
|
|
88
|
+
get all() {
|
|
89
|
+
return this.elements.concat(this.attributes);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
style: {
|
|
93
|
+
elements: [],
|
|
94
|
+
attributes: [],
|
|
95
|
+
get all() {
|
|
96
|
+
return this.elements.concat(this.attributes);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
collectHashes(makeCSPHash, content, hashes);
|
|
101
|
+
if (replace) {
|
|
102
|
+
const s = callback(path, hashes, content.toString());
|
|
103
|
+
vinyl.contents = Buffer.from(s, enc);
|
|
104
|
+
} else {
|
|
105
|
+
callback(path, hashes);
|
|
106
|
+
}
|
|
107
|
+
done(null, vinyl);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return transformObjectStream;
|
|
111
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = exports.removeCspMeta = removeCspMeta;
|
|
7
|
+
var _stream = require("stream");
|
|
8
|
+
/**
|
|
9
|
+
* removeCspMeta.js
|
|
10
|
+
*
|
|
11
|
+
* A convenience method to remove the content of a Content-Security-Policy in a meta tag.
|
|
12
|
+
* Useful for development builds that need to ignore CSP meta tags.
|
|
13
|
+
*
|
|
14
|
+
* Copyright (c) 2022 Alex Grant (@localnerve), LocalNerve LLC
|
|
15
|
+
* Licensed under the MIT license.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
function removeCspMeta() {
|
|
19
|
+
return new _stream.Transform({
|
|
20
|
+
objectMode: true,
|
|
21
|
+
transform: (vinyl, enc, done) => {
|
|
22
|
+
var _vinyl$contents;
|
|
23
|
+
let e = null;
|
|
24
|
+
const input = vinyl === null || vinyl === void 0 ? void 0 : (_vinyl$contents = vinyl.contents) === null || _vinyl$contents === void 0 ? void 0 : _vinyl$contents.toString();
|
|
25
|
+
if (input) {
|
|
26
|
+
const output = input.replace(/("?Content-Security-Policy"?)(\s+)(content=")([^"]+)"/i, '$1$2$3$2"');
|
|
27
|
+
vinyl.contents = Buffer.from(output, enc);
|
|
28
|
+
} else {
|
|
29
|
+
e = new Error('removeCspMeta could not get Vinyl object file contents');
|
|
30
|
+
e.errorCode = 'EBADINPUT';
|
|
31
|
+
}
|
|
32
|
+
done(e, vinyl);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
Object.defineProperty(exports, "hashstream", {
|
|
13
|
-
enumerable: true,
|
|
14
|
-
get: function () {
|
|
15
|
-
return _index.hashstream;
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
var _index = require("./lib/index.js");
|
|
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 { hashstream as default, hashstream, removeCspMeta } from './lib/index.js';
|
package/dist/lib/index.js
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = exports.hashstream = hashstream;
|
|
7
|
-
var _cheerio = _interopRequireDefault(require("cheerio"));
|
|
8
|
-
var _through = _interopRequireDefault(require("through2"));
|
|
9
|
-
var _crypto = _interopRequireDefault(require("crypto"));
|
|
10
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
11
1
|
/**
|
|
12
2
|
* CSP Hashes.
|
|
13
3
|
*
|
|
@@ -17,6 +7,10 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
17
7
|
* Copyright (c) 2022 Alex Grant (@localnerve), LocalNerve LLC
|
|
18
8
|
* Licensed under the MIT license.
|
|
19
9
|
*/
|
|
10
|
+
import { Transform } from 'stream';
|
|
11
|
+
import crypto from 'crypto';
|
|
12
|
+
import cheerio from 'cheerio';
|
|
13
|
+
export { removeCspMeta } from './removeCspMeta.js';
|
|
20
14
|
|
|
21
15
|
/**
|
|
22
16
|
* Collect all CSP Hashes and fill the given `hashes` structure.
|
|
@@ -25,21 +19,33 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
25
19
|
* @param {Buffer} html - The html content
|
|
26
20
|
* @param {Object} hashes - The hash structure to fill
|
|
27
21
|
*/
|
|
28
|
-
function collectHashes(hashFn, html, hashes) {
|
|
29
|
-
const $ =
|
|
22
|
+
function collectHashes (hashFn, html, hashes) {
|
|
23
|
+
const $ = cheerio.load(html);
|
|
24
|
+
|
|
30
25
|
Object.keys(hashes).forEach(what => {
|
|
31
|
-
hashes[what].elements = $(`${what}:not([src])`).map(
|
|
26
|
+
hashes[what].elements = $(`${what}:not([src])`).map(
|
|
27
|
+
(i, el) => hashFn($(el).html())
|
|
28
|
+
).toArray();
|
|
32
29
|
});
|
|
33
|
-
|
|
30
|
+
|
|
31
|
+
hashes.style.attributes.push(
|
|
32
|
+
...$('[style]').map((i, el) => hashFn($(el).attr('style'))).toArray()
|
|
33
|
+
);
|
|
34
|
+
|
|
34
35
|
const eventHandlerRe = /^on/i;
|
|
35
36
|
const jsUrlRe = /^javascript:/i;
|
|
37
|
+
|
|
36
38
|
$('*').each(function (i, el) {
|
|
37
39
|
for (const attrName in el.attribs) {
|
|
38
40
|
if (eventHandlerRe.test(attrName)) {
|
|
39
|
-
hashes.script.attributes.push(
|
|
41
|
+
hashes.script.attributes.push(
|
|
42
|
+
hashFn(el.attribs[attrName])
|
|
43
|
+
);
|
|
40
44
|
}
|
|
41
45
|
if (jsUrlRe.test(el.attribs[attrName])) {
|
|
42
|
-
hashes.script.attributes.push(
|
|
46
|
+
hashes.script.attributes.push(
|
|
47
|
+
hashFn(el.attribs[attrName].split(jsUrlRe)[1])
|
|
48
|
+
);
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
});
|
|
@@ -55,46 +61,61 @@ function collectHashes(hashFn, html, hashes) {
|
|
|
55
61
|
* @param {Boolean} [options.replace] - True if callback is used for meta html replacements, defaults to false.
|
|
56
62
|
* @returns Transform object stream to process Vinyl objects.
|
|
57
63
|
*/
|
|
58
|
-
function hashstream({
|
|
64
|
+
export function hashstream ({
|
|
59
65
|
algo = 'sha256',
|
|
60
66
|
replace = false,
|
|
61
67
|
callback = null
|
|
62
68
|
} = {}) {
|
|
69
|
+
|
|
63
70
|
if (!/^sha(256|384|512)$/.test(algo)) {
|
|
64
71
|
throw new Error('algo option must be one of "sha256", "sha384", or "sha512" only.');
|
|
65
72
|
}
|
|
73
|
+
|
|
66
74
|
if (typeof callback !== 'function') {
|
|
67
75
|
throw new Error('callback option must be a valid function.');
|
|
68
76
|
}
|
|
69
|
-
|
|
77
|
+
|
|
78
|
+
const createHash = r => crypto.createHash(algo).update(r).digest('base64');
|
|
70
79
|
const formatHash = h => `'${algo}-${h}'`;
|
|
71
80
|
const makeCSPHash = s => formatHash(createHash(s));
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
81
|
+
|
|
82
|
+
const transformObjectStream = new Transform({
|
|
83
|
+
objectMode: true,
|
|
84
|
+
transform: (vinyl, enc, done) => {
|
|
85
|
+
const path = vinyl.path;
|
|
86
|
+
const content = vinyl.contents;
|
|
87
|
+
|
|
88
|
+
const hashes = {
|
|
89
|
+
script: {
|
|
90
|
+
elements: [],
|
|
91
|
+
attributes: [],
|
|
92
|
+
get all () {
|
|
93
|
+
return this.elements.concat(this.attributes);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
style: {
|
|
97
|
+
elements: [],
|
|
98
|
+
attributes: [],
|
|
99
|
+
get all () {
|
|
100
|
+
return this.elements.concat(this.attributes);
|
|
101
|
+
}
|
|
88
102
|
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
collectHashes(makeCSPHash, content, hashes);
|
|
106
|
+
|
|
107
|
+
if (replace) {
|
|
108
|
+
const s = callback(path, hashes, content.toString());
|
|
109
|
+
vinyl.contents = Buffer.from(s, enc);
|
|
110
|
+
} else {
|
|
111
|
+
callback(path, hashes);
|
|
89
112
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (replace) {
|
|
93
|
-
const s = callback(path, hashes, content.toString());
|
|
94
|
-
vinyl.contents = Buffer.from(s, enc);
|
|
95
|
-
} else {
|
|
96
|
-
callback(path, hashes);
|
|
113
|
+
|
|
114
|
+
done(null, vinyl);
|
|
97
115
|
}
|
|
98
|
-
done(null, vinyl);
|
|
99
116
|
});
|
|
100
|
-
|
|
117
|
+
|
|
118
|
+
return transformObjectStream;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export { hashstream as default }
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* removeCspMeta.js
|
|
3
|
+
*
|
|
4
|
+
* A convenience method to remove the content of a Content-Security-Policy in a meta tag.
|
|
5
|
+
* Useful for development builds that need to ignore CSP meta tags.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2022 Alex Grant (@localnerve), LocalNerve LLC
|
|
8
|
+
* Licensed under the MIT license.
|
|
9
|
+
*/
|
|
10
|
+
import { Transform } from 'stream';
|
|
11
|
+
|
|
12
|
+
export function removeCspMeta () {
|
|
13
|
+
return new Transform({
|
|
14
|
+
objectMode: true,
|
|
15
|
+
transform: (vinyl, enc, done) => {
|
|
16
|
+
let e = null;
|
|
17
|
+
const input = vinyl?.contents?.toString();
|
|
18
|
+
if (input) {
|
|
19
|
+
const output = input.replace(
|
|
20
|
+
/("?Content-Security-Policy"?)(\s+)(content=")([^"]+)"/i, '$1$2$3$2"'
|
|
21
|
+
);
|
|
22
|
+
vinyl.contents = Buffer.from(output, enc);
|
|
23
|
+
} else {
|
|
24
|
+
e = new Error('removeCspMeta could not get Vinyl object file contents');
|
|
25
|
+
e.errorCode = 'EBADINPUT';
|
|
26
|
+
}
|
|
27
|
+
done(e, vinyl);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { removeCspMeta as default }
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
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 { Transform } from 'stream';
|
|
11
|
+
import crypto from 'crypto';
|
|
12
|
+
import cheerio from 'cheerio';
|
|
13
|
+
export { removeCspMeta } from './removeCspMeta.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Collect all CSP Hashes and fill the given `hashes` structure.
|
|
17
|
+
*
|
|
18
|
+
* @param {Function} hashFn - Creates and formats a csp hash
|
|
19
|
+
* @param {Buffer} html - The html content
|
|
20
|
+
* @param {Object} hashes - The hash structure to fill
|
|
21
|
+
*/
|
|
22
|
+
function collectHashes (hashFn, html, hashes) {
|
|
23
|
+
const $ = cheerio.load(html);
|
|
24
|
+
|
|
25
|
+
Object.keys(hashes).forEach(what => {
|
|
26
|
+
hashes[what].elements = $(`${what}:not([src])`).map(
|
|
27
|
+
(i, el) => hashFn($(el).html())
|
|
28
|
+
).toArray();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
hashes.style.attributes.push(
|
|
32
|
+
...$('[style]').map((i, el) => hashFn($(el).attr('style'))).toArray()
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const eventHandlerRe = /^on/i;
|
|
36
|
+
const jsUrlRe = /^javascript:/i;
|
|
37
|
+
|
|
38
|
+
$('*').each(function (i, el) {
|
|
39
|
+
for (const attrName in el.attribs) {
|
|
40
|
+
if (eventHandlerRe.test(attrName)) {
|
|
41
|
+
hashes.script.attributes.push(
|
|
42
|
+
hashFn(el.attribs[attrName])
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (jsUrlRe.test(el.attribs[attrName])) {
|
|
46
|
+
hashes.script.attributes.push(
|
|
47
|
+
hashFn(el.attribs[attrName].split(jsUrlRe)[1])
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* hashstream
|
|
56
|
+
* Accepts the processing options and returns the Vinyl transform object stream.
|
|
57
|
+
*
|
|
58
|
+
* @param {Object} options
|
|
59
|
+
* @param {Function} options.callback - Function to call to process the csp hashes.
|
|
60
|
+
* @param {String} [options.algo] - hash algorithm, default sha256. Can be sha384, sha512.
|
|
61
|
+
* @param {Boolean} [options.replace] - True if callback is used for meta html replacements, defaults to false.
|
|
62
|
+
* @returns Transform object stream to process Vinyl objects.
|
|
63
|
+
*/
|
|
64
|
+
export function hashstream ({
|
|
65
|
+
algo = 'sha256',
|
|
66
|
+
replace = false,
|
|
67
|
+
callback = null
|
|
68
|
+
} = {}) {
|
|
69
|
+
|
|
70
|
+
if (!/^sha(256|384|512)$/.test(algo)) {
|
|
71
|
+
throw new Error('algo option must be one of "sha256", "sha384", or "sha512" only.');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof callback !== 'function') {
|
|
75
|
+
throw new Error('callback option must be a valid function.');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const createHash = r => crypto.createHash(algo).update(r).digest('base64');
|
|
79
|
+
const formatHash = h => `'${algo}-${h}'`;
|
|
80
|
+
const makeCSPHash = s => formatHash(createHash(s));
|
|
81
|
+
|
|
82
|
+
const transformObjectStream = new Transform({
|
|
83
|
+
objectMode: true,
|
|
84
|
+
transform: (vinyl, enc, done) => {
|
|
85
|
+
const path = vinyl.path;
|
|
86
|
+
const content = vinyl.contents;
|
|
87
|
+
|
|
88
|
+
const hashes = {
|
|
89
|
+
script: {
|
|
90
|
+
elements: [],
|
|
91
|
+
attributes: [],
|
|
92
|
+
get all () {
|
|
93
|
+
return this.elements.concat(this.attributes);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
style: {
|
|
97
|
+
elements: [],
|
|
98
|
+
attributes: [],
|
|
99
|
+
get all () {
|
|
100
|
+
return this.elements.concat(this.attributes);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
collectHashes(makeCSPHash, content, hashes);
|
|
106
|
+
|
|
107
|
+
if (replace) {
|
|
108
|
+
const s = callback(path, hashes, content.toString());
|
|
109
|
+
vinyl.contents = Buffer.from(s, enc);
|
|
110
|
+
} else {
|
|
111
|
+
callback(path, hashes);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
done(null, vinyl);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return transformObjectStream;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export { hashstream as default }
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* removeCspMeta.js
|
|
3
|
+
*
|
|
4
|
+
* A convenience method to remove the content of a Content-Security-Policy in a meta tag.
|
|
5
|
+
* Useful for development builds that need to ignore CSP meta tags.
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2022 Alex Grant (@localnerve), LocalNerve LLC
|
|
8
|
+
* Licensed under the MIT license.
|
|
9
|
+
*/
|
|
10
|
+
import { Transform } from 'stream';
|
|
11
|
+
|
|
12
|
+
export function removeCspMeta () {
|
|
13
|
+
return new Transform({
|
|
14
|
+
objectMode: true,
|
|
15
|
+
transform: (vinyl, enc, done) => {
|
|
16
|
+
let e = null;
|
|
17
|
+
const input = vinyl?.contents?.toString();
|
|
18
|
+
if (input) {
|
|
19
|
+
const output = input.replace(
|
|
20
|
+
/("?Content-Security-Policy"?)(\s+)(content=")([^"]+)"/i, '$1$2$3$2"'
|
|
21
|
+
);
|
|
22
|
+
vinyl.contents = Buffer.from(output, enc);
|
|
23
|
+
} else {
|
|
24
|
+
e = new Error('removeCspMeta could not get Vinyl object file contents');
|
|
25
|
+
e.errorCode = 'EBADINPUT';
|
|
26
|
+
}
|
|
27
|
+
done(e, vinyl);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { removeCspMeta as default }
|
package/package.json
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@localnerve/csp-hashes",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Flexible library to generate CSP hashes",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"exports": {
|
|
7
|
-
"import": "./dist/index.
|
|
8
|
-
"require": "./dist/index.js",
|
|
8
|
+
"import": "./dist/index.js",
|
|
9
|
+
"require": "./dist/cjs/index.js",
|
|
9
10
|
"default": "./dist/index.js"
|
|
10
11
|
},
|
|
11
12
|
"scripts": {
|
|
12
13
|
"lint": "eslint .",
|
|
13
|
-
"transpile": "
|
|
14
|
-
"
|
|
15
|
-
"prepublishOnly": "
|
|
14
|
+
"transpile": "babel --out-dir ./dist/cjs index.js && babel --out-dir ./dist/cjs/lib ./lib",
|
|
15
|
+
"preprepublishOnly": "rimraf ./dist && npm run transpile",
|
|
16
|
+
"prepublishOnly": "node -e 'require(\"fs\").copyFileSync(\"./index.js\", \"./dist/index.js\");'",
|
|
17
|
+
"postprepublishOnly": "node -e 'require(\"fs\").cpSync(\"./lib\", \"./dist/lib\", {recursive: true});'",
|
|
16
18
|
"pretest": "node -e 'try{require(\"fs\").symlinkSync(\"../lib\", \"./__tests__/lib\");}catch(e){}'",
|
|
17
19
|
"test": "jest",
|
|
18
20
|
"test:debug": "node --inspect-brk ./node_modules/.bin/jest"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|
|
21
23
|
"@babel/cli": "^7.19.3",
|
|
22
|
-
"@babel/preset-env": "^7.
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"jest": "^29.2.2",
|
|
24
|
+
"@babel/preset-env": "^7.20.2",
|
|
25
|
+
"eslint": "^8.30.0",
|
|
26
|
+
"jest": "^29.3.1",
|
|
26
27
|
"rimraf": "^3.0.2",
|
|
27
28
|
"vinyl": "^2.2.1"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"cheerio": "^1.0.0-rc.12"
|
|
31
|
-
"through2": "^4.0.2"
|
|
31
|
+
"cheerio": "^1.0.0-rc.12"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|
package/readme.md
CHANGED
|
@@ -24,7 +24,9 @@ This Nodejs library generates script and style inline element and attribute hash
|
|
|
24
24
|
+ NodeJS 14+
|
|
25
25
|
|
|
26
26
|
## API
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
### hashstream (also the default export)
|
|
29
|
+
This library exports a 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.
|
|
28
30
|
|
|
29
31
|
```
|
|
30
32
|
Stream hashstream ({
|
|
@@ -34,7 +36,14 @@ Stream hashstream ({
|
|
|
34
36
|
})
|
|
35
37
|
```
|
|
36
38
|
|
|
37
|
-
###
|
|
39
|
+
### removeCspMeta
|
|
40
|
+
This library also exports a convenience helper method, `removeCspMeta` that is useful for some types of development builds. This method takes no options and returns a stream that operates on [Vinyl](https://github.com/gulpjs/vinyl) objects and removes any `Content-Security-Policy` content found in the files.
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Stream removeCspMeta ()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Hashstream Options
|
|
38
47
|
|
|
39
48
|
+ {Function} **callback** - Required - A [function](#callback-function) to process the hashes. Receives file contents and must return new file contents if `replace` option is true.
|
|
40
49
|
+ {Boolean} **\[replace\]** - Optional - Defaults to `false`, set to true to indicate your `callback` function returns new file contents to replace the original.
|
|
@@ -129,6 +138,22 @@ export function cspMetaTags (settings) {
|
|
|
129
138
|
}
|
|
130
139
|
```
|
|
131
140
|
|
|
141
|
+
### Build Step to Remove CSP Meta Tag Content
|
|
142
|
+
In this example, a build step removes any content from a `Content-Security-Policy` in a development build that wishes to ignore it.
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
import gulp from 'gulp';
|
|
146
|
+
import { removeCspMeta } from '@localnerve/csp-hashes';
|
|
147
|
+
|
|
148
|
+
export function stripCspMetaContents (settings) {
|
|
149
|
+
const { dist } = settings;
|
|
150
|
+
|
|
151
|
+
return gulp.src(`${dist}/**/*.html`)
|
|
152
|
+
.pipe(removeCspMeta())
|
|
153
|
+
.pipe(gulp.dest(dist));
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
132
157
|
## LICENSE
|
|
133
158
|
|
|
134
159
|
* [MIT, Alex Grant, LocalNerve, LLC](license.md)
|