@contrast/assess 1.50.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.
|
@@ -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
|
});
|
|
@@ -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/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
|
}
|
|
@@ -34,6 +34,17 @@ module.exports = Core.makeComponent({
|
|
|
34
34
|
*/
|
|
35
35
|
return core.assess.makeSourceContext = function(sourceData) {
|
|
36
36
|
try {
|
|
37
|
+
|
|
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
|
+
}
|
|
47
|
+
|
|
37
48
|
// todo: how to handle non-HTTP sources
|
|
38
49
|
const { incomingMessage: req } = sourceData;
|
|
39
50
|
|
|
@@ -49,16 +60,10 @@ module.exports = Core.makeComponent({
|
|
|
49
60
|
uriPath = req.url;
|
|
50
61
|
queries = '';
|
|
51
62
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
policy: null,
|
|
57
|
-
reqData: {
|
|
58
|
-
method: req.method,
|
|
59
|
-
uriPath,
|
|
60
|
-
queries,
|
|
61
|
-
},
|
|
63
|
+
ctx.reqData = {
|
|
64
|
+
method: req.method,
|
|
65
|
+
uriPath,
|
|
66
|
+
queries,
|
|
62
67
|
};
|
|
63
68
|
|
|
64
69
|
// check whether sampling allows processing
|
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)",
|
|
@@ -21,16 +21,16 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@contrast/common": "1.32.0",
|
|
24
|
-
"@contrast/config": "1.
|
|
25
|
-
"@contrast/core": "1.
|
|
26
|
-
"@contrast/dep-hooks": "1.
|
|
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
|
}
|