@contrast/assess 1.49.0 → 1.51.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/string/split.js +112 -55
- 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/sources/install/express/parsedUrl.js +3 -0
- package/lib/event-factory.js +197 -193
- package/lib/index.js +12 -3
- package/lib/make-source-context.js +66 -56
- package/package.json +11 -11
|
@@ -15,13 +15,14 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const { primordials: { ArrayPrototypeJoin } } = require('@contrast/common');
|
|
18
|
+
const { primordials: { ArrayPrototypeJoin, RegExpPrototypeExec } } = require('@contrast/common');
|
|
19
19
|
const { createSubsetTags, getAdjustedUntrackedValue } = require('../../../tag-utils');
|
|
20
20
|
const { patchType } = require('../../common');
|
|
21
21
|
|
|
22
22
|
module.exports = function(core) {
|
|
23
23
|
const {
|
|
24
24
|
patcher,
|
|
25
|
+
scopes,
|
|
25
26
|
assess: {
|
|
26
27
|
getPropagatorContext,
|
|
27
28
|
eventFactory,
|
|
@@ -32,13 +33,22 @@ module.exports = function(core) {
|
|
|
32
33
|
return core.assess.dataflow.propagation.stringInstrumentation.split = {
|
|
33
34
|
install() {
|
|
34
35
|
const name = 'String.prototype.split';
|
|
36
|
+
const store = { lock: true, name };
|
|
35
37
|
|
|
36
38
|
patcher.patch(String.prototype, 'split', {
|
|
37
39
|
name,
|
|
38
40
|
patchType,
|
|
39
41
|
usePerf: 'sync',
|
|
42
|
+
around(next, data) {
|
|
43
|
+
// prevent instrumenting call to `exec` instance method when splitter is RegExp
|
|
44
|
+
return data.args[0] instanceof RegExp && !scopes.instrumentation.isLocked() ?
|
|
45
|
+
scopes.instrumentation.run(store, next) :
|
|
46
|
+
next();
|
|
47
|
+
},
|
|
40
48
|
post(data) {
|
|
41
49
|
const { name, args: origArgs, obj, result, hooked, orig } = data;
|
|
50
|
+
const splitterIsRx = origArgs[0] instanceof RegExp;
|
|
51
|
+
|
|
42
52
|
if (
|
|
43
53
|
!obj ||
|
|
44
54
|
!result ||
|
|
@@ -46,68 +56,115 @@ module.exports = function(core) {
|
|
|
46
56
|
result.length === 0 ||
|
|
47
57
|
typeof obj !== 'string' ||
|
|
48
58
|
(origArgs.length === 1 && origArgs[0] == null) ||
|
|
49
|
-
!getPropagatorContext()
|
|
59
|
+
!getPropagatorContext() ||
|
|
60
|
+
(!splitterIsRx && origArgs[0]?.[Symbol.split])
|
|
50
61
|
) return;
|
|
51
62
|
|
|
52
63
|
const objInfo = tracker.getData(obj);
|
|
53
64
|
if (!objInfo || obj === result[0]) return;
|
|
54
65
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
moduleName: 'String',
|
|
65
|
-
methodName: 'prototype.split',
|
|
66
|
-
context: `'${objInfo.value}'.split(${ArrayPrototypeJoin.call(args.map(a => a.value))})`,
|
|
67
|
-
history: [objInfo],
|
|
68
|
-
object: {
|
|
69
|
-
value: obj,
|
|
70
|
-
tracked: true,
|
|
71
|
-
},
|
|
72
|
-
args,
|
|
73
|
-
tags: {},
|
|
74
|
-
result: {
|
|
75
|
-
value: getAdjustedUntrackedValue(result),
|
|
76
|
-
tracked: false
|
|
77
|
-
},
|
|
78
|
-
stacktraceOpts: {
|
|
79
|
-
constructorOpt: hooked,
|
|
80
|
-
prependFrames: [orig]
|
|
81
|
-
},
|
|
82
|
-
source: 'O',
|
|
83
|
-
target: 'R'
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
if (!event) return;
|
|
87
|
-
|
|
88
|
-
let idx = 0;
|
|
66
|
+
let readIdx = 0;
|
|
67
|
+
let splitterRxGlobal;
|
|
68
|
+
let sepOffset;
|
|
69
|
+
let rxMatch;
|
|
70
|
+
let _event;
|
|
71
|
+
|
|
72
|
+
// make single global regex to check for calculating sepOffsets (vs using origArgs[0])
|
|
73
|
+
if (origArgs[0] instanceof RegExp) splitterRxGlobal = new RegExp(origArgs[0], 'g');
|
|
74
|
+
|
|
89
75
|
for (let i = 0; i < result.length; i++) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
76
|
+
let tags;
|
|
77
|
+
if (result[i].length) {
|
|
78
|
+
tags = createSubsetTags(objInfo.tags, readIdx, result[i].length);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (tags) {
|
|
82
|
+
const metadata = makeEvent({
|
|
83
|
+
result: { tracked: true, value: result[i] },
|
|
84
|
+
tags: tags,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (metadata) {
|
|
88
|
+
eventFactory.createdEvents.add(metadata);
|
|
89
|
+
const { extern } = tracker.track(result[i], metadata);
|
|
90
|
+
if (extern) data.result[i] = extern;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// increment read offset from element length
|
|
95
|
+
readIdx += result[i].length;
|
|
96
|
+
|
|
97
|
+
// calculate separator offset but don't increment until
|
|
98
|
+
// we check for regex capture groups in result below
|
|
99
|
+
if (splitterRxGlobal) {
|
|
100
|
+
rxMatch = RegExpPrototypeExec.call(splitterRxGlobal, obj);
|
|
101
|
+
if (rxMatch) sepOffset = rxMatch[0].length;
|
|
102
|
+
} else {
|
|
103
|
+
sepOffset = origArgs[0].length;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// handle if any regex matches are interleaved into the result
|
|
107
|
+
if (rxMatch) {
|
|
108
|
+
let groupIdx = rxMatch.length > 1 ? 1 : 0;
|
|
109
|
+
|
|
110
|
+
while (groupIdx && groupIdx < rxMatch.length) {
|
|
111
|
+
// move to next element in result
|
|
112
|
+
i++;
|
|
113
|
+
|
|
114
|
+
const tags = createSubsetTags(objInfo.tags, readIdx, rxMatch[groupIdx].length);
|
|
115
|
+
if (tags) {
|
|
116
|
+
const metadata = makeEvent({
|
|
117
|
+
result: { tracked: true, value: result[i] },
|
|
118
|
+
tags: tags,
|
|
119
|
+
});
|
|
120
|
+
eventFactory.createdEvents.add(metadata);
|
|
121
|
+
const { extern } = tracker.track(result[i], metadata);
|
|
122
|
+
if (extern) data.result[i] = extern;
|
|
123
|
+
}
|
|
124
|
+
// increment read offset using rxMatch[groupIdx].length instead of sepOffset
|
|
125
|
+
readIdx += rxMatch[groupIdx].length;
|
|
126
|
+
groupIdx++;
|
|
109
127
|
}
|
|
128
|
+
} else {
|
|
129
|
+
readIdx += sepOffset;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Defer base event creation until needed. All events will share same
|
|
134
|
+
// common base event data that gets cached per propagator run.
|
|
135
|
+
function makeEvent(mergeData) {
|
|
136
|
+
if (!_event) {
|
|
137
|
+
const args = origArgs.map((arg) => {
|
|
138
|
+
const argInfo = tracker.getData(arg);
|
|
139
|
+
return argInfo ?
|
|
140
|
+
{ tracked: true, value: argInfo.value } :
|
|
141
|
+
{ tracked: false, value: `'${arg}'` };
|
|
142
|
+
});
|
|
143
|
+
_event = eventFactory.createPropagationEvent({
|
|
144
|
+
name,
|
|
145
|
+
moduleName: 'String',
|
|
146
|
+
methodName: 'prototype.split',
|
|
147
|
+
context: `'${objInfo.value}'.split(${ArrayPrototypeJoin.call(args.map(a => a.value))})`,
|
|
148
|
+
history: [objInfo],
|
|
149
|
+
object: {
|
|
150
|
+
value: obj,
|
|
151
|
+
tracked: true,
|
|
152
|
+
},
|
|
153
|
+
args,
|
|
154
|
+
tags: {},
|
|
155
|
+
result: {
|
|
156
|
+
value: getAdjustedUntrackedValue(result),
|
|
157
|
+
tracked: false
|
|
158
|
+
},
|
|
159
|
+
stacktraceOpts: {
|
|
160
|
+
constructorOpt: hooked,
|
|
161
|
+
prependFrames: [orig]
|
|
162
|
+
},
|
|
163
|
+
source: 'O',
|
|
164
|
+
target: 'R'
|
|
165
|
+
});
|
|
110
166
|
}
|
|
167
|
+
return !_event ? null : { ..._event, ...mergeData };
|
|
111
168
|
}
|
|
112
169
|
},
|
|
113
170
|
});
|
|
@@ -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);
|
|
@@ -74,6 +74,9 @@ module.exports = function init(core) {
|
|
|
74
74
|
name: 'express.middleware.init.expressInit',
|
|
75
75
|
patchType,
|
|
76
76
|
pre(data) {
|
|
77
|
+
// no need to patch is assess is off
|
|
78
|
+
if (!core.config.getEffectiveValue('assess.enable')) return;
|
|
79
|
+
|
|
77
80
|
patcher.patch(data.args, '2', {
|
|
78
81
|
name: 'express.middleware.init.expressInit.next',
|
|
79
82
|
patchType,
|
package/lib/event-factory.js
CHANGED
|
@@ -16,200 +16,204 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
24
|
+
module.exports = Core.makeComponent({
|
|
25
|
+
name: 'assess.eventFactory',
|
|
26
|
+
factory(core) {
|
|
27
|
+
const {
|
|
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;
|
|
64
|
+
};
|
|
65
|
+
|
|
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;
|
|
112
|
+
};
|
|
113
|
+
|
|
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;
|
|
151
|
+
};
|
|
152
|
+
|
|
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
|
+
};
|
|
181
|
+
|
|
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
|
+
};
|
|
216
|
+
|
|
217
|
+
return eventFactory;
|
|
218
|
+
}
|
|
219
|
+
});
|
package/lib/index.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
const { inspect } = require('util');
|
|
19
19
|
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
20
|
+
const { ConfigSource } = require('@contrast/config');
|
|
20
21
|
|
|
21
22
|
module.exports = function assess(core) {
|
|
22
23
|
const {
|
|
@@ -27,7 +28,15 @@ module.exports = function assess(core) {
|
|
|
27
28
|
|
|
28
29
|
const assess = core.assess = {
|
|
29
30
|
install() {
|
|
30
|
-
if
|
|
31
|
+
// only force instrumentation if assess is explicitly enabled in local config
|
|
32
|
+
const forceInstrumentation =
|
|
33
|
+
config.preinstrument &&
|
|
34
|
+
config.getEffectiveSource('assess.enable') !== ConfigSource.DEFAULT_VALUE;
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
!forceInstrumentation &&
|
|
38
|
+
!config.getEffectiveValue('assess.enable')
|
|
39
|
+
) {
|
|
31
40
|
core.logger.debug('assess is disabled, skipping installation');
|
|
32
41
|
return;
|
|
33
42
|
}
|
|
@@ -52,10 +61,10 @@ module.exports = function assess(core) {
|
|
|
52
61
|
// ancillary tools used by different features
|
|
53
62
|
require('./sampler')(core);
|
|
54
63
|
require('./get-policy')(core);
|
|
55
|
-
require('./make-source-context')
|
|
64
|
+
core.initComponentSync(require('./make-source-context'));
|
|
56
65
|
require('./rule-scopes')(core);
|
|
57
66
|
require('./get-source-context')(core);
|
|
58
|
-
require('./event-factory')
|
|
67
|
+
core.initComponentSync(require('./event-factory'));
|
|
59
68
|
|
|
60
69
|
// various Assess features
|
|
61
70
|
require('./dataflow')(core);
|
|
@@ -16,75 +16,85 @@
|
|
|
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
|
-
// todo: how to handle non-HTTP sources
|
|
34
|
-
const { incomingMessage: req } = sourceData;
|
|
32
|
+
/**
|
|
33
|
+
* @returns {import('@contrast/assess').SourceContext}
|
|
34
|
+
*/
|
|
35
|
+
return core.assess.makeSourceContext = function(sourceData) {
|
|
36
|
+
try {
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
uriPath = req.url;
|
|
46
|
-
queries = '';
|
|
47
|
-
}
|
|
38
|
+
const ctx = sourceData.store.assess = {
|
|
39
|
+
// default policy to `null` until it is set later below. this will cause
|
|
40
|
+
// all instrumentation to short-circuit, see `./get-source-context.js`.
|
|
41
|
+
policy: null,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (!core.config.getEffectiveValue('assess.enable')) {
|
|
45
|
+
return ctx;
|
|
46
|
+
}
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
// todo: how to handle non-HTTP sources
|
|
49
|
+
const { incomingMessage: req } = sourceData;
|
|
50
|
+
|
|
51
|
+
// minimally process the request data for sampling and exclusions.
|
|
52
|
+
// more request fields will be appended in final result below.
|
|
53
|
+
let uriPath;
|
|
54
|
+
let queries;
|
|
55
|
+
const idx = req.url.indexOf('?');
|
|
56
|
+
if (idx >= 0) {
|
|
57
|
+
uriPath = StringPrototypeSlice.call(req.url, 0, idx);
|
|
58
|
+
queries = StringPrototypeSlice.call(req.url, idx + 1);
|
|
59
|
+
} else {
|
|
60
|
+
uriPath = req.url;
|
|
61
|
+
queries = '';
|
|
62
|
+
}
|
|
63
|
+
ctx.reqData = {
|
|
54
64
|
method: req.method,
|
|
55
65
|
uriPath,
|
|
56
66
|
queries,
|
|
57
|
-
}
|
|
58
|
-
};
|
|
67
|
+
};
|
|
59
68
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
// check whether sampling allows processing
|
|
70
|
+
ctx.sampleInfo = assess.sampler?.getSampleInfo(sourceData) ?? null;
|
|
71
|
+
if (ctx.sampleInfo?.canSample === false) return ctx;
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
// set policy - can be returned as `null` if request is url-excluded.
|
|
74
|
+
ctx.policy = assess.getPolicy(ctx.reqData);
|
|
75
|
+
if (!ctx.policy) return ctx;
|
|
67
76
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
// build remaining reqData
|
|
78
|
+
ctx.reqData.headers = { ...req.headers }; // copy to avoid storing tracked values
|
|
79
|
+
ctx.reqData.ip = req.socket.remoteAddress;
|
|
80
|
+
ctx.reqData.httpVersion = req.httpVersion;
|
|
81
|
+
if (ctx.reqData.headers['content-type'])
|
|
82
|
+
ctx.reqData.contentType = StringPrototypeToLowerCase.call(ctx.reqData.headers['content-type']);
|
|
74
83
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
84
|
+
return {
|
|
85
|
+
...ctx,
|
|
86
|
+
propagationEventsCount: 0,
|
|
87
|
+
sourceEventsCount: 0,
|
|
88
|
+
responseData: {},
|
|
89
|
+
ruleState: {},
|
|
90
|
+
};
|
|
91
|
+
} catch (err) {
|
|
92
|
+
logger.error(
|
|
93
|
+
{ err },
|
|
94
|
+
'unable to construct assess store. assess will be disabled for request.'
|
|
95
|
+
);
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/assess",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.51.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.44.0",
|
|
25
|
+
"@contrast/core": "1.49.0",
|
|
26
|
+
"@contrast/dep-hooks": "1.18.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.28.0",
|
|
29
|
+
"@contrast/logger": "1.22.0",
|
|
30
|
+
"@contrast/patcher": "1.21.0",
|
|
31
|
+
"@contrast/rewriter": "1.25.0",
|
|
32
|
+
"@contrast/route-coverage": "1.39.0",
|
|
33
|
+
"@contrast/scopes": "1.19.0",
|
|
34
34
|
"semver": "^7.6.0"
|
|
35
35
|
}
|
|
36
36
|
}
|