@react-foundry/fastify-harden 0.1.9 → 0.2.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/common.js +2 -7
- package/dist/content-security-policy.js +24 -28
- package/dist/index.js +7 -13
- package/dist/permissions-policy.js +4 -8
- package/package.json +6 -8
- package/dist/common.mjs +0 -2
- package/dist/content-security-policy.mjs +0 -57
- package/dist/index.mjs +0 -50
- package/dist/permissions-policy.mjs +0 -79
package/dist/common.js
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.isDefined = exports.id = void 0;
|
|
4
|
-
const id = (v) => v;
|
|
5
|
-
exports.id = id;
|
|
6
|
-
const isDefined = (v) => (v !== undefined && v !== '');
|
|
7
|
-
exports.isDefined = isDefined;
|
|
1
|
+
export const id = (v) => v;
|
|
2
|
+
export const isDefined = (v) => (v !== undefined && v !== '');
|
|
@@ -1,41 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
exports.unsafeEval = "'unsafe-eval'";
|
|
8
|
-
exports.unsafeInline = "'unsafe-inline'";
|
|
9
|
-
const contentSecurityPolicy = ({ dev = process.env.NODE_ENV === 'development', formAction: _formAction, frameAncestors: _frameAncestors, nonce: _nonce }) => {
|
|
1
|
+
import { id, isDefined } from './common';
|
|
2
|
+
export const none = "'none'";
|
|
3
|
+
export const self = "'self'";
|
|
4
|
+
export const unsafeEval = "'unsafe-eval'";
|
|
5
|
+
export const unsafeInline = "'unsafe-inline'";
|
|
6
|
+
export const contentSecurityPolicy = ({ dev = process.env.NODE_ENV === 'development', formAction: _formAction, frameAncestors: _frameAncestors, nonce: _nonce }) => {
|
|
10
7
|
const formAction = (Array.isArray(_formAction)
|
|
11
8
|
? _formAction
|
|
12
|
-
: [_formAction]).filter(
|
|
9
|
+
: [_formAction]).filter(id);
|
|
13
10
|
const frameAncestors = (Array.isArray(_frameAncestors)
|
|
14
11
|
? _frameAncestors
|
|
15
|
-
: [_frameAncestors]).filter(
|
|
12
|
+
: [_frameAncestors]).filter(id);
|
|
16
13
|
const frameAncestor = (frameAncestors.length > 1
|
|
17
14
|
? 'multiple'
|
|
18
|
-
: frameAncestors[0]) ||
|
|
15
|
+
: frameAncestors[0]) || none;
|
|
19
16
|
const frameOptions = (frameAncestor === 'multiple'
|
|
20
17
|
? undefined
|
|
21
|
-
: (frameAncestor ===
|
|
18
|
+
: (frameAncestor === self
|
|
22
19
|
? 'SAMEORIGIN'
|
|
23
20
|
: 'DENY'));
|
|
24
21
|
const nonce = _nonce && `'nonce-${_nonce}'`;
|
|
25
22
|
const cspObject = {
|
|
26
|
-
'default-src':
|
|
23
|
+
'default-src': none,
|
|
27
24
|
'connect-src': (dev
|
|
28
|
-
? [
|
|
29
|
-
:
|
|
30
|
-
'font-src':
|
|
31
|
-
'frame-src':
|
|
32
|
-
'img-src':
|
|
33
|
-
'manifest-src':
|
|
34
|
-
'media-src':
|
|
35
|
-
'script-src': [
|
|
36
|
-
'style-src': [
|
|
37
|
-
'form-action': formAction.length && formAction ||
|
|
38
|
-
'frame-ancestors': frameAncestors.length && frameAncestors ||
|
|
25
|
+
? [self, 'ws://localhost:*']
|
|
26
|
+
: self),
|
|
27
|
+
'font-src': self,
|
|
28
|
+
'frame-src': self,
|
|
29
|
+
'img-src': self,
|
|
30
|
+
'manifest-src': self,
|
|
31
|
+
'media-src': self,
|
|
32
|
+
'script-src': [self, nonce],
|
|
33
|
+
'style-src': [self, unsafeInline],
|
|
34
|
+
'form-action': formAction.length && formAction || self,
|
|
35
|
+
'frame-ancestors': frameAncestors.length && frameAncestors || none
|
|
39
36
|
};
|
|
40
37
|
const cspString = (Object.keys(cspObject)
|
|
41
38
|
.map(directive => {
|
|
@@ -44,18 +41,17 @@ const contentSecurityPolicy = ({ dev = process.env.NODE_ENV === 'development', f
|
|
|
44
41
|
? _valueArr
|
|
45
42
|
: [_valueArr]);
|
|
46
43
|
const values = (valueArr
|
|
47
|
-
.filter(
|
|
44
|
+
.filter(isDefined)
|
|
48
45
|
.map(v => `${v}`)
|
|
49
46
|
.join(' '));
|
|
50
47
|
return (values === ''
|
|
51
48
|
? undefined
|
|
52
49
|
: `${directive} ${values}`);
|
|
53
50
|
})
|
|
54
|
-
.filter(
|
|
51
|
+
.filter(isDefined)
|
|
55
52
|
.join('; '));
|
|
56
53
|
return {
|
|
57
54
|
policy: cspString,
|
|
58
55
|
frameOptions
|
|
59
56
|
};
|
|
60
57
|
};
|
|
61
|
-
exports.contentSecurityPolicy = contentSecurityPolicy;
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.fastifyHarden = void 0;
|
|
7
|
-
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
8
|
-
const permissions_policy_1 = require("./permissions-policy");
|
|
9
|
-
const content_security_policy_1 = require("./content-security-policy");
|
|
1
|
+
import fp from 'fastify-plugin';
|
|
2
|
+
import { permissionsPolicy } from './permissions-policy';
|
|
3
|
+
import { contentSecurityPolicy } from './content-security-policy';
|
|
10
4
|
const fastifyHardenPlugin = async (fastify, { contentSecurityPolicy: cspOptions = {}, dev = process.env.NODE_ENV === 'development', permissionsPolicy: ppOptions = {} }) => {
|
|
11
5
|
if (!dev) {
|
|
12
6
|
fastify.setErrorHandler((error, req, reply) => {
|
|
@@ -26,8 +20,8 @@ const fastifyHardenPlugin = async (fastify, { contentSecurityPolicy: cspOptions
|
|
|
26
20
|
fastify.addHook('onSend', async (_req, reply, payload) => {
|
|
27
21
|
const nonce = reply.cspNonce;
|
|
28
22
|
const headers = reply.getHeaders();
|
|
29
|
-
const { policy: pp } =
|
|
30
|
-
const { policy: csp, frameOptions } =
|
|
23
|
+
const { policy: pp } = permissionsPolicy(ppOptions);
|
|
24
|
+
const { policy: csp, frameOptions } = contentSecurityPolicy({
|
|
31
25
|
...cspOptions,
|
|
32
26
|
dev,
|
|
33
27
|
nonce
|
|
@@ -49,8 +43,8 @@ const fastifyHardenPlugin = async (fastify, { contentSecurityPolicy: cspOptions
|
|
|
49
43
|
return payload;
|
|
50
44
|
});
|
|
51
45
|
};
|
|
52
|
-
|
|
46
|
+
export const fastifyHarden = fp(fastifyHardenPlugin, {
|
|
53
47
|
fastify: '5.x',
|
|
54
48
|
name: 'harden',
|
|
55
49
|
});
|
|
56
|
-
|
|
50
|
+
export default fastifyHarden;
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.permissionsPolicy = void 0;
|
|
4
|
-
const common_1 = require("./common");
|
|
1
|
+
import { isDefined } from './common';
|
|
5
2
|
const keywords = [
|
|
6
3
|
'self',
|
|
7
4
|
'src'
|
|
@@ -60,7 +57,7 @@ const ppObj = {
|
|
|
60
57
|
'vertical-scroll': 'self',
|
|
61
58
|
'xr-spatial-tracking': 'self'
|
|
62
59
|
};
|
|
63
|
-
const permissionsPolicy = (_options) => ({
|
|
60
|
+
export const permissionsPolicy = (_options) => ({
|
|
64
61
|
policy: Object.keys(ppObj)
|
|
65
62
|
.map(directive => {
|
|
66
63
|
const _valueArr = ppObj[directive];
|
|
@@ -68,7 +65,7 @@ const permissionsPolicy = (_options) => ({
|
|
|
68
65
|
? _valueArr
|
|
69
66
|
: [_valueArr]);
|
|
70
67
|
const values = (valueArr
|
|
71
|
-
.filter(
|
|
68
|
+
.filter(isDefined)
|
|
72
69
|
.map(v => (keywords.includes(v)
|
|
73
70
|
? v
|
|
74
71
|
: `"${v}"`))
|
|
@@ -77,7 +74,6 @@ const permissionsPolicy = (_options) => ({
|
|
|
77
74
|
? undefined
|
|
78
75
|
: `${directive}=(${values})`);
|
|
79
76
|
})
|
|
80
|
-
.filter(
|
|
77
|
+
.filter(isDefined)
|
|
81
78
|
.join(', ')
|
|
82
79
|
});
|
|
83
|
-
exports.permissionsPolicy = permissionsPolicy;
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-foundry/fastify-harden",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Fastify plugin for extra cyber-security hardening.",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"exports": {
|
|
7
8
|
".": {
|
|
8
9
|
"types": "./dist/index.d.ts",
|
|
9
|
-
"import": "./dist/index.
|
|
10
|
-
"
|
|
11
|
-
"default": "./dist/index.mjs"
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
@@ -31,11 +31,9 @@
|
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
34
|
-
"build": "
|
|
35
|
-
"build:esm": "tsc -m es2022 && find dist -name '*.js' -exec sh -c 'mv \"$0\" \"${0%.js}.mjs\"' {} \\;",
|
|
36
|
-
"build:cjs": "tsc",
|
|
34
|
+
"build": "tsc",
|
|
37
35
|
"clean": "rm -rf dist tsconfig.tsbuildinfo"
|
|
38
36
|
},
|
|
39
|
-
"module": "dist/index.
|
|
37
|
+
"module": "dist/index.js",
|
|
40
38
|
"typings": "dist/index.d.ts"
|
|
41
39
|
}
|
package/dist/common.mjs
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { id, isDefined } from './common';
|
|
2
|
-
export const none = "'none'";
|
|
3
|
-
export const self = "'self'";
|
|
4
|
-
export const unsafeEval = "'unsafe-eval'";
|
|
5
|
-
export const unsafeInline = "'unsafe-inline'";
|
|
6
|
-
export const contentSecurityPolicy = ({ dev = process.env.NODE_ENV === 'development', formAction: _formAction, frameAncestors: _frameAncestors, nonce: _nonce }) => {
|
|
7
|
-
const formAction = (Array.isArray(_formAction)
|
|
8
|
-
? _formAction
|
|
9
|
-
: [_formAction]).filter(id);
|
|
10
|
-
const frameAncestors = (Array.isArray(_frameAncestors)
|
|
11
|
-
? _frameAncestors
|
|
12
|
-
: [_frameAncestors]).filter(id);
|
|
13
|
-
const frameAncestor = (frameAncestors.length > 1
|
|
14
|
-
? 'multiple'
|
|
15
|
-
: frameAncestors[0]) || none;
|
|
16
|
-
const frameOptions = (frameAncestor === 'multiple'
|
|
17
|
-
? undefined
|
|
18
|
-
: (frameAncestor === self
|
|
19
|
-
? 'SAMEORIGIN'
|
|
20
|
-
: 'DENY'));
|
|
21
|
-
const nonce = _nonce && `'nonce-${_nonce}'`;
|
|
22
|
-
const cspObject = {
|
|
23
|
-
'default-src': none,
|
|
24
|
-
'connect-src': (dev
|
|
25
|
-
? [self, 'ws://localhost:*']
|
|
26
|
-
: self),
|
|
27
|
-
'font-src': self,
|
|
28
|
-
'frame-src': self,
|
|
29
|
-
'img-src': self,
|
|
30
|
-
'manifest-src': self,
|
|
31
|
-
'media-src': self,
|
|
32
|
-
'script-src': [self, nonce],
|
|
33
|
-
'style-src': [self, unsafeInline],
|
|
34
|
-
'form-action': formAction.length && formAction || self,
|
|
35
|
-
'frame-ancestors': frameAncestors.length && frameAncestors || none
|
|
36
|
-
};
|
|
37
|
-
const cspString = (Object.keys(cspObject)
|
|
38
|
-
.map(directive => {
|
|
39
|
-
const _valueArr = cspObject[directive];
|
|
40
|
-
const valueArr = (Array.isArray(_valueArr)
|
|
41
|
-
? _valueArr
|
|
42
|
-
: [_valueArr]);
|
|
43
|
-
const values = (valueArr
|
|
44
|
-
.filter(isDefined)
|
|
45
|
-
.map(v => `${v}`)
|
|
46
|
-
.join(' '));
|
|
47
|
-
return (values === ''
|
|
48
|
-
? undefined
|
|
49
|
-
: `${directive} ${values}`);
|
|
50
|
-
})
|
|
51
|
-
.filter(isDefined)
|
|
52
|
-
.join('; '));
|
|
53
|
-
return {
|
|
54
|
-
policy: cspString,
|
|
55
|
-
frameOptions
|
|
56
|
-
};
|
|
57
|
-
};
|
package/dist/index.mjs
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import fp from 'fastify-plugin';
|
|
2
|
-
import { permissionsPolicy } from './permissions-policy';
|
|
3
|
-
import { contentSecurityPolicy } from './content-security-policy';
|
|
4
|
-
const fastifyHardenPlugin = async (fastify, { contentSecurityPolicy: cspOptions = {}, dev = process.env.NODE_ENV === 'development', permissionsPolicy: ppOptions = {} }) => {
|
|
5
|
-
if (!dev) {
|
|
6
|
-
fastify.setErrorHandler((error, req, reply) => {
|
|
7
|
-
const statusCode = error && error.statusCode;
|
|
8
|
-
if (!statusCode || statusCode === 500) {
|
|
9
|
-
error.message = 'An unexpected error occurred.';
|
|
10
|
-
}
|
|
11
|
-
reply.send(error);
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
fastify.addHook('preHandler', async (_req, reply) => {
|
|
15
|
-
const nonce = (Math.random()
|
|
16
|
-
.toString(36)
|
|
17
|
-
.slice(2));
|
|
18
|
-
reply.cspNonce = nonce;
|
|
19
|
-
});
|
|
20
|
-
fastify.addHook('onSend', async (_req, reply, payload) => {
|
|
21
|
-
const nonce = reply.cspNonce;
|
|
22
|
-
const headers = reply.getHeaders();
|
|
23
|
-
const { policy: pp } = permissionsPolicy(ppOptions);
|
|
24
|
-
const { policy: csp, frameOptions } = contentSecurityPolicy({
|
|
25
|
-
...cspOptions,
|
|
26
|
-
dev,
|
|
27
|
-
nonce
|
|
28
|
-
});
|
|
29
|
-
if (!headers['cache-control']) {
|
|
30
|
-
reply.header('Cache-Control', 'no-cache, no-store, must-revalidate, private');
|
|
31
|
-
reply.header('Pragma', 'no-cache');
|
|
32
|
-
reply.header('Expires', '0');
|
|
33
|
-
reply.header('Cross-Origin-Embedder-Policy', 'require-corp');
|
|
34
|
-
reply.header('Cross-Origin-Resource-Policy', 'same-origin');
|
|
35
|
-
reply.header('Cross-Origin-Opener-Policy', 'same-origin');
|
|
36
|
-
}
|
|
37
|
-
reply.header('X-Content-Type-Options', 'nosniff');
|
|
38
|
-
if (frameOptions) {
|
|
39
|
-
reply.header('X-Frame-Options', frameOptions);
|
|
40
|
-
}
|
|
41
|
-
reply.header('Content-Security-Policy', csp);
|
|
42
|
-
reply.header('Permissions-Policy', pp);
|
|
43
|
-
return payload;
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
export const fastifyHarden = fp(fastifyHardenPlugin, {
|
|
47
|
-
fastify: '5.x',
|
|
48
|
-
name: 'harden',
|
|
49
|
-
});
|
|
50
|
-
export default fastifyHarden;
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { isDefined } from './common';
|
|
2
|
-
const keywords = [
|
|
3
|
-
'self',
|
|
4
|
-
'src'
|
|
5
|
-
];
|
|
6
|
-
const ppObj = {
|
|
7
|
-
'accelerometer': 'self',
|
|
8
|
-
'ambient-light-sensor': 'self',
|
|
9
|
-
'attribution-reporting': 'self',
|
|
10
|
-
'autoplay': 'self',
|
|
11
|
-
'battery': 'self',
|
|
12
|
-
'bluetooth': 'self',
|
|
13
|
-
'browsing-topics': 'self',
|
|
14
|
-
'camera': 'self',
|
|
15
|
-
'clipboard-read': 'self',
|
|
16
|
-
'clipboard-write': 'self',
|
|
17
|
-
'compute-pressure': 'self',
|
|
18
|
-
'conversion-measurement': 'self',
|
|
19
|
-
'cross-origin-isolated': 'self',
|
|
20
|
-
'display-capture': 'self',
|
|
21
|
-
'document-domain': 'self',
|
|
22
|
-
'encrypted-media': 'self',
|
|
23
|
-
'execution-while-not-rendered': 'self',
|
|
24
|
-
'execution-while-out-of-viewport': 'self',
|
|
25
|
-
'focus-without-user-activation': 'self',
|
|
26
|
-
'fullscreen': 'self',
|
|
27
|
-
'gamepad': 'self',
|
|
28
|
-
'geolocation': 'self',
|
|
29
|
-
'gyroscope': 'self',
|
|
30
|
-
'keyboard-map': 'self',
|
|
31
|
-
'hid': 'self',
|
|
32
|
-
'identity-credentials-get': 'self',
|
|
33
|
-
'idle-detection': 'self',
|
|
34
|
-
'interest-cohort': 'self',
|
|
35
|
-
'local-fonts': 'self',
|
|
36
|
-
'magnetometer': 'self',
|
|
37
|
-
'microphone': 'self',
|
|
38
|
-
'midi': 'self',
|
|
39
|
-
'navigation-override': 'self',
|
|
40
|
-
'otp-credentials': 'self',
|
|
41
|
-
'payment': 'self',
|
|
42
|
-
'picture-in-picture': 'self',
|
|
43
|
-
'publickey-credentials-create': 'self',
|
|
44
|
-
'publickey-credentials-get': 'self',
|
|
45
|
-
'screen-wake-lock': 'self',
|
|
46
|
-
'serial': 'self',
|
|
47
|
-
'speaker-selection': 'self',
|
|
48
|
-
'storage-access': 'self',
|
|
49
|
-
'sync-script': 'self',
|
|
50
|
-
'sync-xhr': 'self',
|
|
51
|
-
'trust-token-redemption': 'self',
|
|
52
|
-
'unload': 'self',
|
|
53
|
-
'usb': 'self',
|
|
54
|
-
'web-share': 'self',
|
|
55
|
-
'window-management': 'self',
|
|
56
|
-
'window-placement': 'self',
|
|
57
|
-
'vertical-scroll': 'self',
|
|
58
|
-
'xr-spatial-tracking': 'self'
|
|
59
|
-
};
|
|
60
|
-
export const permissionsPolicy = (_options) => ({
|
|
61
|
-
policy: Object.keys(ppObj)
|
|
62
|
-
.map(directive => {
|
|
63
|
-
const _valueArr = ppObj[directive];
|
|
64
|
-
const valueArr = (Array.isArray(_valueArr)
|
|
65
|
-
? _valueArr
|
|
66
|
-
: [_valueArr]);
|
|
67
|
-
const values = (valueArr
|
|
68
|
-
.filter(isDefined)
|
|
69
|
-
.map(v => (keywords.includes(v)
|
|
70
|
-
? v
|
|
71
|
-
: `"${v}"`))
|
|
72
|
-
.join(' '));
|
|
73
|
-
return (values === ''
|
|
74
|
-
? undefined
|
|
75
|
-
: `${directive}=(${values})`);
|
|
76
|
-
})
|
|
77
|
-
.filter(isDefined)
|
|
78
|
-
.join(', ')
|
|
79
|
-
});
|