@contrast/assess 1.63.0 → 1.65.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/lib/{session-configuration → configuration-analysis}/common.js +1 -1
- package/lib/{session-configuration → configuration-analysis}/handlers.js +24 -11
- package/lib/{session-configuration → configuration-analysis}/index.js +6 -4
- package/lib/configuration-analysis/install/apollo-server.js +92 -0
- package/lib/{session-configuration → configuration-analysis}/install/express-session.js +2 -2
- package/lib/{session-configuration → configuration-analysis}/install/fastify-cookie.js +2 -2
- package/lib/configuration-analysis/install/graphql-yoga.js +90 -0
- package/lib/{session-configuration → configuration-analysis}/install/hapi.js +2 -2
- package/lib/{session-configuration → configuration-analysis}/install/koa.js +3 -3
- package/lib/dataflow/propagation/install/string/substring.js +1 -1
- package/lib/dataflow/sources/handler.js +30 -26
- package/lib/dataflow/sources/index.js +2 -0
- package/lib/dataflow/sources/install/fastify-websocket.js +63 -0
- package/lib/dataflow/sources/install/http.js +42 -38
- package/lib/dataflow/sources/install/koa/index.js +1 -1
- package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +76 -48
- package/lib/dataflow/sources/install/koa/koa-multer.js +1 -1
- package/lib/dataflow/sources/install/koa/koa-routers.js +2 -2
- package/lib/dataflow/sources/install/koa/{koa2.js → koa.js} +3 -3
- package/lib/dataflow/sources/install/socket.io.js +80 -0
- package/lib/get-source-context.js +10 -21
- package/lib/index.d.ts +4 -3
- package/lib/index.js +2 -2
- package/lib/make-source-context.js +5 -10
- package/lib/policy.js +400 -0
- package/lib/response-scanning/handlers/index.js +10 -14
- package/package.json +12 -12
- package/lib/get-policy.js +0 -336
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
|
|
18
18
|
const {
|
|
19
19
|
Event,
|
|
20
|
-
|
|
20
|
+
ConfigurationRule,
|
|
21
21
|
isString,
|
|
22
22
|
} = require('@contrast/common');
|
|
23
23
|
|
|
24
|
-
const { HTTPONLY, SECURE_FLAG_MISSING } =
|
|
24
|
+
const { HTTPONLY, SECURE_FLAG_MISSING, GRAPHQL_INTROSPECTION } = ConfigurationRule;
|
|
25
25
|
|
|
26
26
|
module.exports = function (core) {
|
|
27
27
|
const {
|
|
28
|
-
assess: {
|
|
28
|
+
assess: { configurationAnalysis },
|
|
29
29
|
messages,
|
|
30
30
|
} = core;
|
|
31
31
|
|
|
@@ -40,7 +40,7 @@ module.exports = function (core) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* @param {
|
|
43
|
+
* @param {ConfigurationRule} ruleId
|
|
44
44
|
* @param {import('@contrast/assess').SourceContext} sourceContext
|
|
45
45
|
* @returns {import('@contrast/assess').SessionRuleState}
|
|
46
46
|
*/
|
|
@@ -67,7 +67,7 @@ module.exports = function (core) {
|
|
|
67
67
|
function handle(ruleId, sourceContext, cookie, sessionEvent) {
|
|
68
68
|
const state = ensureState(ruleId, sourceContext);
|
|
69
69
|
|
|
70
|
-
if (
|
|
70
|
+
if (sourceContext?.policy?.disabledRules?.has?.(ruleId) || state.reported) return;
|
|
71
71
|
|
|
72
72
|
for (const value of ensureIterable(cookie)) {
|
|
73
73
|
if (state.valuesAnalyzed.has(value)) continue;
|
|
@@ -76,7 +76,7 @@ module.exports = function (core) {
|
|
|
76
76
|
if (!isVulnerable(ruleId, value)) continue;
|
|
77
77
|
|
|
78
78
|
else {
|
|
79
|
-
|
|
79
|
+
configurationAnalysis.reportFindings({
|
|
80
80
|
ruleId,
|
|
81
81
|
sinkEvent: sessionEvent,
|
|
82
82
|
properties: {
|
|
@@ -89,17 +89,30 @@ module.exports = function (core) {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
configurationAnalysis.handleHttpOnly = function(sourceContext, cookie, sessionEvent) {
|
|
93
93
|
handle(HTTPONLY, sourceContext, cookie, sessionEvent);
|
|
94
94
|
};
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
configurationAnalysis.handleSecure = function (sourceContext, cookie, sessionEvent) {
|
|
97
97
|
handle(SECURE_FLAG_MISSING, sourceContext, cookie, sessionEvent);
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
configurationAnalysis.handleGraphqlIntrospection = function (sourceContext, sessionEvent, value) {
|
|
101
|
+
const ruleId = GRAPHQL_INTROSPECTION;
|
|
102
|
+
const state = ensureState(ruleId, sourceContext);
|
|
103
|
+
if (sourceContext?.policy?.disabledRules?.has?.(ruleId) || state.reported) return;
|
|
104
|
+
|
|
105
|
+
configurationAnalysis.reportFindings({
|
|
106
|
+
ruleId,
|
|
107
|
+
sinkEvent: sessionEvent,
|
|
108
|
+
evidence: value
|
|
109
|
+
});
|
|
110
|
+
state.reported = true;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
configurationAnalysis.reportFindings = function (finding) {
|
|
114
|
+
messages.emit(Event.ASSESS_CONFIGURATION_FINDING, finding);
|
|
102
115
|
};
|
|
103
116
|
|
|
104
|
-
return
|
|
117
|
+
return configurationAnalysis;
|
|
105
118
|
};
|
|
@@ -18,17 +18,19 @@
|
|
|
18
18
|
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
19
19
|
|
|
20
20
|
module.exports = function(core) {
|
|
21
|
-
const
|
|
21
|
+
const configurationAnalysis = core.assess.configurationAnalysis = {};
|
|
22
22
|
|
|
23
23
|
require('./handlers')(core);
|
|
24
|
+
require('./install/apollo-server')(core);
|
|
25
|
+
require('./install/graphql-yoga')(core);
|
|
24
26
|
require('./install/express-session')(core);
|
|
25
27
|
require('./install/fastify-cookie')(core);
|
|
26
28
|
require('./install/hapi')(core);
|
|
27
29
|
require('./install/koa')(core);
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
callChildComponentMethodsSync(
|
|
31
|
+
configurationAnalysis.install = function() {
|
|
32
|
+
callChildComponentMethodsSync(configurationAnalysis, 'install');
|
|
31
33
|
};
|
|
32
34
|
|
|
33
|
-
return
|
|
35
|
+
return configurationAnalysis;
|
|
34
36
|
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2025 Contrast Security, Inc
|
|
3
|
+
* Contact: support@contrastsecurity.com
|
|
4
|
+
* License: Commercial
|
|
5
|
+
|
|
6
|
+
* NOTICE: This Software and the patented inventions embodied within may only be
|
|
7
|
+
* used as part of Contrast Security’s commercial offerings. Even though it is
|
|
8
|
+
* made available through public repositories, use of this Software is subject to
|
|
9
|
+
* the applicable End User Licensing Agreement found at
|
|
10
|
+
* https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
11
|
+
* between Contrast Security and the End User. The Software may not be reverse
|
|
12
|
+
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
|
+
* way not consistent with the End User License Agreement.
|
|
14
|
+
*/
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const { patchType } = require('../common');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {{
|
|
21
|
+
* assess: import('@contrast/assess').Assess,
|
|
22
|
+
* scopes: import('@contrast/scopes').Scopes,
|
|
23
|
+
* }} core
|
|
24
|
+
*/
|
|
25
|
+
module.exports = function (core) {
|
|
26
|
+
const {
|
|
27
|
+
assess: {
|
|
28
|
+
inspect, // TODO NODE-3455: remove
|
|
29
|
+
getSourceContext,
|
|
30
|
+
eventFactory: { createSessionEvent },
|
|
31
|
+
configurationAnalysis: {
|
|
32
|
+
handleGraphqlIntrospection
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
depHooks,
|
|
36
|
+
patcher,
|
|
37
|
+
} = core;
|
|
38
|
+
|
|
39
|
+
const apolloServer = core.assess.configurationAnalysis.apolloServer = {};
|
|
40
|
+
|
|
41
|
+
apolloServer.install = function () {
|
|
42
|
+
return depHooks.resolve({ name: '@apollo/server', version: '>=4', file: 'dist/cjs' }, (xport) => {
|
|
43
|
+
if (!xport.ApolloServer) return;
|
|
44
|
+
patcher.patch(xport, 'ApolloServer', {
|
|
45
|
+
name: '@apollo/server.ApolloServer',
|
|
46
|
+
patchType,
|
|
47
|
+
post(data) {
|
|
48
|
+
if (!data.args[0]?.introspection) return;
|
|
49
|
+
|
|
50
|
+
const options = { introspection: true };
|
|
51
|
+
const optionsString = inspect(options);
|
|
52
|
+
const sessionEvent = createSessionEvent({
|
|
53
|
+
args: [{
|
|
54
|
+
tracked: false,
|
|
55
|
+
value: optionsString,
|
|
56
|
+
}],
|
|
57
|
+
context: optionsString,
|
|
58
|
+
name: '@apollo/server',
|
|
59
|
+
moduleName: 'ApolloServer',
|
|
60
|
+
methodName: '',
|
|
61
|
+
object: {
|
|
62
|
+
tracked: false,
|
|
63
|
+
value: 'ApolloServer',
|
|
64
|
+
},
|
|
65
|
+
result: {
|
|
66
|
+
tracked: false,
|
|
67
|
+
},
|
|
68
|
+
source: 'P0',
|
|
69
|
+
stacktraceOpts: {
|
|
70
|
+
constructorOpt: data.hooked,
|
|
71
|
+
},
|
|
72
|
+
framework: 'graphql',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
patcher.patch(data.result, 'executeHTTPGraphQLRequest', {
|
|
76
|
+
name: 'ApolloServer.executeHTTPGraphQLRequest',
|
|
77
|
+
patchType,
|
|
78
|
+
post(data) {
|
|
79
|
+
const sourceContext = getSourceContext();
|
|
80
|
+
if (!sourceContext) return;
|
|
81
|
+
|
|
82
|
+
handleGraphqlIntrospection(sourceContext, sessionEvent, optionsString);
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return apolloServer;
|
|
92
|
+
};
|
|
@@ -29,7 +29,7 @@ module.exports = function (core) {
|
|
|
29
29
|
inspect, // TODO NODE-3455: remove
|
|
30
30
|
getSourceContext,
|
|
31
31
|
eventFactory: { createSessionEvent },
|
|
32
|
-
|
|
32
|
+
configurationAnalysis: {
|
|
33
33
|
handleHttpOnly,
|
|
34
34
|
handleSecure,
|
|
35
35
|
},
|
|
@@ -38,7 +38,7 @@ module.exports = function (core) {
|
|
|
38
38
|
patcher,
|
|
39
39
|
} = core;
|
|
40
40
|
|
|
41
|
-
const expressSession = core.assess.
|
|
41
|
+
const expressSession = core.assess.configurationAnalysis.expressSession = {};
|
|
42
42
|
|
|
43
43
|
expressSession.install = function () {
|
|
44
44
|
return depHooks.resolve({ name: 'express-session', version: '<2' }, (session) => {
|
|
@@ -29,7 +29,7 @@ module.exports = function (core) {
|
|
|
29
29
|
inspect, // TODO NODE-3455: remove
|
|
30
30
|
getSourceContext,
|
|
31
31
|
eventFactory: { createSessionEvent },
|
|
32
|
-
|
|
32
|
+
configurationAnalysis: {
|
|
33
33
|
handleHttpOnly,
|
|
34
34
|
handleSecure,
|
|
35
35
|
},
|
|
@@ -38,7 +38,7 @@ module.exports = function (core) {
|
|
|
38
38
|
patcher,
|
|
39
39
|
} = core;
|
|
40
40
|
|
|
41
|
-
return core.assess.
|
|
41
|
+
return core.assess.configurationAnalysis.fastifyCookie = {
|
|
42
42
|
install () {
|
|
43
43
|
depHooks.resolve({ name: '@fastify/cookie', version: '<12' }, (_export) => {
|
|
44
44
|
const patched = patcher.patch(_export, {
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2025 Contrast Security, Inc
|
|
3
|
+
* Contact: support@contrastsecurity.com
|
|
4
|
+
* License: Commercial
|
|
5
|
+
|
|
6
|
+
* NOTICE: This Software and the patented inventions embodied within may only be
|
|
7
|
+
* used as part of Contrast Security’s commercial offerings. Even though it is
|
|
8
|
+
* made available through public repositories, use of this Software is subject to
|
|
9
|
+
* the applicable End User Licensing Agreement found at
|
|
10
|
+
* https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
11
|
+
* between Contrast Security and the End User. The Software may not be reverse
|
|
12
|
+
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
|
+
* way not consistent with the End User License Agreement.
|
|
14
|
+
*/
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const { patchType } = require('../common');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {{
|
|
21
|
+
* assess: import('@contrast/assess').Assess,
|
|
22
|
+
* scopes: import('@contrast/scopes').Scopes,
|
|
23
|
+
* }} core
|
|
24
|
+
*/
|
|
25
|
+
module.exports = function (core) {
|
|
26
|
+
const {
|
|
27
|
+
assess: {
|
|
28
|
+
inspect, // TODO NODE-3455: remove
|
|
29
|
+
getSourceContext,
|
|
30
|
+
eventFactory: { createSessionEvent },
|
|
31
|
+
configurationAnalysis: {
|
|
32
|
+
handleGraphqlIntrospection
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
depHooks,
|
|
36
|
+
patcher,
|
|
37
|
+
} = core;
|
|
38
|
+
|
|
39
|
+
const graphqlYoga = core.assess.configurationAnalysis.graphqlYoga = {};
|
|
40
|
+
|
|
41
|
+
graphqlYoga.install = function () {
|
|
42
|
+
return depHooks.resolve({ name: '@graphql-yoga/plugin-disable-introspection', version: '*', file: 'cjs' }, (xport) => patcher.patch(xport, 'useDisableIntrospection', {
|
|
43
|
+
name: '@graphql-yoga/plugin-disable-introspection.useDisableIntrospection',
|
|
44
|
+
patchType,
|
|
45
|
+
post(data) {
|
|
46
|
+
const options = data.args[0];
|
|
47
|
+
const optionsString = inspect(options);
|
|
48
|
+
patcher.patch(data.result, 'onValidate', {
|
|
49
|
+
name: 'onValidate',
|
|
50
|
+
patchType,
|
|
51
|
+
pre(data) {
|
|
52
|
+
patcher.patch(data.args[0], 'addValidationRule', {
|
|
53
|
+
name: 'addValidationRule',
|
|
54
|
+
patchType,
|
|
55
|
+
post(data) {
|
|
56
|
+
const sourceContext = getSourceContext();
|
|
57
|
+
if (!sourceContext) return;
|
|
58
|
+
const sessionEvent = createSessionEvent({
|
|
59
|
+
args: [{
|
|
60
|
+
tracked: false,
|
|
61
|
+
value: optionsString,
|
|
62
|
+
}],
|
|
63
|
+
context: optionsString,
|
|
64
|
+
name: '@graphql-yoga',
|
|
65
|
+
moduleName: 'plugin-disable-introspection',
|
|
66
|
+
methodName: 'addValidationRule',
|
|
67
|
+
object: {
|
|
68
|
+
tracked: false,
|
|
69
|
+
value: 'plugin-disable-introspection',
|
|
70
|
+
},
|
|
71
|
+
result: {
|
|
72
|
+
tracked: false,
|
|
73
|
+
},
|
|
74
|
+
source: 'P0',
|
|
75
|
+
stacktraceOpts: {
|
|
76
|
+
constructorOpt: data.hooked,
|
|
77
|
+
},
|
|
78
|
+
framework: 'graphql',
|
|
79
|
+
});
|
|
80
|
+
handleGraphqlIntrospection(sourceContext, sessionEvent, optionsString);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}));
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return graphqlYoga;
|
|
90
|
+
};
|
|
@@ -21,7 +21,7 @@ module.exports = function (core) {
|
|
|
21
21
|
assess: {
|
|
22
22
|
inspect, // TODO NODE-3455: remove
|
|
23
23
|
eventFactory: { createSessionEvent },
|
|
24
|
-
|
|
24
|
+
configurationAnalysis: {
|
|
25
25
|
handleHttpOnly,
|
|
26
26
|
handleSecure,
|
|
27
27
|
},
|
|
@@ -31,7 +31,7 @@ module.exports = function (core) {
|
|
|
31
31
|
scopes: { sources },
|
|
32
32
|
} = core;
|
|
33
33
|
|
|
34
|
-
const hapiSession = core.assess.
|
|
34
|
+
const hapiSession = core.assess.configurationAnalysis.hapiSession = {};
|
|
35
35
|
|
|
36
36
|
hapiSession.install = function () {
|
|
37
37
|
return depHooks.resolve({ name: '@hapi/hapi', version: '>=18 <22' }, (hapi) => {
|
|
@@ -28,7 +28,7 @@ module.exports = function (core) {
|
|
|
28
28
|
inspect, // TODO NODE-3455: remove
|
|
29
29
|
getSourceContext,
|
|
30
30
|
eventFactory: { createSessionEvent },
|
|
31
|
-
|
|
31
|
+
configurationAnalysis: {
|
|
32
32
|
handleHttpOnly,
|
|
33
33
|
handleSecure,
|
|
34
34
|
},
|
|
@@ -37,9 +37,9 @@ module.exports = function (core) {
|
|
|
37
37
|
patcher,
|
|
38
38
|
} = core;
|
|
39
39
|
|
|
40
|
-
return core.assess.
|
|
40
|
+
return core.assess.configurationAnalysis.koa = {
|
|
41
41
|
install () {
|
|
42
|
-
depHooks.resolve({ name: 'koa', version: '>=2.3.0 <
|
|
42
|
+
depHooks.resolve({ name: 'koa', version: '>=2.3.0 <4' }, (Koa) => {
|
|
43
43
|
patcher.patch(Koa.prototype, 'use', {
|
|
44
44
|
name: 'Koa.Application',
|
|
45
45
|
patchType,
|
|
@@ -89,7 +89,7 @@ module.exports = function(core) {
|
|
|
89
89
|
const event = createPropagationEvent({
|
|
90
90
|
name,
|
|
91
91
|
moduleName: 'String',
|
|
92
|
-
methodName:
|
|
92
|
+
methodName: `prototype.${method}`,
|
|
93
93
|
get context() {
|
|
94
94
|
return `'${objInfo.value}'.substring(${ArrayPrototypeJoin.call(args.map(a => a.value))})`;
|
|
95
95
|
},
|
|
@@ -45,24 +45,19 @@ module.exports = Core.makeComponent({
|
|
|
45
45
|
const logger = core.logger.child({ name: 'contrast:sources' });
|
|
46
46
|
|
|
47
47
|
sources.createTags = function createTags({ inputType, fieldName = '', value, tagNames }) {
|
|
48
|
-
if (!value?.length)
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
48
|
+
if (!value?.length) return null;
|
|
51
49
|
|
|
52
50
|
const stop = value.length - 1;
|
|
53
|
-
const tags = {
|
|
54
|
-
[DataflowTag.UNTRUSTED]: [0, stop]
|
|
55
|
-
};
|
|
51
|
+
const tags = { [DataflowTag.UNTRUSTED]: [0, stop] };
|
|
56
52
|
|
|
57
|
-
if (tagNames)
|
|
58
|
-
for (const tag of tagNames)
|
|
53
|
+
if (tagNames)
|
|
54
|
+
for (const tag of tagNames)
|
|
59
55
|
tags[tag] = [0, stop];
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
56
|
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
if (
|
|
58
|
+
inputType === InputType.HEADER &&
|
|
59
|
+
StringPrototypeToLowerCase.call(fieldName) === 'referer'
|
|
60
|
+
) tags[DataflowTag.HEADER] = [0, stop];
|
|
66
61
|
|
|
67
62
|
return tags;
|
|
68
63
|
};
|
|
@@ -81,6 +76,7 @@ module.exports = Core.makeComponent({
|
|
|
81
76
|
stacktraceOpts,
|
|
82
77
|
data,
|
|
83
78
|
sourceContext,
|
|
79
|
+
onEvent,
|
|
84
80
|
}) {
|
|
85
81
|
if (!data) return;
|
|
86
82
|
|
|
@@ -89,14 +85,7 @@ module.exports = Core.makeComponent({
|
|
|
89
85
|
return null;
|
|
90
86
|
}
|
|
91
87
|
|
|
92
|
-
|
|
93
|
-
if (!sourceContext.policy) {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (!context) {
|
|
98
|
-
context = inputType;
|
|
99
|
-
}
|
|
88
|
+
if (!context) context = inputType;
|
|
100
89
|
|
|
101
90
|
const { policy: requestPolicy } = sourceContext;
|
|
102
91
|
const max = config.assess.max_context_source_events;
|
|
@@ -111,10 +100,13 @@ module.exports = Core.makeComponent({
|
|
|
111
100
|
}
|
|
112
101
|
|
|
113
102
|
function createEvent({ fieldName, pathName, value, excludedRules }) {
|
|
114
|
-
|
|
103
|
+
let tagNames;
|
|
104
|
+
if (excludedRules) {
|
|
105
|
+
tagNames = Array.from(excludedRules).map((ruleId) => `excluded:${ruleId}`);
|
|
106
|
+
}
|
|
115
107
|
// create the stacktrace once per call to .handle()
|
|
116
108
|
stack || (stack = sources.createStacktrace(stacktraceOpts));
|
|
117
|
-
|
|
109
|
+
const eventData = {
|
|
118
110
|
context: `${context}.${pathName}`,
|
|
119
111
|
name,
|
|
120
112
|
fieldName,
|
|
@@ -123,11 +115,19 @@ module.exports = Core.makeComponent({
|
|
|
123
115
|
inputType,
|
|
124
116
|
tags: sources.createTags({ inputType, fieldName, value, tagNames }),
|
|
125
117
|
result: { tracked: true, value },
|
|
126
|
-
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const event = eventFactory.createSourceEvent(eventData);;
|
|
121
|
+
if (event && onEvent) onEvent(event, fieldName, pathName);
|
|
122
|
+
|
|
123
|
+
return event;
|
|
127
124
|
}
|
|
128
125
|
|
|
129
126
|
if (Buffer.isBuffer(data) && !tracker.getData(data)) {
|
|
130
|
-
const
|
|
127
|
+
const inputPolicy = requestPolicy.getInputPolicy(InputType.BODY);
|
|
128
|
+
const track = !!inputPolicy;
|
|
129
|
+
const excludedRules = inputPolicy?.constructor?.name == 'Set' ? inputPolicy : undefined;
|
|
130
|
+
|
|
131
131
|
if (!track) {
|
|
132
132
|
core.logger.debug({ inputType }, 'assess input exclusion disabled tracking');
|
|
133
133
|
return;
|
|
@@ -135,6 +135,7 @@ module.exports = Core.makeComponent({
|
|
|
135
135
|
|
|
136
136
|
const event = createEvent({ pathName: 'body', value: data, fieldName: '', excludedRules });
|
|
137
137
|
if (event) {
|
|
138
|
+
if (onEvent) onEvent(event);
|
|
138
139
|
tracker.track(data, event);
|
|
139
140
|
}
|
|
140
141
|
|
|
@@ -149,7 +150,10 @@ module.exports = Core.makeComponent({
|
|
|
149
150
|
return true;
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
const
|
|
153
|
+
const inputPolicy = sourceContext.policy.getInputPolicy(inputType, fieldName);
|
|
154
|
+
const track = !!inputPolicy;
|
|
155
|
+
const excludedRules = inputPolicy?.constructor?.name == 'Set' ? inputPolicy : undefined;
|
|
156
|
+
|
|
153
157
|
if (!track) {
|
|
154
158
|
core.logger.debug({ fieldName, inputType }, 'assess input exclusion disabling tracking');
|
|
155
159
|
return;
|
|
@@ -29,12 +29,14 @@ module.exports = function (core) {
|
|
|
29
29
|
require('./install/body-parser')(core);
|
|
30
30
|
require('./install/busboy')(core);
|
|
31
31
|
require('./install/cookie-parser1')(core);
|
|
32
|
+
core.initComponentSync(require('./install/fastify-websocket'));
|
|
32
33
|
require('./install/formidable1')(core);
|
|
33
34
|
require('./install/graphql-http')(core);
|
|
34
35
|
require('./install/http')(core);
|
|
35
36
|
require('./install/qs6')(core);
|
|
36
37
|
require('./install/querystring')(core);
|
|
37
38
|
require('./install/multer1')(core);
|
|
39
|
+
core.initComponentSync(require('./install/socket.io'));
|
|
38
40
|
|
|
39
41
|
sources.install = function install() {
|
|
40
42
|
callChildComponentMethodsSync(sources, 'install');
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { InputType, set } = require('@contrast/common');
|
|
4
|
+
const Core = require('@contrast/core/lib/ioc/core');
|
|
5
|
+
const { patchType } = require('../common');
|
|
6
|
+
|
|
7
|
+
const COMPONENT_NAME = 'assess.dataflow.sources.fastifyWebsocketInstrumentation';
|
|
8
|
+
|
|
9
|
+
module.exports = Core.makeComponent({
|
|
10
|
+
name: COMPONENT_NAME,
|
|
11
|
+
factory: (core) => new FastifyWebsocketAssessSource(core),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
class FastifyWebsocketAssessSource {
|
|
15
|
+
constructor(core) {
|
|
16
|
+
Object.defineProperty(this, 'core', { value: core });
|
|
17
|
+
set(core, COMPONENT_NAME, this);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Deploys @fastify/websocket instrumentation.
|
|
22
|
+
*/
|
|
23
|
+
install() {
|
|
24
|
+
const {
|
|
25
|
+
depHooks,
|
|
26
|
+
patcher,
|
|
27
|
+
assess,
|
|
28
|
+
} = this.core;
|
|
29
|
+
|
|
30
|
+
depHooks.resolve({ name: '@fastify/websocket', version: '*' }, (fws) => {
|
|
31
|
+
// patch exported function
|
|
32
|
+
return patcher.patch(fws, {
|
|
33
|
+
name: '@fastify/websocket',
|
|
34
|
+
patchType,
|
|
35
|
+
post(data) {
|
|
36
|
+
// the plugin decorates fastify with the ws.WebSocketServer instance.
|
|
37
|
+
// we use the connection event to get reference to connecting
|
|
38
|
+
// WebSockets, and track when they emit message buffers.
|
|
39
|
+
data.args[0].websocketServer?.on?.('connection', (socket) => {
|
|
40
|
+
socket.on('message', function handler(data) {
|
|
41
|
+
const sourceContext = assess.getSourceContext();
|
|
42
|
+
// this should be present since sources run 'upgrade' requests in request scope
|
|
43
|
+
if (!sourceContext) return;
|
|
44
|
+
|
|
45
|
+
// this will track the emitted buffer
|
|
46
|
+
assess.dataflow.sources.handle({
|
|
47
|
+
data,
|
|
48
|
+
name: 'fastify-websocket',
|
|
49
|
+
inputType: InputType.WEBSOCKET,
|
|
50
|
+
stacktraceOpts: { constructorOpt: handler },
|
|
51
|
+
sourceContext,
|
|
52
|
+
onEvent(event) {
|
|
53
|
+
event.context = 'WebSocket.on("message", ...args)';
|
|
54
|
+
event.args = [{ value: 'args.0', tracked: true }];
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|