@contrast/assess 1.13.0 → 1.14.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.
Files changed (31) hide show
  1. package/lib/dataflow/propagation/install/buffer.js +6 -5
  2. package/lib/dataflow/propagation/install/contrast-methods/add.js +3 -0
  3. package/lib/dataflow/propagation/install/joi/any.js +46 -0
  4. package/lib/dataflow/propagation/install/joi/boolean.js +109 -0
  5. package/lib/dataflow/propagation/install/joi/expression.js +99 -0
  6. package/lib/dataflow/propagation/install/joi/index.js +145 -8
  7. package/lib/dataflow/propagation/install/joi/number.js +107 -0
  8. package/lib/dataflow/propagation/install/joi/object.js +46 -0
  9. package/lib/dataflow/propagation/install/joi/string-schema.js +15 -51
  10. package/lib/dataflow/propagation/install/joi/utils.js +111 -0
  11. package/lib/dataflow/propagation/install/joi/values.js +21 -8
  12. package/lib/dataflow/propagation/install/path/basename.js +1 -3
  13. package/lib/dataflow/propagation/install/path/join-and-resolve.js +1 -3
  14. package/lib/dataflow/propagation/install/path/normalize.js +1 -3
  15. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +7 -6
  16. package/lib/dataflow/propagation/install/string/match-all.js +6 -5
  17. package/lib/dataflow/propagation/install/string/match.js +14 -11
  18. package/lib/dataflow/propagation/install/string/replace.js +6 -5
  19. package/lib/dataflow/propagation/install/string/slice.js +3 -0
  20. package/lib/dataflow/propagation/install/string/split.js +6 -5
  21. package/lib/dataflow/propagation/install/string/substring.js +1 -3
  22. package/lib/dataflow/propagation/install/string/trim.js +2 -0
  23. package/lib/dataflow/propagation/install/url/parse.js +3 -8
  24. package/lib/dataflow/propagation/install/url/searchParams.js +7 -7
  25. package/lib/dataflow/propagation/install/validator/hooks.js +4 -4
  26. package/lib/dataflow/sinks/index.js +3 -3
  27. package/lib/dataflow/sinks/install/eval.js +63 -67
  28. package/lib/dataflow/sinks/install/function.js +87 -91
  29. package/lib/dataflow/sinks/install/vm.js +7 -7
  30. package/lib/dataflow/tracker.js +1 -3
  31. package/package.json +6 -4
@@ -18,9 +18,8 @@
18
18
  const {
19
19
  DataflowTag: { ALPHANUM_SPACE_HYPHEN, LIMITED_CHARS, STRING_TYPE_CHECKED },
20
20
  inspect,
21
- join,
22
- split,
23
21
  } = require('@contrast/common');
22
+ const { handleReferences } = require('./utils');
24
23
  const { createFullLengthCopyTags } = require('../../../tag-utils');
25
24
  const { patchType } = require('../../common');
26
25
  const VALIDATORS = {
@@ -44,7 +43,12 @@ module.exports = function(core) {
44
43
  patcher,
45
44
  assess: {
46
45
  eventFactory: { createPropagationEvent },
47
- dataflow: { tracker },
46
+ dataflow: {
47
+ tracker, propagation: {
48
+ joiInstrumentation
49
+ }
50
+ },
51
+
48
52
  },
49
53
  } = core;
50
54
 
@@ -92,18 +96,6 @@ module.exports = function(core) {
92
96
  return propagationFn;
93
97
  }
94
98
 
95
- function getRefInstancesTrackingData(obj, refInstancesPaths) {
96
- return refInstancesPaths
97
- .map((referenceInstance) => {
98
- const value = split(referenceInstance, '.').reduce(
99
- (acc, v) => acc[v] || acc,
100
- obj
101
- );
102
- return tracker.getData(value);
103
- })
104
- .filter(Boolean);
105
- }
106
-
107
99
  function patchValidator(
108
100
  validatorObj,
109
101
  patchName,
@@ -125,19 +117,6 @@ module.exports = function(core) {
125
117
  )
126
118
  return;
127
119
 
128
- const contrastData = schema?.schema?.__CONTRAST__;
129
- let refTargetPath = join(schema.state.path, '.');
130
- const inReferenceTargetPath = join(schema.state.path.slice(0, -1), '.');
131
- if (contrastData?.inReferenceTargets.has(inReferenceTargetPath)) {
132
- refTargetPath = join(
133
- schema.state.path.slice(0, -1),
134
- '.'
135
- );
136
- }
137
- const references = contrastData?.refTargets[refTargetPath];
138
-
139
- const strInfo = tracker.getData(input);
140
-
141
120
  const inspectedSchema = inspect(schema);
142
121
  const validation = definePropagation(
143
122
  validatorName,
@@ -145,27 +124,9 @@ module.exports = function(core) {
145
124
  inspectedSchema,
146
125
  data.orig
147
126
  );
148
- if (references?.length) {
149
- getRefInstancesTrackingData(
150
- schema.state.ancestors[schema.state.ancestors.length - 1],
151
- references
152
- ).forEach((refMetadata) => {
153
- let { eventualValidations } = refMetadata;
154
- if (!eventualValidations) {
155
- eventualValidations = { [refTargetPath]: [validation] };
156
- } else if (!eventualValidations[refTargetPath]) {
157
- eventualValidations[refTargetPath] = [validation];
158
- } else if (
159
- !eventualValidations[refTargetPath].find(
160
- (v) => v.name === tagName
161
- )
162
- ) {
163
- eventualValidations[refTargetPath].push(validation);
164
- }
127
+ const strInfo = tracker.getData(input);
165
128
 
166
- refMetadata.eventualValidations = eventualValidations;
167
- });
168
- }
129
+ handleReferences(tracker, schema, validation);
169
130
 
170
131
  if (strInfo) {
171
132
  validation(strInfo);
@@ -221,6 +182,8 @@ module.exports = function(core) {
221
182
  },
222
183
  });
223
184
 
185
+ if (!event) return;
186
+
224
187
  const { extern } = tracker.track(result.value, event);
225
188
 
226
189
  if (extern) {
@@ -230,7 +193,7 @@ module.exports = function(core) {
230
193
  });
231
194
  }
232
195
 
233
- return (core.assess.dataflow.propagation.joiInstrumentation.stringSchema = {
196
+ return (joiInstrumentation.stringSchema = {
234
197
  install() {
235
198
  depHooks.resolve(
236
199
  { name: 'joi', file: 'lib/types/string.js', version: '>=17.0.0' },
@@ -238,7 +201,10 @@ module.exports = function(core) {
238
201
  const stringTypePrototype = Object.getPrototypeOf(stringType);
239
202
  const definition = stringTypePrototype?._definition;
240
203
  const rules = definition?.rules || {};
204
+ const coerce = definition?.coerce;
241
205
 
206
+ stringTypePrototype && joiInstrumentation.patchValidateAsync(stringTypePrototype, 'string.external');
207
+ definition && joiInstrumentation.patchCustomValidate(definition, 'string');
242
208
  patchValidator(
243
209
  definition,
244
210
  'joi.string.validate',
@@ -257,8 +223,6 @@ module.exports = function(core) {
257
223
  }
258
224
  }
259
225
 
260
- const coerce = definition?.coerce;
261
-
262
226
  if (coerce && rules['isoDate']) {
263
227
  reTrackCoercedValue(coerce);
264
228
  }
@@ -0,0 +1,111 @@
1
+ /*
2
+ * Copyright: 2023 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const { split, DataflowTag: { CUSTOM_VALIDATED }, join } = require('@contrast/common');
19
+
20
+ function getRefInstancesTrackingData(tracker, obj, refInstancesPaths) {
21
+ return refInstancesPaths
22
+ .map((referenceInstance) => {
23
+ const value = split(referenceInstance, '.').reduce(
24
+ (acc, v) => acc[v] || acc,
25
+ obj
26
+ );
27
+ return tracker.getData(value);
28
+ })
29
+ .filter(Boolean);
30
+ }
31
+
32
+ function tagCustomValidatedString(createPropagationEvent, strInfo, metadata) {
33
+ const { inspectedSecondArg, origFn, methodName, target } = metadata;
34
+
35
+ if (!strInfo) return;
36
+
37
+ const event = createPropagationEvent({
38
+ addedTags: [CUSTOM_VALIDATED],
39
+ name: `Joi.${methodName}`,
40
+ moduleName: 'joi',
41
+ methodName,
42
+ history: [{ ...strInfo }],
43
+ object: {
44
+ tracked: false,
45
+ value: `Joi.${methodName}`,
46
+ },
47
+ args: [
48
+ { tracked: true, value: strInfo.value },
49
+ inspectedSecondArg && { tracked: false, value: inspectedSecondArg },
50
+ ].filter(Boolean),
51
+ result: {
52
+ tracked: target === 'R',
53
+ value: target === 'R' ? strInfo.value : undefined
54
+ },
55
+ source: 'P0',
56
+ tags: {
57
+ ...strInfo.tags,
58
+ [CUSTOM_VALIDATED]: [0, strInfo.value.length - 1],
59
+ },
60
+ target,
61
+ stacktraceOpts: {
62
+ prependFrames: [origFn],
63
+ },
64
+ });
65
+
66
+ if (event) {
67
+ Object.assign(strInfo, event);
68
+ }
69
+ }
70
+
71
+ function handleReferences(tracker, schema, validationFn) {
72
+ const contrastData = schema?.schema?.__CONTRAST__;
73
+ let refTargetPath = contrastData && join(schema.state.path, '.');
74
+ const inReferenceTargetPath = contrastData && join(schema.state.path.slice(0, -1), '.');
75
+ if (contrastData?.inReferenceTargets.has(inReferenceTargetPath)) {
76
+ refTargetPath = join(
77
+ schema.state.path.slice(0, -1),
78
+ '.'
79
+ );
80
+ }
81
+ const references = contrastData?.refTargets[refTargetPath];
82
+
83
+ if (references?.length) {
84
+ getRefInstancesTrackingData(
85
+ tracker,
86
+ schema.state.ancestors[schema.state.ancestors.length - 1],
87
+ references
88
+ ).forEach((refMetadata) => {
89
+ let { eventualValidations } = refMetadata;
90
+ if (!eventualValidations) {
91
+ eventualValidations = { [refTargetPath]: [validationFn] };
92
+ } else if (!eventualValidations[refTargetPath]) {
93
+ eventualValidations[refTargetPath] = [validationFn];
94
+ } else if (
95
+ !eventualValidations[refTargetPath].find(
96
+ (v) => v.name === CUSTOM_VALIDATED
97
+ )
98
+ ) {
99
+ eventualValidations[refTargetPath].push(validationFn);
100
+ }
101
+
102
+ refMetadata.eventualValidations = eventualValidations;
103
+ });
104
+ }
105
+ }
106
+
107
+ module.exports = {
108
+ getRefInstancesTrackingData,
109
+ tagCustomValidatedString,
110
+ handleReferences
111
+ };
@@ -54,20 +54,33 @@ module.exports = function(core) {
54
54
  prefs: inspect(prefs),
55
55
  orig: data.orig
56
56
  };
57
- const targetAbsolutePath = join(result.ref.absolute(state), '.');
58
57
 
59
- if (isString(value)) {
60
- validateStringValue(value, result.value, result.ref, targetAbsolutePath, metadata);
61
- } else if (isNonEmptyObject(value)) {
62
- traverseValues(value, (path, _type, v) => {
63
- validateStringValue(v, result.value, result.ref, join([...targetAbsolutePath, ...path], '.'), metadata, path);
64
- });
58
+
59
+ if (result.ref) {
60
+ const targetAbsolutePath = join(result.ref.absolute(state), '.');
61
+
62
+ if (isString(value)) {
63
+ validateStringReferenceValue(value, result.value, result.ref, targetAbsolutePath, metadata);
64
+ } else if (isNonEmptyObject(value)) {
65
+ traverseValues(value, (path, _type, v) => {
66
+ validateStringReferenceValue(v, result.value, result.ref, join([...targetAbsolutePath, ...path], '.'), metadata, path);
67
+ });
68
+ }
69
+ } else if (data.obj?._values.has(data.result?.value)) {
70
+ if (isString(value)) {
71
+ // Should we untrack the argument too?
72
+ tracker.untrack(result.value);
73
+ } else if (isNonEmptyObject(result.value)) {
74
+ traverseValues(value, (_path, _type, v) => {
75
+ tracker.untrack(v);
76
+ });
77
+ }
65
78
  }
66
79
  },
67
80
  });
68
81
  }
69
82
 
70
- function validateStringValue(value, resValue, ref, targetAbsolutePath, metadata, path = []) {
83
+ function validateStringReferenceValue(value, resValue, ref, targetAbsolutePath, metadata, path = []) {
71
84
  const strInfo = ref && tracker.getData(value);
72
85
  const resStringValue = path.reduce((acc, val) => acc[val] || acc, resValue);
73
86
 
@@ -102,9 +102,7 @@ module.exports = function(core) {
102
102
  },
103
103
  });
104
104
 
105
- if (!event) {
106
- return;
107
- }
105
+ if (!event) return;
108
106
 
109
107
  const { extern } = tracker.track(result, event);
110
108
 
@@ -118,9 +118,7 @@ module.exports = function(core) {
118
118
  },
119
119
  });
120
120
 
121
- if (!event) {
122
- return;
123
- }
121
+ if (!event) return;
124
122
 
125
123
  const { extern } = tracker.track(result, event);
126
124
 
@@ -101,9 +101,7 @@ module.exports = function(core) {
101
101
  },
102
102
  });
103
103
 
104
- if (!event) {
105
- return;
106
- }
104
+ if (!event) return;
107
105
 
108
106
  const { extern } = tracker.track(result, event);
109
107
 
@@ -45,7 +45,7 @@ module.exports = function(core) {
45
45
  context: `'${obj}'.exec('${strInfo.value}')`,
46
46
  history: [strInfo],
47
47
  object: {
48
- value: obj,
48
+ value: 'RegExp',
49
49
  tracked: false,
50
50
  },
51
51
  args: [
@@ -162,11 +162,12 @@ module.exports = function(core) {
162
162
  metadata
163
163
  });
164
164
 
165
- if (event) {
166
- const { extern } = tracker.track(res, event);
167
- if (extern) {
168
- result.groups[key] = extern;
169
- }
165
+ if (!event) return;
166
+
167
+ const { extern } = tracker.track(res, event);
168
+
169
+ if (extern) {
170
+ result.groups[key] = extern;
170
171
  }
171
172
  });
172
173
  }
@@ -195,11 +195,12 @@ module.exports = function(core) {
195
195
  });
196
196
  }
197
197
 
198
- if (event) {
199
- const { extern } = tracker.track(res, event);
200
- if (extern) {
201
- resValue.groups[key] = extern;
202
- }
198
+ if (!event) return;
199
+
200
+ const { extern } = tracker.track(res, event);
201
+
202
+ if (extern) {
203
+ resValue.groups[key] = extern;
203
204
  }
204
205
  });
205
206
  }
@@ -30,6 +30,7 @@ module.exports = function(core) {
30
30
  },
31
31
  },
32
32
  } = core;
33
+
33
34
  const name = 'String.prototype.match';
34
35
 
35
36
  function getPropagationEvent(data, res, objInfo, start) {
@@ -49,7 +50,7 @@ module.exports = function(core) {
49
50
  moduleName: 'String',
50
51
  methodName: 'prototype.match',
51
52
  context: `'${objInfo.value}'.match(${args[0].value})`,
52
- history: [objInfo],
53
+ history: [{ ...objInfo }],
53
54
  object: {
54
55
  value: objInfo.value,
55
56
  tracked: true,
@@ -131,11 +132,12 @@ module.exports = function(core) {
131
132
  event = getPropagationEvent(data, res, objInfo, start);
132
133
  }
133
134
 
134
- if (event) {
135
- const { extern } = tracker.track(res, event);
136
- if (extern) {
137
- data.result[i] = extern;
138
- }
135
+ if (!event) continue;
136
+
137
+ const { extern } = tracker.track(res, event);
138
+
139
+ if (extern) {
140
+ data.result[i] = extern;
139
141
  }
140
142
  }
141
143
  if (hasCaptureGroups && result.groups) {
@@ -153,11 +155,12 @@ module.exports = function(core) {
153
155
  event = getPropagationEvent(data, res, objInfo, start);
154
156
  }
155
157
 
156
- if (event) {
157
- const { extern } = tracker.track(res, event);
158
- if (extern) {
159
- data.result.groups[key] = extern;
160
- }
158
+ if (!event) return;
159
+
160
+ const { extern } = tracker.track(res, event);
161
+
162
+ if (extern) {
163
+ data.result.groups[key] = extern;
161
164
  }
162
165
  });
163
166
  }
@@ -199,11 +199,12 @@ module.exports = function(core) {
199
199
  target: 'R',
200
200
  });
201
201
 
202
- if (event) {
203
- const { extern } = tracker.track(result, event);
204
- if (extern) {
205
- data.result = extern;
206
- }
202
+ if (!event) return;
203
+
204
+ const { extern } = tracker.track(result, event);
205
+
206
+ if (extern) {
207
+ data.result = extern;
207
208
  }
208
209
  }
209
210
  });
@@ -98,6 +98,9 @@ module.exports = function(core) {
98
98
  prependFrames: [orig]
99
99
  }
100
100
  });
101
+
102
+ if (!event) return;
103
+
101
104
  const { extern } = tracker.track(result, event);
102
105
 
103
106
  if (extern) {
@@ -95,11 +95,12 @@ module.exports = function(core) {
95
95
  target: 'R'
96
96
  });
97
97
 
98
- if (event) {
99
- const { extern } = tracker.track(res, event);
100
- if (extern) {
101
- data.result[i] = extern;
102
- }
98
+ if (!event) continue;
99
+
100
+ const { extern } = tracker.track(res, event);
101
+
102
+ if (extern) {
103
+ data.result[i] = extern;
103
104
  }
104
105
  }
105
106
  }
@@ -108,9 +108,7 @@ module.exports = function(core) {
108
108
  target: 'R',
109
109
  });
110
110
 
111
- if (!event) {
112
- return;
113
- }
111
+ if (!event) return;
114
112
 
115
113
  const { extern } = tracker.track(result, event);
116
114
 
@@ -82,6 +82,8 @@ module.exports = function(core) {
82
82
  target: 'R'
83
83
  });
84
84
 
85
+ if (!event) return;
86
+
85
87
  const { extern } = tracker.track(result, event);
86
88
 
87
89
  if (extern) {
@@ -106,16 +106,11 @@ module.exports = function(core) {
106
106
  prependFrames: [orig]
107
107
  },
108
108
  });
109
- if (!event) return;
110
109
 
111
- if (partInfo) {
112
- Object.assign(partInfo, event);
113
- }
114
- const { extern } = partInfo || tracker.track(part, event);
110
+ if (!event) return;
115
111
 
116
- if (extern) {
117
- result[key] = extern;
118
- }
112
+ Object.assign(partInfo, event);
113
+ result[key] = substr;
119
114
  }
120
115
  } else {
121
116
  traverse(substr, url, key, 0);
@@ -90,10 +90,8 @@ module.exports = function(core) {
90
90
  if (event) Object.assign(paramInfo, event);
91
91
  }
92
92
 
93
- const trackedKey = keyInfo?.extern;
94
- const trackedParam = paramInfo?.extern;
95
- if (trackedKey) result.delete(key);
96
- result.set(trackedKey || key, trackedParam || param);
93
+ if (keyInfo) result.delete(key);
94
+ result.set(key, param);
97
95
  });
98
96
  }
99
97
 
@@ -106,11 +104,13 @@ module.exports = function(core) {
106
104
  if (!event) return;
107
105
 
108
106
  Object.assign(paramInfo, event);
109
- const { extern } = paramInfo || tracker.track(params[key], event);
107
+ let value = params[key];
110
108
 
111
- if (extern) {
112
- result.set(key, extern);
109
+ if (!paramInfo) {
110
+ ({ extern: value } = tracker.track(params[key], event));
113
111
  }
112
+
113
+ result.set(key, value);
114
114
  });
115
115
  }
116
116
 
@@ -132,13 +132,13 @@ module.exports = function(core) {
132
132
  'R'
133
133
  );
134
134
  let resultTracked = tracker.getData(data.result);
135
- if (!resultTracked) {
136
- resultTracked = tracker.track(data.result, event);
137
- if (resultTracked.extern) data.result = resultTracked.extern;
138
- }
135
+
139
136
  if (event) {
137
+ resultTracked = resultTracked || tracker.track(data.result, event);
140
138
  Object.assign(resultTracked, event);
141
139
  }
140
+
141
+ if (resultTracked.extern) data.result = resultTracked.extern;
142
142
  }
143
143
  }
144
144
  }));
@@ -20,7 +20,7 @@ const { callChildComponentMethodsSync, Event, Rule } = require('@contrast/common
20
20
  const { isVulnerable } = require('../utils/is-vulnerable');
21
21
  const { isSafeContentType } = require('../utils/is-safe-content-type');
22
22
 
23
- module.exports = function(core) {
23
+ module.exports = function (core) {
24
24
  const {
25
25
  logger,
26
26
  messages,
@@ -30,7 +30,7 @@ module.exports = function(core) {
30
30
  const sinkScopes = {
31
31
  [Rule.SQL_INJECTION]: new AsyncLocalStorage(),
32
32
  [Rule.NOSQL_INJECTION_MONGO]: new AsyncLocalStorage(),
33
- ['unsafe-code-execution']: new AsyncLocalStorage()
33
+ [Rule.UNSAFE_CODE_EXECUTION]: new AsyncLocalStorage()
34
34
  };
35
35
  const sinks = core.assess.dataflow.sinks = {
36
36
  isVulnerable,
@@ -83,7 +83,7 @@ module.exports = function(core) {
83
83
  require('./install/sqlite3')(core);
84
84
  require('./install/vm')(core);
85
85
 
86
- sinks.install = function() {
86
+ sinks.install = function () {
87
87
  callChildComponentMethodsSync(core.assess.dataflow.sinks, 'install');
88
88
  };
89
89