@contrast/assess 1.47.0 → 1.49.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,6 +15,7 @@
15
15
 
16
16
  'use strict';
17
17
 
18
+ const { isString } = require('@contrast/common');
18
19
  const { createAppendTags } = require('../../../tag-utils');
19
20
 
20
21
  module.exports = function(core) {
@@ -40,7 +41,11 @@ module.exports = function(core) {
40
41
  // first get result, then following logic acts as post-hook in patcher speak
41
42
  const result = add(...args);
42
43
 
43
- if (!result || !getPropagatorContext()) return result;
44
+ if (
45
+ !result ||
46
+ !isString(result) ||
47
+ !getPropagatorContext()
48
+ ) return result;
44
49
 
45
50
  const rInfo = tracker.getData(result);
46
51
  if (rInfo) {
@@ -21,26 +21,40 @@ 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 = {
28
27
  install() {
29
- depHooks.resolve({ name: '@fastify/send', version: '<4', file: 'lib/SendStream.js' }, (_SendStream) => {
30
- patcher.patch(_SendStream.prototype, 'sendFile', {
28
+ depHooks.resolve({ name: '@fastify/send', version: '<3', file: 'lib/SendStream.js' }, (SendStream) => {
29
+ patcher.patch(SendStream.prototype, 'sendFile', {
31
30
  name: '@fastify/send/lib/SendStream.js',
32
31
  patchType,
33
32
  usePerf: 'sync',
34
33
  pre(data) {
35
34
  const { args } = data;
36
-
37
- if (!getPropagatorContext()) return;
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
  },
42
42
  });
43
43
  });
44
+
45
+ depHooks.resolve({ name: '@fastify/send', version: '>=3 <5', file: 'lib/send.js' }, (send) => {
46
+ patcher.patch(send, 'send', {
47
+ name: '@fastify/send/lib/send.js',
48
+ patchType,
49
+ usePerf: 'sync',
50
+ pre(data) {
51
+ const { args } = data;
52
+ // (†)
53
+ const untrackedPath = StringPrototypeSlice.call(` ${args[1]}`, 1);
54
+ args[1] = untrackedPath;
55
+ },
56
+ });
57
+ });
44
58
  }
45
59
  };
46
60
  };
@@ -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
- if (!getPropagatorContext()) return;
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
- const { obj, result, hooked, orig } = data;
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
- let globalOffset = typeof obj !== 'function' ? obj.length : 0;
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
- globalOffset += `${arg}`.length;
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() {
@@ -31,7 +31,7 @@ module.exports = function (core) {
31
31
 
32
32
  const source = sources.hapiInstrumentation.hapi = {
33
33
  install() {
34
- return depHooks.resolve({ name: '@hapi/hapi', version: '>=18 <22' }, (hapi) => {
34
+ depHooks.resolve({ name: '@hapi/hapi', version: '>=18 <22' }, (hapi) => {
35
35
  ['server', 'Server'].forEach((server) => {
36
36
  patcher.patch(hapi, server, {
37
37
  name: `hapi.${server}`,
@@ -40,7 +40,7 @@ module.exports = function (core) {
40
40
 
41
41
  server.ext('onRequest', (req, h) => {
42
42
  const sourceContext = getSourceContext();
43
- if (!sourceContext) return;
43
+ if (!sourceContext) return h.continue;
44
44
 
45
45
  [
46
46
  { key: 'query', inputType: InputType.QUERYSTRING, trackedFlag: 'parsedQuery' },
@@ -70,9 +70,10 @@ module.exports = function (core) {
70
70
  });
71
71
  return h.continue;
72
72
  });
73
+
73
74
  server.ext('onPostAuth', (req, h) => {
74
75
  const sourceContext = core.scopes.sources.getStore()?.assess;
75
- if (!sourceContext) return;
76
+ if (!sourceContext) return h.continue;
76
77
 
77
78
  [
78
79
  { key: 'state', inputType: InputType.COOKIE_VALUE, trackedFlag: 'parsedCookies' },
@@ -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([], tags, idx) : { ...tags };
466
+ return idx >= 0 ? createAppendTags(empties.OBJECT, tags, idx) : { ...tags };
467
467
  }
468
468
 
469
469
  /**
@@ -15,7 +15,7 @@
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
19
  const ANNOTATION_REGEX = /^(A|O|R|P|P\d+)$/;
20
20
  const SOURCE_EVENT_MSG = 'Source event not created: %s';
21
21
  const PROPAGATION_EVENT_MSG = 'Propagation event not created: %s';
@@ -33,37 +33,24 @@ module.exports = function (core) {
33
33
  eventFactory.createdEvents = new WeakSet();
34
34
 
35
35
  eventFactory.createSourceEvent = function (data = {}) {
36
- const {
37
- name,
38
- result = { value: null, tracked: false },
39
- tags,
40
- inputType,
41
- stack,
42
- } = data;
43
-
44
- if (!result.value) {
45
- logger.debug({ name }, SOURCE_EVENT_MSG, 'invalid result');
36
+ if (!data.result?.value) {
37
+ logger.debug(SOURCE_EVENT_MSG, `invalid result: ${data.name}`);
46
38
  return null;
47
39
  }
48
-
49
- if (!name) {
50
- logger.debug({ name }, SOURCE_EVENT_MSG, 'invalid name');
40
+ if (!data.name) {
41
+ logger.debug(SOURCE_EVENT_MSG, `invalid name: ${data.name}`);
51
42
  return null;
52
43
  }
53
-
54
- if (!(inputType in InputType)) {
55
- logger.debug({ name }, SOURCE_EVENT_MSG, 'invalid inputType');
44
+ if (!(data.inputType in InputType)) {
45
+ logger.debug(SOURCE_EVENT_MSG, `invalid inputType: ${data.name}`);
56
46
  return null;
57
47
  }
58
-
59
- if (!tags) {
60
- logger.debug({ name }, SOURCE_EVENT_MSG, 'event has no tags');
48
+ if (!data.tags) {
49
+ logger.debug(SOURCE_EVENT_MSG, `event has no tags: ${data.name}`);
61
50
  return null;
62
51
  }
63
-
64
-
65
- if (!stack || !Array.isArray(stack)) {
66
- logger.debug({ name }, SOURCE_EVENT_MSG, 'invalid stack');
52
+ if (!data.stack || !Array.isArray(data.stack)) {
53
+ logger.debug(SOURCE_EVENT_MSG, `invalid stack: ${data.name}`);
67
54
  return null;
68
55
  }
69
56
 
@@ -74,205 +61,121 @@ module.exports = function (core) {
74
61
  };
75
62
 
76
63
  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
64
  const sourceContext = sources.getStore()?.assess;
95
65
 
96
66
  if (!sourceContext) {
97
- logger.debug({ name }, 'No sourceContext found during Propagation event creation');
67
+ logger.debug('No sourceContext found during Propagation event creation: %s', data.name);
98
68
  return null;
99
69
  }
100
-
101
70
  if (sourceContext.propagationEventsCount >= config.assess.max_propagation_events) {
102
- logger.debug({ name }, 'Maximum number of Propagation events reached. Event not created');
71
+ logger.debug('Maximum number of Propagation events reached. Event not created: %s', data.name);
103
72
  return null;
104
73
  }
105
-
106
- if (!name) {
107
- logger.debug({ name }, PROPAGATION_EVENT_MSG, 'invalid name');
74
+ if (!data.name) {
75
+ logger.debug(PROPAGATION_EVENT_MSG, `invalid name (${data.name})`);
108
76
  return null;
109
77
  }
110
-
111
- if (!history.length) {
112
- logger.debug({ name }, PROPAGATION_EVENT_MSG, 'invalid history');
78
+ if (!data.history.length) {
79
+ logger.debug(PROPAGATION_EVENT_MSG, `invalid history (${data.name})`);
113
80
  return null;
114
81
  }
115
-
116
- if (!source || !StringPrototypeMatch.call(source, ANNOTATION_REGEX)) {
117
- logger.debug({ name }, PROPAGATION_EVENT_MSG, 'invalid source');
82
+ if (!data.source || !StringPrototypeMatch.call(data.source, ANNOTATION_REGEX)) {
83
+ logger.debug(PROPAGATION_EVENT_MSG, `invalid source (${data.name})`);
118
84
  return null;
119
85
  }
120
-
121
- if (!target || !StringPrototypeMatch.call(target, ANNOTATION_REGEX)) {
122
- logger.debug({ name }, PROPAGATION_EVENT_MSG, 'invalid target');
86
+ if (!data.target || !StringPrototypeMatch.call(data.target, ANNOTATION_REGEX)) {
87
+ logger.debug(PROPAGATION_EVENT_MSG, `invalid target (${data.name})`);
123
88
  return null;
124
89
  }
125
90
 
126
- let stack;
127
91
  if (config.assess.stacktraces === 'ALL') {
128
- stack = createSnapshot(stacktraceOpts)();
92
+ data.stack = createSnapshot(data.stacktraceOpts)();
129
93
  } else {
130
- stack = [];
94
+ data.stack = empties.ARRAY;
131
95
  }
132
96
 
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(),
149
- };
97
+ data.args ??= empties.ARRAY;
98
+ data.addedTags ??= empties.ARRAY;
99
+ data.history ??= empties.ARRAY;
100
+ data.object ??= empties.UNTRACKED_VALUE_OBJ;
101
+ data.result ??= empties.UNTRACKED_VALUE_OBJ;
102
+ data.removedTags ??= empties.ARRAY;
103
+ data.time = Date.now();
150
104
 
151
- eventFactory.createdEvents.add(event);
105
+ eventFactory.createdEvents.add(data);
152
106
  sourceContext.propagationEventsCount++;
153
107
 
154
- return event;
108
+ return data;
155
109
  };
156
110
 
157
111
  eventFactory.createSinkEvent = function (data) {
158
- const {
159
- context,
160
- name = '',
161
- moduleName,
162
- methodName,
163
- history = [],
164
- object = { value: null, tracked: false },
165
- args = [],
166
- result = { value: null, tracked: false },
167
- tags = {},
168
- source,
169
- stacktraceOpts
170
- } = data;
171
-
172
112
  const sourceContext = sources.getStore()?.assess;
113
+
173
114
  if (!sourceContext) {
174
- logger.debug({ name }, 'no sourceContext found during sink event creation');
115
+ logger.debug('no sourceContext found during sink event creation: %s', data.name);
175
116
  return null;
176
117
  }
177
- if (!name) {
178
- logger.debug({ name }, 'no sink event name');
118
+ if (!data.name) {
119
+ logger.debug('no sink event name: %s', data.name);
179
120
  return null;
180
121
  }
181
- if (!history.length) {
182
- logger.debug({ name }, 'empty history for sink event');
122
+ if (!data.history.length) {
123
+ logger.debug('empty history for sink event: %s', data.name);
183
124
  return null;
184
125
  }
185
126
  if (
186
- (!source || !StringPrototypeMatch.call(source, ANNOTATION_REGEX))
127
+ (!data.source || !StringPrototypeMatch.call(data.source, ANNOTATION_REGEX))
187
128
  ) {
188
- logger.debug({ name }, 'malformed or missing sink event source field');
129
+ logger.debug('malformed or missing sink event source field: %s', data.name);
189
130
  return null;
190
131
  }
191
132
 
192
- let stack;
193
133
  if (config.assess.stacktraces !== 'NONE') {
194
- stack = createSnapshot(stacktraceOpts)();
134
+ data.stack = createSnapshot(data.stacktraceOpts)();
195
135
  } else {
196
- stack = [];
136
+ data.stack = empties.ARRAY;
197
137
  }
198
138
 
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(),
212
- };
139
+ data.args ??= empties.ARRAY;
140
+ data.history ??= empties.ARRAY;
141
+ data.object ??= empties.ARRAY;
142
+ data.result ??= empties.UNTRACKED_VALUE_OBJ;
143
+ data.time = Date.now();
213
144
 
214
- eventFactory.createdEvents.add(event);
145
+ eventFactory.createdEvents.add(data);
215
146
 
216
- return event;
147
+ return data;
217
148
  };
218
149
 
219
150
  eventFactory.createSessionEvent = function (data) {
220
- const {
221
- context,
222
- name = '',
223
- moduleName,
224
- methodName,
225
- object = { value: null, tracked: false },
226
- args = [],
227
- result = { value: null, tracked: false },
228
- source,
229
- stacktraceOpts,
230
- framework,
231
- options
232
- } = data;
233
-
234
- if (!name) {
235
- logger.debug({ name }, 'no sink event name');
151
+ if (!data.name) {
152
+ logger.debug('no sink event name: %s', data.name);
236
153
  return null;
237
154
  }
238
-
239
155
  if (
240
- (!source || !StringPrototypeMatch.call(source, ANNOTATION_REGEX))
156
+ (!data.source || !StringPrototypeMatch.call(data.source, ANNOTATION_REGEX))
241
157
  ) {
242
- logger.debug({ name }, 'malformed or missing sink event source field');
158
+ logger.debug('malformed or missing sink event source field: %s', data.name);
243
159
  return null;
244
160
  }
245
-
246
- let stack;
247
161
  if (config.assess.stacktraces !== 'NONE') {
248
- stack = createSnapshot(stacktraceOpts)();
162
+ data.stack = createSnapshot(data.stacktraceOpts)();
249
163
  } else {
250
- stack = [];
164
+ data.stack = empties.ARRAY;
251
165
  }
252
166
 
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,
268
- };
167
+ data.args ??= empties.ARRAY;
168
+ data.history ??= empties.ARRAY;
169
+ data.obj ??= empties.UNTRACKED_VALUE_OBJ;
170
+ data.result ??= empties.UNTRACKED_VALUE_OBJ;
171
+ data.tags ??= empties.OBJECT;
172
+ data.time = Date.now();
269
173
 
270
- eventFactory.createdEvents.add(event);
174
+ eventFactory.createdEvents.add(data);
271
175
 
272
- return event;
176
+ return data;
273
177
  };
274
178
 
275
-
276
179
  /**
277
180
  * @param {{
278
181
  * context: string,
@@ -288,32 +191,21 @@ module.exports = function (core) {
288
191
  * @returns {any}
289
192
  */
290
193
  eventFactory.createCryptoAnalysisEvent = function (data) {
291
- const {
292
- name = '',
293
- source,
294
- stacktraceOpts,
295
- } = data;
296
-
297
- if (!name) {
298
- logger.debug({ name }, 'no sink event name');
194
+ if (!data.name) {
195
+ logger.debug('no sink event name: %s', data.name);
299
196
  return null;
300
197
  }
301
-
302
- if (!source || !StringPrototypeMatch.call(source, ANNOTATION_REGEX)) {
303
- logger.debug({ name }, 'malformed or missing sink event source field');
198
+ if (!data.source || !StringPrototypeMatch.call(data.source, ANNOTATION_REGEX)) {
199
+ logger.debug('malformed or missing sink event source field: %s', data.name);
304
200
  return null;
305
201
  }
306
-
307
- let stack;
308
202
  if (config.assess.stacktraces !== 'NONE') {
309
- stack = createSnapshot(stacktraceOpts)();
203
+ data.stack = createSnapshot(data.stacktraceOpts)();
310
204
  } else {
311
- stack = [];
205
+ data.stack = empties.ARRAY;
312
206
  }
313
207
 
314
- data.stack = stack;
315
208
  data.time = Date.now();
316
-
317
209
  eventFactory.createdEvents.add(data);
318
210
 
319
211
  return data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/assess",
3
- "version": "1.47.0",
3
+ "version": "1.49.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.29.1",
24
- "@contrast/config": "1.40.2",
25
- "@contrast/core": "1.45.2",
26
- "@contrast/dep-hooks": "1.14.2",
23
+ "@contrast/common": "1.31.0",
24
+ "@contrast/config": "1.42.0",
25
+ "@contrast/core": "1.47.0",
26
+ "@contrast/dep-hooks": "1.16.0",
27
27
  "@contrast/distringuish": "^5.1.0",
28
- "@contrast/instrumentation": "1.24.2",
29
- "@contrast/logger": "1.18.2",
30
- "@contrast/patcher": "1.17.2",
31
- "@contrast/rewriter": "1.21.4",
32
- "@contrast/route-coverage": "1.35.2",
33
- "@contrast/scopes": "1.15.2",
28
+ "@contrast/instrumentation": "1.26.0",
29
+ "@contrast/logger": "1.20.0",
30
+ "@contrast/patcher": "1.19.0",
31
+ "@contrast/rewriter": "1.23.0",
32
+ "@contrast/route-coverage": "1.37.0",
33
+ "@contrast/scopes": "1.17.0",
34
34
  "semver": "^7.6.0"
35
35
  }
36
36
  }