@contrast/assess 1.48.0 → 1.50.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/dataflow/propagation/install/fastify-send.js +5 -7
- package/lib/dataflow/propagation/install/send.js +4 -3
- package/lib/dataflow/propagation/install/string/concat.js +33 -15
- package/lib/dataflow/propagation/install/util-format.js +39 -13
- package/lib/dataflow/sources/handler.js +143 -139
- package/lib/dataflow/sources/index.js +1 -2
- package/lib/dataflow/tag-utils.js +3 -3
- package/lib/event-factory.js +189 -293
- package/lib/index.js +2 -2
- package/lib/make-source-context.js +64 -59
- package/package.json +11 -11
|
@@ -21,7 +21,6 @@ module.exports = function (core) {
|
|
|
21
21
|
const {
|
|
22
22
|
depHooks,
|
|
23
23
|
patcher,
|
|
24
|
-
assess: { getPropagatorContext }
|
|
25
24
|
} = core;
|
|
26
25
|
|
|
27
26
|
return core.assess.dataflow.propagation.fastifySend = {
|
|
@@ -33,9 +32,10 @@ module.exports = function (core) {
|
|
|
33
32
|
usePerf: 'sync',
|
|
34
33
|
pre(data) {
|
|
35
34
|
const { args } = data;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
// (†) This is a minimal propagator that just untracks the argument. There are
|
|
36
|
+
// no propagation events generated and no expensive tag range computations. We
|
|
37
|
+
// don't get the propagator context before proceeding since it could lead to false
|
|
38
|
+
// positives e.g. if the number of propagation events exceeds the configured limit.
|
|
39
39
|
const untrackedPath = StringPrototypeSlice.call(` ${args[0]}`, 1);
|
|
40
40
|
args[0] = untrackedPath;
|
|
41
41
|
},
|
|
@@ -49,9 +49,7 @@ module.exports = function (core) {
|
|
|
49
49
|
usePerf: 'sync',
|
|
50
50
|
pre(data) {
|
|
51
51
|
const { args } = data;
|
|
52
|
-
|
|
53
|
-
if (!getPropagatorContext()) return;
|
|
54
|
-
|
|
52
|
+
// (†)
|
|
55
53
|
const untrackedPath = StringPrototypeSlice.call(` ${args[1]}`, 1);
|
|
56
54
|
args[1] = untrackedPath;
|
|
57
55
|
},
|
|
@@ -21,7 +21,6 @@ module.exports = function (core) {
|
|
|
21
21
|
const {
|
|
22
22
|
depHooks,
|
|
23
23
|
patcher,
|
|
24
|
-
assess: { getPropagatorContext }
|
|
25
24
|
} = core;
|
|
26
25
|
|
|
27
26
|
const send = {};
|
|
@@ -38,8 +37,10 @@ module.exports = function (core) {
|
|
|
38
37
|
patchType,
|
|
39
38
|
pre(data) {
|
|
40
39
|
const { args } = data;
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
// This is a minimal propagator that just untracks the argument. There are
|
|
41
|
+
// no propagation events generated and no expensive tag range computations. We
|
|
42
|
+
// don't get the propagator context before proceeding since it could lead to false
|
|
43
|
+
// positives e.g. if the number of propagation events exceeds the configured limit.
|
|
43
44
|
const untrackedPath = StringPrototypeSlice.call(` ${args[0]}`, 1);
|
|
44
45
|
args[0] = untrackedPath;
|
|
45
46
|
},
|
|
@@ -32,30 +32,48 @@ module.exports = function(core) {
|
|
|
32
32
|
return core.assess.dataflow.propagation.stringInstrumentation.concat = {
|
|
33
33
|
install() {
|
|
34
34
|
const name = 'String.prototype.concat';
|
|
35
|
+
const store = { name: `${name}:${patchType}`, lock: true };
|
|
35
36
|
|
|
36
37
|
patcher.patch(String.prototype, 'concat', {
|
|
37
38
|
name,
|
|
38
39
|
patchType,
|
|
39
40
|
usePerf: 'sync',
|
|
41
|
+
around(next, data) {
|
|
42
|
+
let hasArray = false;
|
|
43
|
+
for (let i = 0; i < data.args.length; i++) {
|
|
44
|
+
if (Array.isArray(data.args[i])) {
|
|
45
|
+
hasArray = true;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// prevent propagation from happening in original concat method
|
|
51
|
+
// if arg is Array, since arg.join will be called on it.
|
|
52
|
+
if (hasArray && !core.scopes.instrumentation.isLocked())
|
|
53
|
+
return core.scopes.instrumentation.run(store, next);
|
|
54
|
+
|
|
55
|
+
return next();
|
|
56
|
+
},
|
|
40
57
|
post(data) {
|
|
41
|
-
|
|
42
|
-
if (!result || !getPropagatorContext()) return;
|
|
58
|
+
if (!data.result || !getPropagatorContext()) return;
|
|
43
59
|
|
|
44
|
-
const rInfo = tracker.getData(result);
|
|
60
|
+
const rInfo = tracker.getData(data.result);
|
|
45
61
|
if (rInfo) {
|
|
46
62
|
// this may happen w/ trackedStr.concat('') => trackedStr
|
|
47
63
|
return;
|
|
48
64
|
}
|
|
49
65
|
|
|
50
|
-
const objInfo = tracker.getData(obj);
|
|
66
|
+
const objInfo = tracker.getData(data.obj);
|
|
51
67
|
const history = objInfo ? new Set([objInfo]) : new Set();
|
|
52
|
-
|
|
68
|
+
|
|
69
|
+
let globalOffset = typeof data.obj !== 'function' ? data.obj.length : 0;
|
|
53
70
|
const args = [];
|
|
54
71
|
let tags = objInfo?.tags;
|
|
55
72
|
|
|
56
73
|
for (const arg of data.args) {
|
|
74
|
+
const isArray = Array.isArray(arg);
|
|
75
|
+
// TODO NODE-3748: handle tag ranges when arg is an Array
|
|
57
76
|
const strInfo = tracker.getData(arg);
|
|
58
|
-
|
|
59
77
|
if (strInfo) {
|
|
60
78
|
args.push({ tracked: true, value: arg });
|
|
61
79
|
history.add(strInfo);
|
|
@@ -64,12 +82,12 @@ module.exports = function(core) {
|
|
|
64
82
|
args.push({ tracked: false, value: getAdjustedUntrackedValue(arg) });
|
|
65
83
|
}
|
|
66
84
|
|
|
67
|
-
|
|
85
|
+
// if arg is an Array, then `${arg}` causes arg.join to get called and cause unwanted propagation
|
|
86
|
+
globalOffset += isArray ? ArrayPrototypeJoin.call(arg).length : `${arg}`.length;
|
|
68
87
|
}
|
|
69
88
|
|
|
70
|
-
|
|
71
89
|
if (history.size) {
|
|
72
|
-
const objVal = objInfo ? `'${objInfo.value}'` : getAdjustedUntrackedValue(obj);
|
|
90
|
+
const objVal = objInfo ? `'${objInfo.value}'` : getAdjustedUntrackedValue(data.obj);
|
|
73
91
|
const context = `${objVal}.concat(${ArrayPrototypeJoin.call(args.map((a) => a.value))})`;
|
|
74
92
|
const event = createPropagationEvent({
|
|
75
93
|
name,
|
|
@@ -77,11 +95,11 @@ module.exports = function(core) {
|
|
|
77
95
|
methodName: 'prototype.concat',
|
|
78
96
|
context,
|
|
79
97
|
object: {
|
|
80
|
-
value: objInfo?.value ?? getAdjustedUntrackedValue(obj),
|
|
98
|
+
value: objInfo?.value ?? getAdjustedUntrackedValue(data.obj),
|
|
81
99
|
tracked: !!objInfo
|
|
82
100
|
},
|
|
83
101
|
result: {
|
|
84
|
-
value: result,
|
|
102
|
+
value: data.result,
|
|
85
103
|
tracked: true
|
|
86
104
|
},
|
|
87
105
|
args,
|
|
@@ -90,19 +108,19 @@ module.exports = function(core) {
|
|
|
90
108
|
source: objInfo ? (history.size > 1 ? 'A' : 'O') : 'P',
|
|
91
109
|
target: 'R',
|
|
92
110
|
stacktraceOpts: {
|
|
93
|
-
constructorOpt: hooked,
|
|
94
|
-
prependFrames: [orig]
|
|
111
|
+
constructorOpt: data.hooked,
|
|
112
|
+
prependFrames: [data.orig]
|
|
95
113
|
},
|
|
96
114
|
});
|
|
97
115
|
|
|
98
116
|
if (!event) return;
|
|
99
|
-
const { extern } = tracker.track(result, event);
|
|
117
|
+
const { extern } = tracker.track(data.result, event);
|
|
100
118
|
|
|
101
119
|
if (extern) {
|
|
102
120
|
data.result = extern;
|
|
103
121
|
}
|
|
104
122
|
}
|
|
105
|
-
}
|
|
123
|
+
}
|
|
106
124
|
});
|
|
107
125
|
},
|
|
108
126
|
uninstall() {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const { isString, primordials: { StringPrototypeMatch
|
|
18
|
+
const { isString, primordials: { StringPrototypeMatch } } = require('@contrast/common');
|
|
19
19
|
const { createAppendTags } = require('../../tag-utils');
|
|
20
20
|
const { patchType } = require('../common');
|
|
21
21
|
|
|
@@ -47,7 +47,7 @@ module.exports = function(core) {
|
|
|
47
47
|
let newTags = {};
|
|
48
48
|
const history = [];
|
|
49
49
|
const eventArgs = [];
|
|
50
|
-
const formatChars = args[0].includes('%') ? StringPrototypeMatch.call(args[0],
|
|
50
|
+
const formatChars = args[0].includes('%') ? StringPrototypeMatch.call(args[0], /%[sdifjoOc]/g).map((x) => x[1]) : [];
|
|
51
51
|
let i = 0;
|
|
52
52
|
|
|
53
53
|
if (formatChars.length > 0) {
|
|
@@ -58,14 +58,44 @@ module.exports = function(core) {
|
|
|
58
58
|
for (i; i < args.length; i++) {
|
|
59
59
|
let arg = args[i];
|
|
60
60
|
const formatChar = formatChars[i - 1];
|
|
61
|
-
if (formatChar
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
if (formatChar) {
|
|
62
|
+
switch (formatChar) {
|
|
63
|
+
case 's':
|
|
64
|
+
if (typeof arg === 'object') {
|
|
65
|
+
// util.inspect instrumentation NYI
|
|
66
|
+
arg = arg?.toString ? arg.toString() : util.inspect(arg, { depth: 0, colors: false, compact: 3 });
|
|
67
|
+
} else {
|
|
68
|
+
arg = String(arg);
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
case 'd':
|
|
72
|
+
case 'i':
|
|
73
|
+
case 'f':
|
|
74
|
+
// won't be tracked
|
|
75
|
+
break;
|
|
76
|
+
case 'j':
|
|
77
|
+
arg = JSON.stringify(arg) ?? 'undefined';
|
|
78
|
+
break;
|
|
79
|
+
case 'o':
|
|
80
|
+
// util.inspect instrumentation NYI
|
|
81
|
+
arg = util.inspect(arg, { showHidden: true, showProxy: true });
|
|
82
|
+
break;
|
|
83
|
+
case 'O':
|
|
84
|
+
// util.inspect instrumentation NYI
|
|
85
|
+
arg = util.inspect(arg);
|
|
86
|
+
break;
|
|
87
|
+
case 'c':
|
|
88
|
+
// c is ignored and skipped
|
|
89
|
+
arg = '';
|
|
90
|
+
break;
|
|
67
91
|
}
|
|
92
|
+
} else if (typeof arg !== 'string') {
|
|
93
|
+
arg = util.inspect(arg);
|
|
68
94
|
}
|
|
95
|
+
|
|
96
|
+
const argInfo = tracker.getData(arg);
|
|
97
|
+
if (!argInfo) continue;
|
|
98
|
+
|
|
69
99
|
const currIdx = result.indexOf(arg, idx);
|
|
70
100
|
if (currIdx > -1) {
|
|
71
101
|
idx = currIdx + arg.length;
|
|
@@ -73,9 +103,6 @@ module.exports = function(core) {
|
|
|
73
103
|
continue;
|
|
74
104
|
}
|
|
75
105
|
|
|
76
|
-
const argInfo = tracker.getData(arg);
|
|
77
|
-
if (!argInfo) continue;
|
|
78
|
-
|
|
79
106
|
newTags = createAppendTags(newTags, argInfo.tags, currIdx);
|
|
80
107
|
|
|
81
108
|
history.push({ ...argInfo });
|
|
@@ -120,8 +147,7 @@ module.exports = function(core) {
|
|
|
120
147
|
}
|
|
121
148
|
}
|
|
122
149
|
});
|
|
123
|
-
}
|
|
124
|
-
);
|
|
150
|
+
});
|
|
125
151
|
},
|
|
126
152
|
};
|
|
127
153
|
};
|
|
@@ -24,178 +24,182 @@ const {
|
|
|
24
24
|
StringPrototypeToLowerCase
|
|
25
25
|
}
|
|
26
26
|
} = require('@contrast/common');
|
|
27
|
+
const { Core } = require('@contrast/core/lib/ioc/core');
|
|
28
|
+
|
|
29
|
+
module.exports = Core.makeComponent({
|
|
30
|
+
name: 'assess.dataflow.sources.handle',
|
|
31
|
+
factory(core) {
|
|
32
|
+
const {
|
|
33
|
+
assess: {
|
|
34
|
+
eventFactory,
|
|
35
|
+
dataflow: {
|
|
36
|
+
sources,
|
|
37
|
+
tracker
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
config,
|
|
41
|
+
createSnapshot,
|
|
42
|
+
} = core;
|
|
27
43
|
|
|
28
|
-
|
|
29
|
-
const {
|
|
30
|
-
assess: {
|
|
31
|
-
eventFactory,
|
|
32
|
-
dataflow: {
|
|
33
|
-
sources,
|
|
34
|
-
tracker
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
config,
|
|
38
|
-
createSnapshot,
|
|
39
|
-
} = core;
|
|
40
|
-
|
|
41
|
-
const logger = core.logger.child({ name: 'contrast:sources' });
|
|
44
|
+
const logger = core.logger.child({ name: 'contrast:sources' });
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
const emptyStack = Object.freeze([]);
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
sources.createTags = function createTags({ inputType, fieldName = '', value, tagNames }) {
|
|
49
|
+
if (!value?.length) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
const stop = value.length - 1;
|
|
54
|
+
const tags = {
|
|
55
|
+
[DataflowTag.UNTRUSTED]: [0, stop]
|
|
56
|
+
};
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
if (tagNames) {
|
|
59
|
+
for (const tag of tagNames) {
|
|
60
|
+
tags[tag] = [0, stop];
|
|
61
|
+
}
|
|
58
62
|
}
|
|
59
|
-
}
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
if (inputType === InputType.HEADER && StringPrototypeToLowerCase.call(fieldName) === 'referer') {
|
|
65
|
+
tags[DataflowTag.HEADER] = [0, stop];
|
|
66
|
+
}
|
|
64
67
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
sources.createStacktrace = function (stacktraceOpts) {
|
|
69
|
-
return config.assess.stacktraces === 'NONE' || config.assess.stacktraces === 'SINK'
|
|
70
|
-
? emptyStack
|
|
71
|
-
: createSnapshot(stacktraceOpts)();
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
sources.handle = function ({
|
|
75
|
-
context,
|
|
76
|
-
keys,
|
|
77
|
-
name,
|
|
78
|
-
inputType = InputType.UNKNOWN,
|
|
79
|
-
stacktraceOpts,
|
|
80
|
-
data,
|
|
81
|
-
sourceContext,
|
|
82
|
-
}) {
|
|
83
|
-
if (!data) return;
|
|
84
|
-
|
|
85
|
-
if (!sourceContext) {
|
|
86
|
-
logger.trace({ inputType, sourceName: name }, 'skipping assess source handling - no request context');
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
68
|
+
return tags;
|
|
69
|
+
};
|
|
89
70
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
71
|
+
sources.createStacktrace = function(stacktraceOpts) {
|
|
72
|
+
return config.assess.stacktraces === 'NONE' || config.assess.stacktraces === 'SINK'
|
|
73
|
+
? emptyStack
|
|
74
|
+
: createSnapshot(stacktraceOpts)();
|
|
75
|
+
};
|
|
94
76
|
|
|
95
|
-
|
|
96
|
-
context
|
|
97
|
-
|
|
77
|
+
sources.handle = function({
|
|
78
|
+
context,
|
|
79
|
+
keys,
|
|
80
|
+
name,
|
|
81
|
+
inputType = InputType.UNKNOWN,
|
|
82
|
+
stacktraceOpts,
|
|
83
|
+
data,
|
|
84
|
+
sourceContext,
|
|
85
|
+
}) {
|
|
86
|
+
if (!data) return;
|
|
87
|
+
|
|
88
|
+
if (!sourceContext) {
|
|
89
|
+
logger.trace({ inputType, sourceName: name }, 'skipping assess source handling - no request context');
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
98
92
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
93
|
+
// url exclusion
|
|
94
|
+
if (!sourceContext.policy) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
103
97
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
for (const key of keys) {
|
|
107
|
-
_data[key] = data[key];
|
|
98
|
+
if (!context) {
|
|
99
|
+
context = inputType;
|
|
108
100
|
}
|
|
109
|
-
}
|
|
110
101
|
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return eventFactory.createSourceEvent({
|
|
116
|
-
context: `${context}.${pathName}`,
|
|
117
|
-
name,
|
|
118
|
-
fieldName,
|
|
119
|
-
pathName,
|
|
120
|
-
stack,
|
|
121
|
-
inputType,
|
|
122
|
-
tags: sources.createTags({ inputType, fieldName, value, tagNames }),
|
|
123
|
-
result: { tracked: true, value },
|
|
124
|
-
});
|
|
125
|
-
}
|
|
102
|
+
const { policy: requestPolicy } = sourceContext;
|
|
103
|
+
const max = config.assess.max_context_source_events;
|
|
104
|
+
let _data = data;
|
|
105
|
+
let stack;
|
|
126
106
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
107
|
+
if (keys) {
|
|
108
|
+
_data = {};
|
|
109
|
+
for (const key of keys) {
|
|
110
|
+
_data[key] = data[key];
|
|
111
|
+
}
|
|
132
112
|
}
|
|
133
113
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
114
|
+
function createEvent({ fieldName, pathName, value, excludedRules }) {
|
|
115
|
+
const tagNames = Array.from(excludedRules).map((ruleId) => `excluded:${ruleId}`);
|
|
116
|
+
// create the stacktrace once per call to .handle()
|
|
117
|
+
stack || (stack = sources.createStacktrace(stacktraceOpts));
|
|
118
|
+
return eventFactory.createSourceEvent({
|
|
119
|
+
context: `${context}.${pathName}`,
|
|
120
|
+
name,
|
|
121
|
+
fieldName,
|
|
122
|
+
pathName,
|
|
123
|
+
stack,
|
|
124
|
+
inputType,
|
|
125
|
+
tags: sources.createTags({ inputType, fieldName, value, tagNames }),
|
|
126
|
+
result: { tracked: true, value },
|
|
127
|
+
});
|
|
137
128
|
}
|
|
138
129
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
130
|
+
if (Buffer.isBuffer(data) && !tracker.getData(data)) {
|
|
131
|
+
const { track, excludedRules } = requestPolicy.getInputPolicy(InputType.BODY);
|
|
132
|
+
if (!track) {
|
|
133
|
+
core.logger.debug({ inputType }, 'assess input exclusion disabled tracking');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
144
136
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
137
|
+
const event = createEvent({ pathName: 'body', value: data, fieldName: '', excludedRules });
|
|
138
|
+
if (event) {
|
|
139
|
+
tracker.track(data, event);
|
|
140
|
+
}
|
|
149
141
|
|
|
150
|
-
const { track, excludedRules } = sourceContext.policy.getInputPolicy(inputType, fieldName);
|
|
151
|
-
if (!track) {
|
|
152
|
-
core.logger.debug({ fieldName, inputType }, 'assess input exclusion disabling tracking');
|
|
153
142
|
return;
|
|
154
143
|
}
|
|
155
144
|
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// alternatively we could treat this more like a propagation event and update existing metadata.
|
|
163
|
-
value = strInfo.value;
|
|
145
|
+
traverse(_data, (path, fieldName, value, obj) => {
|
|
146
|
+
const pathName = ArrayPrototypeJoin.call(path, '.');
|
|
147
|
+
|
|
148
|
+
if (sourceContext.sourceEventsCount >= max) {
|
|
149
|
+
logger.trace({ inputType, sourceName: name }, 'exiting assess source handling - %s max events exceeded', max);
|
|
150
|
+
return true;
|
|
164
151
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
152
|
+
|
|
153
|
+
const { track, excludedRules } = sourceContext.policy.getInputPolicy(inputType, fieldName);
|
|
154
|
+
if (!track) {
|
|
155
|
+
core.logger.debug({ fieldName, inputType }, 'assess input exclusion disabling tracking');
|
|
168
156
|
return;
|
|
169
157
|
}
|
|
170
158
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
159
|
+
if (isString(value) && value.length) {
|
|
160
|
+
const strInfo = tracker.getData(value);
|
|
161
|
+
if (strInfo) {
|
|
162
|
+
// TODO: confirm this "layering-on" approach is what we want
|
|
163
|
+
// when a value is already tracked, the handler will "re-track" the value with new source
|
|
164
|
+
// event metadata. without this step tracker would complain about value already being tracked.
|
|
165
|
+
// alternatively we could treat this more like a propagation event and update existing metadata.
|
|
166
|
+
value = strInfo.value;
|
|
167
|
+
}
|
|
168
|
+
const event = createEvent({ pathName, value, fieldName, excludedRules });
|
|
169
|
+
if (!event) {
|
|
170
|
+
logger.warn({ inputType, sourceName: name, pathName, value }, 'unable to create source event');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const { extern } = tracker.track(value, event);
|
|
175
|
+
if (extern) {
|
|
176
|
+
logger.trace({ extern, fieldName, sourceName: name, inputType }, 'tracked');
|
|
177
|
+
obj[fieldName] = extern;
|
|
178
|
+
|
|
179
|
+
sourceContext.sourceEventsCount++;
|
|
180
|
+
}
|
|
181
|
+
} else if (Buffer.isBuffer(value) && !tracker.getData(value)) {
|
|
182
|
+
const event = createEvent({ pathName, value, fieldName, excludedRules });
|
|
183
|
+
if (event) {
|
|
184
|
+
tracker.track(value, event);
|
|
185
|
+
} else {
|
|
186
|
+
logger.warn({ inputType, sourceName: name, pathName, value }, 'unable to create source event');
|
|
187
|
+
}
|
|
177
188
|
}
|
|
178
|
-
}
|
|
179
|
-
const event = createEvent({ pathName, value, fieldName, excludedRules });
|
|
180
|
-
if (event) {
|
|
181
|
-
tracker.track(value, event);
|
|
182
|
-
} else {
|
|
183
|
-
logger.warn({ inputType, sourceName: name, pathName, value }, 'unable to create source event');
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
});
|
|
189
|
+
});
|
|
187
190
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
+
if (keys) {
|
|
192
|
+
for (const key of keys) {
|
|
193
|
+
data[key] = _data[key];
|
|
194
|
+
}
|
|
191
195
|
}
|
|
192
|
-
}
|
|
193
196
|
|
|
194
|
-
|
|
195
|
-
|
|
197
|
+
return data;
|
|
198
|
+
};
|
|
196
199
|
|
|
197
|
-
|
|
198
|
-
}
|
|
200
|
+
return sources;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
199
203
|
|
|
200
204
|
/**
|
|
201
205
|
* A custom traversal function for handling source value tracking efficiently.
|
|
@@ -20,8 +20,7 @@ const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
|
20
20
|
module.exports = function (core) {
|
|
21
21
|
const sources = core.assess.dataflow.sources = {};
|
|
22
22
|
|
|
23
|
-
require('./handler')
|
|
24
|
-
|
|
23
|
+
core.initComponentSync(require('./handler'));
|
|
25
24
|
require('./install/express')(core);
|
|
26
25
|
require('./install/fastify')(core);
|
|
27
26
|
require('./install/hapi')(core);
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const { primordials: { StringPrototypeSplit } } = require('@contrast/common');
|
|
17
|
+
const { empties, primordials: { StringPrototypeSplit } } = require('@contrast/common');
|
|
18
18
|
|
|
19
19
|
//
|
|
20
20
|
// This module implements tag range manipulation functions. There are generally
|
|
@@ -295,7 +295,7 @@ function createMergedTags(firstTags, secondTags) {
|
|
|
295
295
|
|
|
296
296
|
function mergeCore(firstTagRanges, secondTagRanges) {
|
|
297
297
|
if (!firstTagRanges && !secondTagRanges) {
|
|
298
|
-
return
|
|
298
|
+
return empties.ARRAY;
|
|
299
299
|
} else if (!firstTagRanges) {
|
|
300
300
|
return [...secondTagRanges];
|
|
301
301
|
} else if (!secondTagRanges) {
|
|
@@ -463,7 +463,7 @@ function createAdjustedQueryTags(path, tags, value, argString) {
|
|
|
463
463
|
break;
|
|
464
464
|
}
|
|
465
465
|
}
|
|
466
|
-
return idx >= 0 ? createAppendTags(
|
|
466
|
+
return idx >= 0 ? createAppendTags(empties.OBJECT, tags, idx) : { ...tags };
|
|
467
467
|
}
|
|
468
468
|
|
|
469
469
|
/**
|
package/lib/event-factory.js
CHANGED
|
@@ -15,309 +15,205 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const { InputType, primordials: { StringPrototypeMatch } } = require('@contrast/common');
|
|
18
|
+
const { InputType, empties, primordials: { StringPrototypeMatch } } = require('@contrast/common');
|
|
19
|
+
const { Core } = require('@contrast/core/lib/ioc/core');
|
|
19
20
|
const ANNOTATION_REGEX = /^(A|O|R|P|P\d+)$/;
|
|
20
21
|
const SOURCE_EVENT_MSG = 'Source event not created: %s';
|
|
21
22
|
const PROPAGATION_EVENT_MSG = 'Propagation event not created: %s';
|
|
22
23
|
|
|
23
|
-
module.exports =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
config,
|
|
27
|
-
logger,
|
|
28
|
-
scopes: { sources },
|
|
29
|
-
} = core;
|
|
30
|
-
|
|
31
|
-
const eventFactory = core.assess.eventFactory = {};
|
|
32
|
-
|
|
33
|
-
eventFactory.createdEvents = new WeakSet();
|
|
34
|
-
|
|
35
|
-
eventFactory.createSourceEvent = function (data = {}) {
|
|
24
|
+
module.exports = Core.makeComponent({
|
|
25
|
+
name: 'assess.eventFactory',
|
|
26
|
+
factory(core) {
|
|
36
27
|
const {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return data;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
eventFactory.createPropagationEvent = function (data) {
|
|
77
|
-
const {
|
|
78
|
-
name = '',
|
|
79
|
-
moduleName,
|
|
80
|
-
methodName,
|
|
81
|
-
history = [],
|
|
82
|
-
object = { value: null, tracked: false },
|
|
83
|
-
args = [],
|
|
84
|
-
context,
|
|
85
|
-
result = { value: null, tracked: false },
|
|
86
|
-
tags = {},
|
|
87
|
-
addedTags = [],
|
|
88
|
-
removedTags = [],
|
|
89
|
-
source,
|
|
90
|
-
target,
|
|
91
|
-
stacktraceOpts
|
|
92
|
-
} = data;
|
|
93
|
-
|
|
94
|
-
const sourceContext = sources.getStore()?.assess;
|
|
95
|
-
|
|
96
|
-
if (!sourceContext) {
|
|
97
|
-
logger.debug({ name }, 'No sourceContext found during Propagation event creation');
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (sourceContext.propagationEventsCount >= config.assess.max_propagation_events) {
|
|
102
|
-
logger.debug({ name }, 'Maximum number of Propagation events reached. Event not created');
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (!name) {
|
|
107
|
-
logger.debug({ name }, PROPAGATION_EVENT_MSG, 'invalid name');
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (!history.length) {
|
|
112
|
-
logger.debug({ name }, PROPAGATION_EVENT_MSG, 'invalid history');
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (!source || !StringPrototypeMatch.call(source, ANNOTATION_REGEX)) {
|
|
117
|
-
logger.debug({ name }, PROPAGATION_EVENT_MSG, 'invalid source');
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (!target || !StringPrototypeMatch.call(target, ANNOTATION_REGEX)) {
|
|
122
|
-
logger.debug({ name }, PROPAGATION_EVENT_MSG, 'invalid target');
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
let stack;
|
|
127
|
-
if (config.assess.stacktraces === 'ALL') {
|
|
128
|
-
stack = createSnapshot(stacktraceOpts)();
|
|
129
|
-
} else {
|
|
130
|
-
stack = [];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const event = {
|
|
134
|
-
addedTags,
|
|
135
|
-
args,
|
|
136
|
-
context,
|
|
137
|
-
history,
|
|
138
|
-
name,
|
|
139
|
-
moduleName,
|
|
140
|
-
methodName,
|
|
141
|
-
object,
|
|
142
|
-
removedTags,
|
|
143
|
-
result,
|
|
144
|
-
source,
|
|
145
|
-
stack,
|
|
146
|
-
tags,
|
|
147
|
-
target,
|
|
148
|
-
time: Date.now(),
|
|
28
|
+
createSnapshot,
|
|
29
|
+
config,
|
|
30
|
+
logger,
|
|
31
|
+
scopes: { sources },
|
|
32
|
+
} = core;
|
|
33
|
+
|
|
34
|
+
const eventFactory = core.assess.eventFactory = {};
|
|
35
|
+
|
|
36
|
+
eventFactory.createdEvents = new WeakSet();
|
|
37
|
+
|
|
38
|
+
eventFactory.createSourceEvent = function(data = {}) {
|
|
39
|
+
if (!data.result?.value) {
|
|
40
|
+
logger.debug(SOURCE_EVENT_MSG, `invalid result: ${data.name}`);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
if (!data.name) {
|
|
44
|
+
logger.debug(SOURCE_EVENT_MSG, `invalid name: ${data.name}`);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (!(data.inputType in InputType)) {
|
|
48
|
+
logger.debug(SOURCE_EVENT_MSG, `invalid inputType: ${data.name}`);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
if (!data.tags) {
|
|
52
|
+
logger.debug(SOURCE_EVENT_MSG, `event has no tags: ${data.name}`);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
if (!data.stack || !Array.isArray(data.stack)) {
|
|
56
|
+
logger.debug(SOURCE_EVENT_MSG, `invalid stack: ${data.name}`);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
data.time = Date.now();
|
|
61
|
+
eventFactory.createdEvents.add(data);
|
|
62
|
+
|
|
63
|
+
return data;
|
|
149
64
|
};
|
|
150
65
|
|
|
151
|
-
eventFactory.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const event = {
|
|
200
|
-
args,
|
|
201
|
-
context,
|
|
202
|
-
history,
|
|
203
|
-
name,
|
|
204
|
-
moduleName,
|
|
205
|
-
methodName,
|
|
206
|
-
object,
|
|
207
|
-
result,
|
|
208
|
-
source,
|
|
209
|
-
stack,
|
|
210
|
-
tags,
|
|
211
|
-
time: Date.now(),
|
|
66
|
+
eventFactory.createPropagationEvent = function(data) {
|
|
67
|
+
const sourceContext = sources.getStore()?.assess;
|
|
68
|
+
|
|
69
|
+
if (!sourceContext) {
|
|
70
|
+
logger.debug('No sourceContext found during Propagation event creation: %s', data.name);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
if (sourceContext.propagationEventsCount >= config.assess.max_propagation_events) {
|
|
74
|
+
logger.debug('Maximum number of Propagation events reached. Event not created: %s', data.name);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
if (!data.name) {
|
|
78
|
+
logger.debug(PROPAGATION_EVENT_MSG, `invalid name (${data.name})`);
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
if (!data.history.length) {
|
|
82
|
+
logger.debug(PROPAGATION_EVENT_MSG, `invalid history (${data.name})`);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
if (!data.source || !StringPrototypeMatch.call(data.source, ANNOTATION_REGEX)) {
|
|
86
|
+
logger.debug(PROPAGATION_EVENT_MSG, `invalid source (${data.name})`);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
if (!data.target || !StringPrototypeMatch.call(data.target, ANNOTATION_REGEX)) {
|
|
90
|
+
logger.debug(PROPAGATION_EVENT_MSG, `invalid target (${data.name})`);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (config.assess.stacktraces === 'ALL') {
|
|
95
|
+
data.stack = createSnapshot(data.stacktraceOpts)();
|
|
96
|
+
} else {
|
|
97
|
+
data.stack = empties.ARRAY;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
data.args ??= empties.ARRAY;
|
|
101
|
+
data.addedTags ??= empties.ARRAY;
|
|
102
|
+
data.history ??= empties.ARRAY;
|
|
103
|
+
data.object ??= empties.UNTRACKED_VALUE_OBJ;
|
|
104
|
+
data.result ??= empties.UNTRACKED_VALUE_OBJ;
|
|
105
|
+
data.removedTags ??= empties.ARRAY;
|
|
106
|
+
data.time = Date.now();
|
|
107
|
+
|
|
108
|
+
eventFactory.createdEvents.add(data);
|
|
109
|
+
sourceContext.propagationEventsCount++;
|
|
110
|
+
|
|
111
|
+
return data;
|
|
212
112
|
};
|
|
213
113
|
|
|
214
|
-
eventFactory.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const event = {
|
|
254
|
-
args,
|
|
255
|
-
context,
|
|
256
|
-
history: [],
|
|
257
|
-
name,
|
|
258
|
-
moduleName,
|
|
259
|
-
methodName,
|
|
260
|
-
object,
|
|
261
|
-
result,
|
|
262
|
-
source,
|
|
263
|
-
stack,
|
|
264
|
-
tags: {},
|
|
265
|
-
time: Date.now(),
|
|
266
|
-
framework,
|
|
267
|
-
options,
|
|
114
|
+
eventFactory.createSinkEvent = function(data) {
|
|
115
|
+
const sourceContext = sources.getStore()?.assess;
|
|
116
|
+
|
|
117
|
+
if (!sourceContext) {
|
|
118
|
+
logger.debug('no sourceContext found during sink event creation: %s', data.name);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
if (!data.name) {
|
|
122
|
+
logger.debug('no sink event name: %s', data.name);
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
if (!data.history.length) {
|
|
126
|
+
logger.debug('empty history for sink event: %s', data.name);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
if (
|
|
130
|
+
(!data.source || !StringPrototypeMatch.call(data.source, ANNOTATION_REGEX))
|
|
131
|
+
) {
|
|
132
|
+
logger.debug('malformed or missing sink event source field: %s', data.name);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (config.assess.stacktraces !== 'NONE') {
|
|
137
|
+
data.stack = createSnapshot(data.stacktraceOpts)();
|
|
138
|
+
} else {
|
|
139
|
+
data.stack = empties.ARRAY;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
data.args ??= empties.ARRAY;
|
|
143
|
+
data.history ??= empties.ARRAY;
|
|
144
|
+
data.object ??= empties.ARRAY;
|
|
145
|
+
data.result ??= empties.UNTRACKED_VALUE_OBJ;
|
|
146
|
+
data.time = Date.now();
|
|
147
|
+
|
|
148
|
+
eventFactory.createdEvents.add(data);
|
|
149
|
+
|
|
150
|
+
return data;
|
|
268
151
|
};
|
|
269
152
|
|
|
270
|
-
eventFactory.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
logger.debug({ name }, 'no sink event name');
|
|
299
|
-
return null;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (!source || !StringPrototypeMatch.call(source, ANNOTATION_REGEX)) {
|
|
303
|
-
logger.debug({ name }, 'malformed or missing sink event source field');
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
let stack;
|
|
308
|
-
if (config.assess.stacktraces !== 'NONE') {
|
|
309
|
-
stack = createSnapshot(stacktraceOpts)();
|
|
310
|
-
} else {
|
|
311
|
-
stack = [];
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
data.stack = stack;
|
|
315
|
-
data.time = Date.now();
|
|
316
|
-
|
|
317
|
-
eventFactory.createdEvents.add(data);
|
|
153
|
+
eventFactory.createSessionEvent = function(data) {
|
|
154
|
+
if (!data.name) {
|
|
155
|
+
logger.debug('no sink event name: %s', data.name);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
if (
|
|
159
|
+
(!data.source || !StringPrototypeMatch.call(data.source, ANNOTATION_REGEX))
|
|
160
|
+
) {
|
|
161
|
+
logger.debug('malformed or missing sink event source field: %s', data.name);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
if (config.assess.stacktraces !== 'NONE') {
|
|
165
|
+
data.stack = createSnapshot(data.stacktraceOpts)();
|
|
166
|
+
} else {
|
|
167
|
+
data.stack = empties.ARRAY;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
data.args ??= empties.ARRAY;
|
|
171
|
+
data.history ??= empties.ARRAY;
|
|
172
|
+
data.obj ??= empties.UNTRACKED_VALUE_OBJ;
|
|
173
|
+
data.result ??= empties.UNTRACKED_VALUE_OBJ;
|
|
174
|
+
data.tags ??= empties.OBJECT;
|
|
175
|
+
data.time = Date.now();
|
|
176
|
+
|
|
177
|
+
eventFactory.createdEvents.add(data);
|
|
178
|
+
|
|
179
|
+
return data;
|
|
180
|
+
};
|
|
318
181
|
|
|
319
|
-
|
|
320
|
-
|
|
182
|
+
/**
|
|
183
|
+
* @param {{
|
|
184
|
+
* context: string,
|
|
185
|
+
* name: string,
|
|
186
|
+
* moduleName: string,
|
|
187
|
+
* methodName: string,
|
|
188
|
+
* object: { value: any, tracked: boolean },
|
|
189
|
+
* args: any[],
|
|
190
|
+
* result: { value: vany, tracked: boolean },
|
|
191
|
+
* source: string,
|
|
192
|
+
* stacktraceOpts: { constructorOpt?: Function},
|
|
193
|
+
* }} data
|
|
194
|
+
* @returns {any}
|
|
195
|
+
*/
|
|
196
|
+
eventFactory.createCryptoAnalysisEvent = function(data) {
|
|
197
|
+
if (!data.name) {
|
|
198
|
+
logger.debug('no sink event name: %s', data.name);
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
if (!data.source || !StringPrototypeMatch.call(data.source, ANNOTATION_REGEX)) {
|
|
202
|
+
logger.debug('malformed or missing sink event source field: %s', data.name);
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
if (config.assess.stacktraces !== 'NONE') {
|
|
206
|
+
data.stack = createSnapshot(data.stacktraceOpts)();
|
|
207
|
+
} else {
|
|
208
|
+
data.stack = empties.ARRAY;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
data.time = Date.now();
|
|
212
|
+
eventFactory.createdEvents.add(data);
|
|
213
|
+
|
|
214
|
+
return data;
|
|
215
|
+
};
|
|
321
216
|
|
|
322
|
-
|
|
323
|
-
}
|
|
217
|
+
return eventFactory;
|
|
218
|
+
}
|
|
219
|
+
});
|
package/lib/index.js
CHANGED
|
@@ -52,10 +52,10 @@ module.exports = function assess(core) {
|
|
|
52
52
|
// ancillary tools used by different features
|
|
53
53
|
require('./sampler')(core);
|
|
54
54
|
require('./get-policy')(core);
|
|
55
|
-
require('./make-source-context')
|
|
55
|
+
core.initComponentSync(require('./make-source-context'));
|
|
56
56
|
require('./rule-scopes')(core);
|
|
57
57
|
require('./get-source-context')(core);
|
|
58
|
-
require('./event-factory')
|
|
58
|
+
core.initComponentSync(require('./event-factory'));
|
|
59
59
|
|
|
60
60
|
// various Assess features
|
|
61
61
|
require('./dataflow')(core);
|
|
@@ -16,75 +16,80 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { primordials: { StringPrototypeToLowerCase, StringPrototypeSlice } } = require('@contrast/common');
|
|
19
|
+
const { Core } = require('@contrast/core/lib/ioc/core');
|
|
20
|
+
|
|
19
21
|
/**
|
|
20
22
|
* @param {{
|
|
21
23
|
* assess: import('@contrast/assess').Assess,
|
|
22
24
|
* logger: import('@contrast/logger').Logger,
|
|
23
25
|
* }} core
|
|
24
26
|
*/
|
|
25
|
-
module.exports =
|
|
26
|
-
|
|
27
|
+
module.exports = Core.makeComponent({
|
|
28
|
+
name: 'assess.makeSourceContext',
|
|
29
|
+
factory(core) {
|
|
30
|
+
const { assess, logger } = core;
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
/**
|
|
33
|
+
* @returns {import('@contrast/assess').SourceContext}
|
|
34
|
+
*/
|
|
35
|
+
return core.assess.makeSourceContext = function(sourceData) {
|
|
36
|
+
try {
|
|
37
|
+
// todo: how to handle non-HTTP sources
|
|
38
|
+
const { incomingMessage: req } = sourceData;
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
// minimally process the request data for sampling and exclusions.
|
|
41
|
+
// more request fields will be appended in final result below.
|
|
42
|
+
let uriPath;
|
|
43
|
+
let queries;
|
|
44
|
+
const idx = req.url.indexOf('?');
|
|
45
|
+
if (idx >= 0) {
|
|
46
|
+
uriPath = StringPrototypeSlice.call(req.url, 0, idx);
|
|
47
|
+
queries = StringPrototypeSlice.call(req.url, idx + 1);
|
|
48
|
+
} else {
|
|
49
|
+
uriPath = req.url;
|
|
50
|
+
queries = '';
|
|
51
|
+
}
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
const ctx = sourceData.store.assess = {
|
|
54
|
+
// default policy to `null` until it is set later below. this will cause
|
|
55
|
+
// all instrumentation to short-circuit, see `./get-source-context.js`.
|
|
56
|
+
policy: null,
|
|
57
|
+
reqData: {
|
|
58
|
+
method: req.method,
|
|
59
|
+
uriPath,
|
|
60
|
+
queries,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
// check whether sampling allows processing
|
|
65
|
+
ctx.sampleInfo = assess.sampler?.getSampleInfo(sourceData) ?? null;
|
|
66
|
+
if (ctx.sampleInfo?.canSample === false) return ctx;
|
|
63
67
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
// set policy - can be returned as `null` if request is url-excluded.
|
|
69
|
+
ctx.policy = assess.getPolicy(ctx.reqData);
|
|
70
|
+
if (!ctx.policy) return ctx;
|
|
67
71
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
// build remaining reqData
|
|
73
|
+
ctx.reqData.headers = { ...req.headers }; // copy to avoid storing tracked values
|
|
74
|
+
ctx.reqData.ip = req.socket.remoteAddress;
|
|
75
|
+
ctx.reqData.httpVersion = req.httpVersion;
|
|
76
|
+
if (ctx.reqData.headers['content-type'])
|
|
77
|
+
ctx.reqData.contentType = StringPrototypeToLowerCase.call(ctx.reqData.headers['content-type']);
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
79
|
+
return {
|
|
80
|
+
...ctx,
|
|
81
|
+
propagationEventsCount: 0,
|
|
82
|
+
sourceEventsCount: 0,
|
|
83
|
+
responseData: {},
|
|
84
|
+
ruleState: {},
|
|
85
|
+
};
|
|
86
|
+
} catch (err) {
|
|
87
|
+
logger.error(
|
|
88
|
+
{ err },
|
|
89
|
+
'unable to construct assess store. assess will be disabled for request.'
|
|
90
|
+
);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/assess",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.50.0",
|
|
4
4
|
"description": "Contrast service providing framework-agnostic Assess support",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -20,17 +20,17 @@
|
|
|
20
20
|
"test": "../scripts/test.sh"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@contrast/common": "1.
|
|
24
|
-
"@contrast/config": "1.
|
|
25
|
-
"@contrast/core": "1.
|
|
26
|
-
"@contrast/dep-hooks": "1.
|
|
23
|
+
"@contrast/common": "1.32.0",
|
|
24
|
+
"@contrast/config": "1.43.0",
|
|
25
|
+
"@contrast/core": "1.48.0",
|
|
26
|
+
"@contrast/dep-hooks": "1.17.0",
|
|
27
27
|
"@contrast/distringuish": "^5.1.0",
|
|
28
|
-
"@contrast/instrumentation": "1.
|
|
29
|
-
"@contrast/logger": "1.
|
|
30
|
-
"@contrast/patcher": "1.
|
|
31
|
-
"@contrast/rewriter": "1.
|
|
32
|
-
"@contrast/route-coverage": "1.
|
|
33
|
-
"@contrast/scopes": "1.
|
|
28
|
+
"@contrast/instrumentation": "1.27.0",
|
|
29
|
+
"@contrast/logger": "1.21.0",
|
|
30
|
+
"@contrast/patcher": "1.20.0",
|
|
31
|
+
"@contrast/rewriter": "1.24.0",
|
|
32
|
+
"@contrast/route-coverage": "1.38.0",
|
|
33
|
+
"@contrast/scopes": "1.18.0",
|
|
34
34
|
"semver": "^7.6.0"
|
|
35
35
|
}
|
|
36
36
|
}
|