@contrast/agent 4.25.1 → 4.25.3

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 (59) hide show
  1. package/lib/assess/express/sinks/xss.js +1 -1
  2. package/lib/assess/fastify/sinks/unvalidated-redirect.js +1 -1
  3. package/lib/assess/fastify/sinks/xss.js +2 -2
  4. package/lib/assess/hapi/sinks/unvalidated-redirect.js +1 -1
  5. package/lib/assess/hapi/sinks/xss.js +1 -1
  6. package/lib/assess/koa/sinks/unvalidated-redirect.js +1 -1
  7. package/lib/assess/koa/sinks/xss.js +1 -1
  8. package/lib/assess/loopback4/sinks/xss.js +1 -1
  9. package/lib/assess/membrane/deserialization-membrane.js +1 -1
  10. package/lib/assess/membrane/source-membrane.js +2 -2
  11. package/lib/assess/models/call-context.js +123 -162
  12. package/lib/assess/propagators/JSON/stringify.js +3 -2
  13. package/lib/assess/propagators/array-prototype-join.js +1 -1
  14. package/lib/assess/propagators/common.js +2 -2
  15. package/lib/assess/propagators/fastify-static/allowed-path.js +1 -1
  16. package/lib/assess/propagators/handlebars-escape-expresssion.js +1 -1
  17. package/lib/assess/propagators/joi/boolean.js +1 -1
  18. package/lib/assess/propagators/joi/number.js +1 -1
  19. package/lib/assess/propagators/joi/string-base.js +1 -1
  20. package/lib/assess/propagators/joi/string-schema.js +1 -1
  21. package/lib/assess/propagators/manager.js +1 -1
  22. package/lib/assess/propagators/mongoose/helpers.js +3 -1
  23. package/lib/assess/propagators/mongoose/string.js +1 -1
  24. package/lib/assess/propagators/number.js +1 -1
  25. package/lib/assess/propagators/path/basename.js +2 -2
  26. package/lib/assess/propagators/path/dirname.js +2 -2
  27. package/lib/assess/propagators/path/extname.js +2 -2
  28. package/lib/assess/propagators/querystring/escape.js +1 -1
  29. package/lib/assess/propagators/querystring/parse.js +2 -3
  30. package/lib/assess/propagators/querystring/stringify.js +3 -3
  31. package/lib/assess/propagators/querystring/unescape.js +2 -2
  32. package/lib/assess/propagators/sequelize/sql-string-escape.js +1 -1
  33. package/lib/assess/propagators/sequelize/sql-string-format-named-parameters.js +1 -1
  34. package/lib/assess/propagators/sequelize/sql-string-format.js +1 -1
  35. package/lib/assess/propagators/string-prototype-replace.js +10 -11
  36. package/lib/assess/propagators/string-prototype-split.js +6 -6
  37. package/lib/assess/propagators/string-prototype-trim.js +3 -3
  38. package/lib/assess/propagators/template-escape.js +2 -2
  39. package/lib/assess/propagators/templates.js +1 -1
  40. package/lib/assess/propagators/url/utils.js +1 -1
  41. package/lib/assess/propagators/v8/init-hooks.js +1 -1
  42. package/lib/assess/propagators/validator/init-hooks.js +2 -2
  43. package/lib/assess/restify/session.js +3 -1
  44. package/lib/assess/restify/sinks/unvalidated-redirect.js +1 -1
  45. package/lib/assess/restify/sinks/xss.js +1 -1
  46. package/lib/assess/sinks/common.js +2 -1
  47. package/lib/assess/sinks/dustjs-linkedin-xss.js +1 -1
  48. package/lib/assess/sinks/dynamo.js +1 -1
  49. package/lib/assess/sinks/hapi-16-xss.js +1 -1
  50. package/lib/assess/sinks/libxmljs-xxe.js +1 -1
  51. package/lib/assess/sinks/mongodb.js +2 -2
  52. package/lib/assess/sinks/rethinkdb-nosql-injection.js +2 -2
  53. package/lib/assess/sources/event-handler.js +2 -2
  54. package/lib/assess/spdy/sinks/xss.js +1 -1
  55. package/lib/core/config/options.js +3 -2
  56. package/lib/core/hapi/utils.js +3 -2
  57. package/lib/hooks/express-session.js +1 -1
  58. package/lib/list-installed.js +3 -1
  59. package/package.json +1 -1
@@ -72,7 +72,7 @@ class ExpressXss {
72
72
  ruleId
73
73
  })
74
74
  ) {
75
- const ctxt = new CallContext({
75
+ const ctxt = CallContext.create({
76
76
  obj: body,
77
77
  args: [body],
78
78
  result: body,
@@ -80,7 +80,7 @@ class FastifyRedirectSink {
80
80
  const url = typeof code === 'string' ? code : data.args[1];
81
81
 
82
82
  if (isVulnerable({ input: url, disallowedTags, requiredTags })) {
83
- const ctxt = new CallContext({
83
+ const ctxt = CallContext.create({
84
84
  obj: Reply,
85
85
  args: [url],
86
86
  result: url
@@ -209,13 +209,13 @@ class FastifyXssSink {
209
209
  const replyState = AsyncStorage.get(KEYS.FASTIFY_REPLY_SEND_STATE);
210
210
  // If the handler called Reply.send with a vuln report that stack. Otherwise just report the current stack
211
211
  const ctxt = replyState
212
- ? new CallContext({
212
+ ? CallContext.create({
213
213
  obj: replyState.reply,
214
214
  args: [replyState.payload],
215
215
  result: null,
216
216
  stacktrace: replyState.stack
217
217
  })
218
- : new CallContext({
218
+ : CallContext.create({
219
219
  obj: data.obj,
220
220
  args: [payload],
221
221
  result: null,
@@ -82,7 +82,7 @@ class HapiRedirectSink {
82
82
  }
83
83
  const [url] = data.args;
84
84
  if (isVulnerable({ input: url, disallowedTags, requiredTags })) {
85
- const ctxt = new CallContext({
85
+ const ctxt = CallContext.create({
86
86
  obj: data.obj,
87
87
  args: [url],
88
88
  result: url,
@@ -151,7 +151,7 @@ class HapiXssSink {
151
151
  const { handler, stack: stacktrace } = AsyncStorage.get(
152
152
  KEYS.HAPI_CALLER
153
153
  );
154
- const ctxt = new CallContext({
154
+ const ctxt = CallContext.create({
155
155
  obj: handler,
156
156
  args: [source],
157
157
  result: source,
@@ -78,7 +78,7 @@ class KoaRedirectSink {
78
78
  }
79
79
  const [url] = data.args;
80
80
  if (isVulnerable({ input: url, disallowedTags, requiredTags })) {
81
- const ctxt = new CallContext({
81
+ const ctxt = CallContext.create({
82
82
  obj: data.obj,
83
83
  args: [url],
84
84
  result: url,
@@ -98,7 +98,7 @@ class KoaXssSink {
98
98
  const { _cs_stack } = ctx;
99
99
  // The CallContext instance will use _cs_stack function if defined,
100
100
  // otherwise will generate one based on stackOpts
101
- const ctxt = new CallContext({
101
+ const ctxt = CallContext.create({
102
102
  obj: ctx.response,
103
103
  args: [body],
104
104
  result: body,
@@ -61,7 +61,7 @@ class LoopbackXss {
61
61
  ruleId
62
62
  })
63
63
  ) {
64
- const ctxt = new CallContext({
64
+ const ctxt = CallContext.create({
65
65
  obj: body,
66
66
  args: [body],
67
67
  result: body,
@@ -41,7 +41,7 @@ class DeserializationMembrane extends Membrane {
41
41
  }
42
42
 
43
43
  onString(str, metadata) {
44
- const context = new CallContext({
44
+ const context = CallContext.create({
45
45
  obj: this.obj,
46
46
  args: [this.arg],
47
47
  result: this.result,
@@ -149,7 +149,7 @@ module.exports = class SourceMembrane extends Membrane {
149
149
  makeCallContext(object, constructorOpt) {
150
150
  // capture the source of all the properties now, not when a string is
151
151
  // referenced.
152
- const context = new CallContext({
152
+ const context = CallContext.create({
153
153
  obj: 'IncomingMessage',
154
154
  args: [''],
155
155
  result: object,
@@ -202,7 +202,7 @@ module.exports = class SourceMembrane extends Membrane {
202
202
  */
203
203
  setTrackingData(trackResult, metadata) {
204
204
  const { str: tracked, props: trackData } = trackResult;
205
- const context = new CallContext({
205
+ const context = CallContext.create({
206
206
  obj: 'IncomingMessage',
207
207
  args: [metadata.path],
208
208
  result: tracked,
@@ -13,9 +13,9 @@ Copyright: 2022 Contrast Security, Inc
13
13
  way not consistent with the End User License Agreement.
14
14
  */
15
15
  'use strict';
16
+
16
17
  const _ = require('lodash');
17
18
  const util = require('util');
18
- const CleanStack = require('../../util/clean-stack');
19
19
  const tracker = require('../../tracker');
20
20
  const stackFactory = require('../../core/stacktrace').singleton;
21
21
  const { PROXY_TARGET } = require('../../../lib/constants');
@@ -25,147 +25,26 @@ const { toUntrackedString } = require('../utils');
25
25
  /**
26
26
  * Holds information about the call context of a function
27
27
  */
28
- module.exports = class CallContext {
29
- /**
30
- * Create a CallContext.
31
- *
32
- * @param {Object} obj - the 'this' for the finding
33
- * @param {Array} args - arguments passed to the function
34
- * @param {*} result - return value
35
- * @param {CallSite[]} stack - the callstack for the event
36
- */
37
- constructor(...rest) {
38
- if (rest.length === 1 && rest[0] !== undefined) {
39
- this.init(rest[0]);
40
- } else {
41
- this.initLegacy(...rest);
42
- }
43
- }
44
-
45
- /**
46
- * Initialize from parameterized constructor option.
47
- * @param {object} data constructor params
48
- */
49
- init(data) {
50
- const {
51
- obj = {},
52
- args = [],
53
- result,
54
- stackOpts = {
55
- constructorOpt: data.hooked,
56
- prependFrames: null
57
- }
58
- } = data;
59
-
60
- const stacktrace =
61
- data.stacktrace ||
62
- stackFactory.createSnapshot({
63
- constructorOpt: stackOpts.constructorOpt
64
- });
65
-
28
+ class CallContext {
29
+ constructor({ obj = {}, args = [], result }) {
66
30
  this.obj = obj;
67
31
  this.args = args;
68
32
  this.result = result;
69
-
70
- Object.defineProperty(this, 'stack', {
71
- get() {
72
- if (!this._stack) {
73
- this._stack = stacktrace();
74
- }
75
- return this._stack;
76
- },
77
- set(value) {
78
- this._stack = value;
79
- }
80
- });
81
- }
82
-
83
- /**
84
- * Initialize from array of constructor options. This will go away once all
85
- * callers use params e.g. `ApplicaitonContext` will need conversion.
86
- * @param {array} rest array of constructor args
87
- */
88
- initLegacy(...rest) {
89
- const obj = rest[0] || {};
90
- const args = rest[1] || [];
91
- const result = rest[2];
92
- const stack = rest[3];
93
- this.obj = obj;
94
- this.args = args;
95
- this.result = result;
96
- decorateCallContext(this, stack || new CleanStack());
97
- }
98
-
99
- static isTracked(str) {
100
- if (tracker.getData(str)) {
101
- return true;
102
- }
103
- return !!(str && typeof str === 'object' && str[PROXY_TARGET]);
104
- }
105
-
106
- static hasTrackedArg(arg, iteration = 0) {
107
- if (tracker.getData(arg)) {
108
- return true;
109
- }
110
-
111
- if (arg && typeof arg === 'object') {
112
-
113
- for (const key in arg) {
114
- if (tracker.getData(arg[key])) {
115
- return true;
116
- }
117
- if (arg[key] && typeof arg[key] === 'object' && iteration < 100) {
118
- return CallContext.hasTrackedArg(arg[key], iteration += 1);
119
- }
120
- }
121
- }
122
- return false;
123
- }
124
-
125
- // eslint-disable-next-line complexity
126
- static getDisplayRange(arg, orgArg = arg, iteration = 0) {
127
- if (tracker.getData(arg)) {
128
- return new TagRange(0, arg.length - 1, 'untrusted');
129
- }
130
-
131
- if (arg && typeof arg === 'object') {
132
- for (const key in arg) {
133
- if (arg[key] && typeof arg[key] === 'object' && iteration < 5) {
134
- const nestedDisplayRange = CallContext.getDisplayRange(arg[key], orgArg, iteration += 1);
135
- if (!_.isEmpty(nestedDisplayRange)) {
136
- return nestedDisplayRange;
137
- }
138
- }
139
- const trackedData = tracker.getData(arg[key]);
140
- if (trackedData && trackedData.tagRanges.length > 0) {
141
- const { start, stop } = trackedData.tagRanges[0];
142
- const offset = Array.isArray(orgArg) ? 2 : 0;
143
- const taintedString = arg[key].substring(start, stop + 1);
144
- const taintRangeStart = CallContext.valueString(orgArg).indexOf(taintedString) + offset;
145
- if (taintRangeStart === -1) {
146
- // If tracked string is not in the abbreviated stringified obj, disable highlighting
147
- return new TagRange(0, 0, 'disable-highlighting');
148
- }
149
- return new TagRange(taintRangeStart, taintRangeStart + taintedString.length - 1, 'untrusted');
150
- }
151
- }
152
- }
153
- return {};
154
33
  }
155
34
 
156
35
  set result(result) {
157
- this.__result = CallContext.valueString(result);
158
- this.resultTracked = CallContext.isTracked(result);
36
+ this.__result = valueString(result);
37
+ this.resultTracked = isTracked(result);
159
38
  }
160
39
  get result() {
161
40
  return this.__result;
162
41
  }
163
42
 
164
43
  set args(args) {
165
- this.__args = args.map(CallContext.valueString);
166
- this.argsTracked = args.map((arg) => CallContext.isTracked(arg));
167
- this.hasArgsTracked = args.map((arg) => CallContext.hasTrackedArg(arg));
168
- this.argsDisplayRanges = args.map((arg) => CallContext.getDisplayRange(arg));
44
+ this.__args = args.map(valueString);
45
+ this.argsTracked = args.map((arg) => isTracked(arg));
46
+ this.hasArgsTracked = args.map((arg) => hasTrackedArg(arg));
47
+ this.argsDisplayRanges = args.map((arg) => getDisplayRange(arg));
169
48
  }
170
49
 
171
50
  get args() {
@@ -173,53 +52,135 @@ module.exports = class CallContext {
173
52
  }
174
53
 
175
54
  set obj(obj) {
176
- this.__obj = CallContext.valueString(obj);
177
- this.objTracked = CallContext.isTracked(obj);
55
+ this.__obj = valueString(obj);
56
+ this.objTracked = isTracked(obj);
178
57
  }
179
58
  get obj() {
180
59
  return this.__obj;
181
60
  }
61
+ }
182
62
 
183
- /**
184
- * Returns either the value of a string passed into it, or the constructor
185
- * name for an object passed into it.
186
- * @static
187
- * @param {*} value
188
- * @return {string}
189
- */
190
- static valueString(value) {
191
- if (_.isString(value)) {
192
- return toUntrackedString(value);
193
- }
63
+ function isTracked(str) {
64
+ if (tracker.getData(str)) {
65
+ return true;
66
+ }
67
+ return !!(str && typeof str === 'object' && str[PROXY_TARGET]);
68
+ }
194
69
 
195
- if (_.isNumber(value)) {
196
- return value.toString();
197
- }
70
+ function hasTrackedArg(arg, iteration = 0) {
71
+ if (tracker.getData(arg)) {
72
+ return true;
73
+ }
198
74
 
199
- const type = _.get(value, 'constructor.name', 'null');
75
+ if (arg && typeof arg === 'object') {
200
76
 
201
- if ((type === 'Object' || type === 'Array') && value) {
202
- // make string representation uniform with no new lines and consistent spaces
203
- let str = util
204
- .inspect(value)
205
- .replace(/(\n|\s+)/g, (match) => (match === '\n' ? '' : ' '));
206
- if (str.length > 50) {
207
- str = str.substring(0, 50);
208
- str += '...';
77
+ for (const key in arg) {
78
+ if (tracker.getData(arg[key])) {
79
+ return true;
209
80
  }
81
+ if (arg[key] && typeof arg[key] === 'object' && iteration < 100) {
82
+ return hasTrackedArg(arg[key], iteration += 1);
83
+ }
84
+ }
85
+ }
86
+ return false;
87
+ }
88
+
89
+ // eslint-disable-next-line complexity
90
+ function getDisplayRange(arg, orgArg = arg, iteration = 0) {
91
+ if (tracker.getData(arg)) {
92
+ return new TagRange(0, arg.length - 1, 'untrusted');
93
+ }
210
94
 
211
- return str;
95
+ if (arg && typeof arg === 'object') {
96
+ for (const key in arg) {
97
+ if (arg[key] && typeof arg[key] === 'object' && iteration < 5) {
98
+ const nestedDisplayRange = getDisplayRange(arg[key], orgArg, iteration += 1);
99
+ if (!_.isEmpty(nestedDisplayRange)) {
100
+ return nestedDisplayRange;
101
+ }
102
+ }
103
+ const trackedData = tracker.getData(arg[key]);
104
+ if (trackedData && trackedData.tagRanges.length > 0) {
105
+ const { start, stop } = trackedData.tagRanges[0];
106
+ const offset = Array.isArray(orgArg) ? 2 : 0;
107
+ const taintedString = arg[key].substring(start, stop + 1);
108
+ const taintRangeStart = valueString(orgArg).indexOf(taintedString) + offset;
109
+ if (taintRangeStart === -1) {
110
+ // If tracked string is not in the abbreviated stringified obj, disable highlighting
111
+ return new TagRange(0, 0, 'disable-highlighting');
112
+ }
113
+ return new TagRange(taintRangeStart, taintRangeStart + taintedString.length - 1, 'untrusted');
114
+ }
212
115
  }
213
- return type;
214
116
  }
215
- };
117
+ return {};
118
+ }
216
119
 
217
120
  /**
218
- *
219
- * @param {*} instance The instance to decorate
121
+ * Returns either the value of a string passed into it, or the constructor
122
+ * name for an object passed into it.
123
+ * @static
124
+ * @param {*} value
125
+ * @return {string}
220
126
  */
221
- const decorateCallContext = (instance, stack) => {
127
+ function valueString(value) {
128
+ if (_.isString(value)) {
129
+ return toUntrackedString(value);
130
+ }
131
+
132
+ if (_.isNumber(value)) {
133
+ return value.toString();
134
+ }
135
+
136
+ const type = _.get(value, 'constructor.name', 'null');
137
+
138
+ if ((type === 'Object' || type === 'Array') && value) {
139
+ // make string representation uniform with no new lines and consistent spaces
140
+ let str = util
141
+ .inspect(value)
142
+ .replace(/(\n|\s+)/g, (match) => (match === '\n' ? '' : ' '));
143
+ if (str.length > 50) {
144
+ str = str.substring(0, 50);
145
+ str += '...';
146
+ }
147
+
148
+ return str;
149
+ }
150
+ return type;
151
+ }
152
+
153
+ function create(params) {
154
+ const instance = new CallContext(params);
155
+ const snapshot = params.stacktrace || stackFactory.createSnapshot({
156
+ constructorOpt: params.hooked
157
+ });
158
+
159
+ let stack;
160
+
161
+ // If this occurs within the constructor it will leak all references within
162
+ // the snapshot's closure.
222
163
  Object.defineProperty(instance, 'stack', {
223
- value: stack.build().stack
164
+ enumerable: true,
165
+ configurable: true,
166
+ get() {
167
+ if (!stack) {
168
+ stack = snapshot();
169
+ }
170
+ return stack;
171
+ },
172
+ set(value) {
173
+ stack = value;
174
+ }
224
175
  });
176
+
177
+ return instance;
178
+ }
179
+
180
+ module.exports = {
181
+ create,
182
+ valueString,
183
+ hasTrackedArg,
184
+ isTracked,
185
+ getDisplayRange
225
186
  };
@@ -250,6 +250,7 @@ module.exports.handle = function() {
250
250
  data.args = [input, contrastReplacer, space];
251
251
  },
252
252
 
253
+ // eslint-disable-next-line complexity
253
254
  post(data) {
254
255
  if (!data.metadata.propagate) {
255
256
  return data.result;
@@ -300,7 +301,7 @@ module.exports.handle = function() {
300
301
 
301
302
  customSources.push(
302
303
  new SourceEvent({
303
- context: new CallContext({
304
+ context: CallContext.create({
304
305
  obj,
305
306
  args: data.args,
306
307
  result: data.result,
@@ -341,7 +342,7 @@ module.exports.handle = function() {
341
342
  }
342
343
 
343
344
  props.event = new PropagationEvent({
344
- context: new CallContext(data),
345
+ context: CallContext.create(data),
345
346
  signature: sig,
346
347
  tagRanges: props.tagRanges,
347
348
  source: 'P',
@@ -149,7 +149,7 @@ function createEvent(
149
149
  srcType = 'P';
150
150
  }
151
151
  const signature = new Signature('Array.prototype.join');
152
- const context = new CallContext(data);
152
+ const context = CallContext.create(data);
153
153
  const event = new PropagationEvent({
154
154
  context,
155
155
  signature,
@@ -94,7 +94,7 @@ const findPropagatorInPolicy = (method) => {
94
94
  */
95
95
  const createEvent = ({ tagRanges, method, parents }, data) => {
96
96
  const signature = new Signature(method);
97
- const context = new CallContext(data);
97
+ const context = CallContext.create(data);
98
98
  const prop = findPropagatorInPolicy(method);
99
99
  return new PropagationEvent({
100
100
  context,
@@ -118,7 +118,7 @@ const trackResult = (metadata, data) => {
118
118
  const tracked = tracker.track(data.result);
119
119
  if (tracked) {
120
120
  tracked.props.tagRanges = metadata.tagRanges;
121
- tracked.props.event = createEvent(metadata, data);
121
+ tracked.props.event = createEvent(metadata, data);
122
122
  data.result = tracked.str;
123
123
  }
124
124
  }
@@ -59,7 +59,7 @@ module.exports.handle = function handle() {
59
59
  );
60
60
  tagRangeUtil.removeInPlace(trackingData.tagRanges, ['untrusted']);
61
61
 
62
- const context = new CallContext(data);
62
+ const context = CallContext.create(data);
63
63
  const event = new PropagationEvent({
64
64
  context,
65
65
  signature: new Signature('fastify-static.allowedPath'),
@@ -55,7 +55,7 @@ function patchUtilsExport(utilsExport) {
55
55
  );
56
56
 
57
57
  const event = new PropagationEvent({
58
- context: new CallContext(data),
58
+ context: CallContext.create(data),
59
59
  parents: [trackData.event],
60
60
  signature: new Signature(
61
61
  'handlebars/dist/cjs/handlebars/utils.escapeExpression'
@@ -50,7 +50,7 @@ function instrumentJoiBoolean(boolean) {
50
50
  new TagRange(0, data.args[0].length - 1, 'alphanum-space-hyphen')
51
51
  );
52
52
  trackingData.event = new PropagationEvent({
53
- context: new CallContext(data),
53
+ context: CallContext.create(data),
54
54
  signature: new Signature('joi.boolean.coerce'),
55
55
  tagRanges: trackingData.tagRanges,
56
56
  source: 'P',
@@ -49,7 +49,7 @@ function instrumentJoiNumber(number) {
49
49
  new TagRange(0, data.args[0].length - 1, 'limited-chars')
50
50
  );
51
51
  trackingData.event = new PropagationEvent({
52
- context: new CallContext(data),
52
+ context: CallContext.create(data),
53
53
  signature: new Signature('joi.number.coerce'),
54
54
  tagRanges: trackingData.tagRanges,
55
55
  source: 'P',
@@ -87,7 +87,7 @@ function instrumentJoiString(string) {
87
87
  new TagRange(0, input.length - 1, 'string-type-checked')
88
88
  );
89
89
  trackingData.event = new PropagationEvent({
90
- context: new CallContext(data),
90
+ context: CallContext.create(data),
91
91
  signature: new Signature('joi.string.validate'),
92
92
  tagRanges: trackingData.tagRanges,
93
93
  source: 'P',
@@ -179,7 +179,7 @@ function createPropagationEvent({
179
179
  }) {
180
180
  const { event: lastEvent } = trackedArgsData;
181
181
 
182
- const context = new CallContext(data);
182
+ const context = CallContext.create(data);
183
183
  const signature = new Signature(`joi.string.${joiMethod}`);
184
184
 
185
185
  const event = new PropagationEvent({
@@ -84,7 +84,7 @@ module.exports = function Propagator(agent, propagationDescriptor) {
84
84
  const sourceMeta = getSourcesMetadata(sources);
85
85
  const { tagRanges: sourceTagRanges, events: sourceEvents } = sourceMeta;
86
86
  const sig = new Signature(signature);
87
- const ctxt = new CallContext(data);
87
+ const ctxt = CallContext.create(data);
88
88
  const event = new PropagationEvent({
89
89
  context: ctxt,
90
90
  signature: sig,
@@ -12,6 +12,8 @@ Copyright: 2022 Contrast Security, Inc
12
12
  engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
13
  way not consistent with the End User License Agreement.
14
14
  */
15
+ 'use strict';
16
+
15
17
  const TagRange = require('../../models/tag-range');
16
18
  const { CallContext, PropagationEvent, Signature } = require('../../models');
17
19
 
@@ -40,7 +42,7 @@ const tagCustomValidatedValues = (values, data, tracker, tagRangeUtil) => {
40
42
  );
41
43
 
42
44
  props.event = new PropagationEvent({
43
- context: new CallContext(data),
45
+ context: CallContext.create(data),
44
46
  signature: new Signature('mongoose.mixed.doValidateSync'),
45
47
  tagRanges: props.tagRanges,
46
48
  source: 'P',
@@ -85,7 +85,7 @@ const doValidateSyncPatcher = (SchemaString) => {
85
85
  );
86
86
 
87
87
  props.event = new PropagationEvent({
88
- context: new CallContext(data),
88
+ context: CallContext.create(data),
89
89
  signature: new Signature('mongoose.string.doValidateSync'),
90
90
  tagRanges: props.tagRanges,
91
91
  source: 'P',
@@ -39,7 +39,7 @@ function handle() {
39
39
  );
40
40
 
41
41
  trackingData.event = new PropagationEvent({
42
- context: new CallContext(data),
42
+ context: CallContext.create(data),
43
43
  signature: new Signature('Number'),
44
44
  tagRanges: trackingData.tagRanges,
45
45
  source: 'P',
@@ -73,14 +73,14 @@ module.exports.handle = function handle() {
73
73
  if (tracked) {
74
74
  tracked.props.tagRanges = tagRanges;
75
75
  tracked.props.event = new PropagationEvent({
76
- context: new CallContext(data),
76
+ context: CallContext.create(data),
77
77
  parents: [parentEvent],
78
78
  signature,
79
79
  source: 'P',
80
80
  tagRanges,
81
81
  target: 'R'
82
82
  });
83
- data.result = tracked.str;
83
+ data.result = tracked.str;
84
84
  }
85
85
  }
86
86
  });
@@ -62,14 +62,14 @@ module.exports.handle = function handle() {
62
62
  if (tracked) {
63
63
  tracked.props.tagRanges = tagRanges;
64
64
  tracked.props.event = new PropagationEvent({
65
- context: new CallContext(data),
65
+ context: CallContext.create(data),
66
66
  parents: [parentEvent],
67
67
  signature,
68
68
  source: 'P',
69
69
  tagRanges,
70
70
  target: 'R'
71
71
  });
72
- data.result = tracked.str;
72
+ data.result = tracked.str;
73
73
  }
74
74
  }
75
75
  });
@@ -67,14 +67,14 @@ module.exports.handle = function handle() {
67
67
  if (tracked) {
68
68
  tracked.props.tagRanges = tagRanges;
69
69
  tracked.props.event = new PropagationEvent({
70
- context: new CallContext(data),
70
+ context: CallContext.create(data),
71
71
  parents: [parentEvent],
72
72
  signature,
73
73
  source: 'P',
74
74
  tagRanges,
75
75
  target: 'R'
76
76
  });
77
- data.result = tracked.str;
77
+ data.result = tracked.str;
78
78
  }
79
79
  }
80
80
  });
@@ -38,7 +38,7 @@ function handler(data) {
38
38
  if (tracked) {
39
39
  tracked.props.tagRanges = tagRanges;
40
40
  tracked.props.event = new PropagationEvent({
41
- context: new CallContext({
41
+ context: CallContext.create({
42
42
  ...data,
43
43
  obj: null
44
44
  }),
@@ -36,7 +36,6 @@ function getUnescapeWrapper(data, unescape) {
36
36
 
37
37
  function unescapeWrapper(part) {
38
38
  let result;
39
- let resultData;
40
39
  const start = input.indexOf(part, data.curPos);
41
40
  // any tag ranges overlapping current part?
42
41
  const tagRanges = tagRangeUtil.trim(
@@ -58,11 +57,11 @@ function getUnescapeWrapper(data, unescape) {
58
57
 
59
58
  // allow propagation through unescape
60
59
  result = Scopes.runInAllowAllScope(() => unescape(result));
61
- resultData = tracker.getData(result);
60
+ const resultData = tracker.getData(result);
62
61
 
63
62
  if (resultData) {
64
63
  resultData.event = new PropagationEvent({
65
- context: new CallContext({
64
+ context: CallContext.create({
66
65
  ...data,
67
66
  obj: null,
68
67
  args: data.origArgs,
@@ -289,12 +289,12 @@ function post(data) {
289
289
  const sorted = _.sortBy(data.state.tagRanges, 'start');
290
290
  tracked.props.tagRanges = [];
291
291
  tagRangeUtil.addAllInPlace(tracked.props.tagRanges, sorted);
292
-
292
+
293
293
  // stringify / encode
294
294
  const method = data.funcKey.split('.')[1];
295
-
295
+
296
296
  tracked.props.event = new PropagationEvent({
297
- context: new CallContext({
297
+ context: CallContext.create({
298
298
  ...data,
299
299
  args: data.state.origArgs,
300
300
  obj: null
@@ -42,7 +42,7 @@ function handler(data) {
42
42
  if (tracked) {
43
43
  tracked.props.tagRanges = tagRanges;
44
44
  tracked.props.event = new PropagationEvent({
45
- context: new CallContext({
45
+ context: CallContext.create({
46
46
  ...data,
47
47
  obj: null
48
48
  }),
@@ -53,7 +53,7 @@ function handler(data) {
53
53
  target: 'R',
54
54
  untags: ['url-encoded']
55
55
  });
56
- data.result = tracked.str;
56
+ data.result = tracked.str;
57
57
  }
58
58
  }
59
59
 
@@ -45,7 +45,7 @@ module.exports.handle = function() {
45
45
  );
46
46
 
47
47
  const event = new PropagationEvent({
48
- context: new CallContext(data),
48
+ context: CallContext.create(data),
49
49
  parents: [trackingData.event],
50
50
  signature: new Signature('sequelize/lib/sql-string.escape'),
51
51
  source: 'P',
@@ -127,7 +127,7 @@ module.exports.handle = function() {
127
127
  }
128
128
 
129
129
  const event = new PropagationEvent({
130
- context: new CallContext(data),
130
+ context: CallContext.create(data),
131
131
  parents: [trackingData.event],
132
132
  signature: new Signature(
133
133
  'sequelize/lib/sql-string.formatNamedParameters'
@@ -88,7 +88,7 @@ module.exports.handle = function() {
88
88
  }
89
89
 
90
90
  const event = new PropagationEvent({
91
- context: new CallContext(data),
91
+ context: CallContext.create(data),
92
92
  parents: [trackingData.event],
93
93
  signature: new Signature('sequelize/lib/sql-string.format'),
94
94
  source: 'P',
@@ -16,7 +16,6 @@ Copyright: 2022 Contrast Security, Inc
16
16
  * String.prototype.replace Propagation Provider module.
17
17
  * @module lib/assess/propagators/String.prototype.replace
18
18
  */
19
-
20
19
  'use strict';
21
20
 
22
21
  const _ = require('lodash');
@@ -183,10 +182,10 @@ function findOffset(args) {
183
182
  return _.isNumber(args[1])
184
183
  ? args[1] // no general capture groups
185
184
  : _.isNumber(args[args.length - 2])
186
- ? args[args.length - 2] // no named-capture groups
187
- : _.isNumber(args[args.length - 3])
188
- ? args[args.length - 3] // w/ named-capture groups
189
- : (() => {
185
+ ? args[args.length - 2] // no named-capture groups
186
+ : _.isNumber(args[args.length - 3])
187
+ ? args[args.length - 3] // w/ named-capture groups
188
+ : (() => {
190
189
  // Invalid call at this point though
191
190
  for (const arg of args) {
192
191
  if (_.isNumber(arg)) return arg;
@@ -595,7 +594,7 @@ function trackFinalResult(callContext) {
595
594
  tagRanges: trackData.resultTagRanges,
596
595
  result: tracked.str
597
596
  });
598
- tracked.props.tagRanges = trackData.resultTagRanges;
597
+ tracked.props.tagRanges = trackData.resultTagRanges;
599
598
  callContext.result = tracked.str;
600
599
  }
601
600
  }
@@ -614,7 +613,7 @@ function createPropagationEvent({ callContext, tagRanges, result }) {
614
613
  }
615
614
  } = callContext;
616
615
  const signature = new Signature(PROPAGATOR_METHOD);
617
- const context = new CallContext({
616
+ const context = CallContext.create({
618
617
  obj,
619
618
  args,
620
619
  result,
@@ -646,8 +645,8 @@ function getParentEvents(source, trackData) {
646
645
  ...(source === 'P'
647
646
  ? [trackData.replacerData.event]
648
647
  : source === 'O'
649
- ? [trackData.objData.event]
650
- : [trackData.replacerData.event, trackData.objData.event]),
648
+ ? [trackData.objData.event]
649
+ : [trackData.replacerData.event, trackData.objData.event]),
651
650
  ...Array.from(trackData.dynamicEvents)
652
651
  ]);
653
652
  }
@@ -661,8 +660,8 @@ function getSourceType({ objData, replacerData }) {
661
660
  return objData.tracked && !replacerData.tracked
662
661
  ? 'O'
663
662
  : !objData.tracked && replacerData.tracked
664
- ? 'P'
665
- : 'A';
663
+ ? 'P'
664
+ : 'A';
666
665
  }
667
666
 
668
667
  module.exports.coerceReplacer = coerceReplacer;
@@ -86,7 +86,7 @@ module.exports.handle = {
86
86
  const oldTagRanges = getTagRanges(origString);
87
87
  const oldEvent = getEvent(origString);
88
88
 
89
- const ctxt = new CallContext(data);
89
+ const ctxt = CallContext.create(data);
90
90
 
91
91
  if (args[0].length === 0) {
92
92
  /***
@@ -166,7 +166,7 @@ module.exports.handle = {
166
166
  const tracked = tracker.track(stringPart);
167
167
  if (tracked) {
168
168
  tracked.props.tagRanges.push(new TagRange(tagStart, tagStop, tag.tag));
169
- result[i] = tracked.str;
169
+ result[i] = tracked.str;
170
170
  }
171
171
  }
172
172
  });
@@ -206,7 +206,7 @@ function handleEmptySeperator(data, oldTagRanges, oldEvent) {
206
206
 
207
207
  const { result } = data;
208
208
 
209
- const ctxt = new CallContext(data);
209
+ const ctxt = CallContext.create(data);
210
210
 
211
211
  // map index --> { Event, tagRanges }, so that we can update existing properties in cases where
212
212
  // more than one tag was on the same index
@@ -233,11 +233,11 @@ function handleEmptySeperator(data, oldTagRanges, oldEvent) {
233
233
  source: 'O',
234
234
  target: 'R'
235
235
  });
236
-
236
+
237
237
  tracked.props.event = event;
238
238
  info = { event, tagRanges: tracked.props.tagRanges };
239
239
  sharedCharInfo.set(i, info);
240
- result[i] = tracked.str;
240
+ result[i] = tracked.str;
241
241
  }
242
242
  }
243
243
 
@@ -259,7 +259,7 @@ function transferTracking(origString, resultArray) {
259
259
  if (tracked) {
260
260
  tracked.props.tagRanges = getTagRanges(origString);
261
261
  tracked.props.event = getEvent(origString);
262
- resultArray[i] = tracked.str;
262
+ resultArray[i] = tracked.str;
263
263
  }
264
264
  }
265
265
  return resultArray;
@@ -45,7 +45,7 @@ function handle(data) {
45
45
  const tracked = tracker.track(result);
46
46
  if (tracked) {
47
47
  tracked.props.tagRanges = targetRanges;
48
- const context = new CallContext(data);
48
+ const context = CallContext.create(data);
49
49
  const event = new PropagationEvent({
50
50
  context,
51
51
  signature,
@@ -55,7 +55,7 @@ function handle(data) {
55
55
  });
56
56
  event.parents.push(sourceEvent);
57
57
  tracked.props.event = event;
58
-
59
- data.result = tracked.str;
58
+
59
+ data.result = tracked.str;
60
60
  }
61
61
  }
@@ -69,7 +69,7 @@ function propagator(data, tagName, signatureName) {
69
69
  if (tracked) {
70
70
  tracked.props.tagRanges = tagRanges;
71
71
  tracked.props.event = new PropagationEvent({
72
- context: new CallContext({
72
+ context: CallContext.create({
73
73
  ...data,
74
74
  obj: null
75
75
  }),
@@ -80,7 +80,7 @@ function propagator(data, tagName, signatureName) {
80
80
  tagRanges,
81
81
  tags: tagName
82
82
  });
83
- data.result = tracked.str;
83
+ data.result = tracked.str;
84
84
  }
85
85
  }
86
86
 
@@ -114,7 +114,7 @@ function buildProperties(props, tagRanges, exps, result, strings, events) {
114
114
  this,
115
115
  [strings].concat(exps.map((exp, i) => `$\{exp${i}}`))
116
116
  );
117
- const ctxt = new CallContext({
117
+ const ctxt = CallContext.create({
118
118
  obj: raw,
119
119
  args: exps,
120
120
  result,
@@ -39,7 +39,7 @@ module.exports.createEvent = function(
39
39
  // To be 100% correct we would use result/url (should be the same)
40
40
  // for obj/result, but it's nicer to show propagation to the specific part.
41
41
  // Since the operation is P2O, we replace O with the url part.
42
- const context = new CallContext({
42
+ const context = CallContext.create({
43
43
  obj: part,
44
44
  args,
45
45
  result
@@ -99,7 +99,7 @@ module.exports.handle = function handle() {
99
99
  tr.stop = data.result.length - 1;
100
100
  }
101
101
  }
102
- const context = new CallContext(data.obj, data.args, data.result);
102
+ const context = CallContext.create(data);
103
103
  const event = new PropagationEvent({
104
104
  context,
105
105
  signature,
@@ -103,7 +103,7 @@ module.exports.handle = function () {
103
103
  );
104
104
  tagRangeUtil.removeInPlace(trackingData.tagRanges, ['untrusted']);
105
105
 
106
- const context = new CallContext(data);
106
+ const context = CallContext.create(data);
107
107
  const event = new PropagationEvent({
108
108
  context,
109
109
  signature,
@@ -233,7 +233,7 @@ module.exports.handle = function () {
233
233
  );
234
234
  tagRangeUtil.removeInPlace(trackingData.tagRanges, ['untrusted']);
235
235
 
236
- const context = new CallContext(data);
236
+ const context = CallContext.create(data);
237
237
  const event = new PropagationEvent({
238
238
  context,
239
239
  signature,
@@ -12,6 +12,8 @@ Copyright: 2022 Contrast Security, Inc
12
12
  engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
13
  way not consistent with the End User License Agreement.
14
14
  */
15
+ 'use strict';
16
+
15
17
  const { get } = require('lodash');
16
18
  const { CallContext, Signature } = require('../models');
17
19
  const patcher = require('../../hooks/patcher');
@@ -30,7 +32,7 @@ module.exports = function registerSessionHandler(response) {
30
32
  patchType: PATCH_TYPES.ASSESS_SINK,
31
33
  post(data) {
32
34
  const options = { cookie: get(data, 'args[2]', {}) };
33
- const context = new CallContext(data);
35
+ const context = CallContext.create(data);
34
36
  const signature = new Signature({
35
37
  moduleName: 'restify-cookies.Response',
36
38
  methodName: 'setCookie',
@@ -81,7 +81,7 @@ class RestifyRedirectSink {
81
81
  report({
82
82
  signature,
83
83
  input: vulnerable,
84
- ctxt: new CallContext({
84
+ ctxt: CallContext.create({
85
85
  obj,
86
86
  args: [input],
87
87
  stackOpts: {
@@ -70,7 +70,7 @@ class RestifyXssSink {
70
70
  report({
71
71
  signature,
72
72
  input,
73
- ctxt: new CallContext({
73
+ ctxt: CallContext.create({
74
74
  obj: data.obj,
75
75
  args: [input],
76
76
  stackOpts: {
@@ -441,6 +441,7 @@ module.exports = (agent) => {
441
441
  // here we will pull the method out of the fn args
442
442
  // and replace it on the signature. the assumption
443
443
  // is the string to replace is always the last position
444
+ // eslint-disable-next-line no-prototype-builtins
444
445
  if (signature.hasOwnProperty('userMethodPosition')) {
445
446
  const method = args.splice(signature.userMethodPosition, 1).join();
446
447
  const methodArray = signature.methodName.split('.');
@@ -461,7 +462,7 @@ module.exports = (agent) => {
461
462
  }
462
463
 
463
464
  if (vulnerableString) {
464
- const ctxt = new CallContext(data);
465
+ const ctxt = CallContext.create(data);
465
466
  // If we had to create a stacktrace already, use it rather than build
466
467
  // another during reporting
467
468
  if (stack) {
@@ -106,7 +106,7 @@ class Handler {
106
106
  ruleId: REFLECTED_XSS,
107
107
  signature,
108
108
  input: str,
109
- ctxt: new CallContext({
109
+ ctxt: CallContext.create({
110
110
  obj: typeName,
111
111
  args: [str],
112
112
  result: str,
@@ -267,7 +267,7 @@ module.exports = ({ common }) => {
267
267
  );
268
268
 
269
269
  if (vulnerableString.length) {
270
- const ctxt = new CallContext(data);
270
+ const ctxt = CallContext.create(data);
271
271
  const signature = new Signature({ moduleName: 'aws-sdk', methodName });
272
272
  report({ ruleId, signature, input: vulnerableString[0], ctxt });
273
273
  }
@@ -55,7 +55,7 @@ module.exports = ({
55
55
  enabled &&
56
56
  isVulnerable({ input: result, disallowedTags, requiredTags, ruleId })
57
57
  ) {
58
- const ctxt = new CallContext({
58
+ const ctxt = CallContext.create({
59
59
  obj: reply,
60
60
  args: [result],
61
61
  result: ret,
@@ -45,7 +45,7 @@ module.exports = ({ common, signature }) => ({
45
45
  });
46
46
 
47
47
  if (rv) {
48
- const ctxt = new CallContext(data);
48
+ const ctxt = CallContext.create(data);
49
49
  common.report({
50
50
  ruleId,
51
51
  signature: new Signature(signature),
@@ -132,7 +132,7 @@ module.exports = ({ common }) => {
132
132
 
133
133
  if (doc && !doc.__contrastContext) {
134
134
  try {
135
- const ctxt = new CallContext({
135
+ const ctxt = CallContext.create({
136
136
  ...data,
137
137
  result: null
138
138
  });
@@ -358,7 +358,7 @@ module.exports = ({ common }) => {
358
358
  */
359
359
  function getCallContext(ctxt, data) {
360
360
  if (!ctxt) {
361
- ctxt = new CallContext(data);
361
+ ctxt = CallContext.create(data);
362
362
  ctxt.signature = data.name;
363
363
  }
364
364
  ctxt.result = data.result;
@@ -96,7 +96,7 @@ class Handler {
96
96
  ruleId: NOSQL_INJECTION,
97
97
  signature: matchSignature,
98
98
  input: str,
99
- ctxt: new CallContext({
99
+ ctxt: CallContext.create({
100
100
  obj,
101
101
  args: [str],
102
102
  result: str,
@@ -118,7 +118,7 @@ class Handler {
118
118
  ruleId: NOSQL_INJECTION,
119
119
  signature: filterSignature,
120
120
  input: str,
121
- ctxt: new CallContext({
121
+ ctxt: CallContext.create({
122
122
  obj,
123
123
  args: [str],
124
124
  result: str,
@@ -198,7 +198,7 @@ class SourceEventHandler {
198
198
 
199
199
  props.tagRanges = this.getTagRanges({ name, result });
200
200
  props.event = new SourceEvent({
201
- context: new CallContext({
201
+ context: CallContext.create({
202
202
  obj: 'IncomingMessage {}',
203
203
  args: [`${this.typeKey}.${name}`],
204
204
  result,
@@ -241,7 +241,7 @@ class SourceEventHandler {
241
241
 
242
242
  const rules = new Set();
243
243
 
244
- /*
244
+ /*
245
245
  Maybe it's pointless because we set skipEvent to true if appliesToAllAssessRules
246
246
  and we won't get here
247
247
  */
@@ -68,7 +68,7 @@ class SpdyXss {
68
68
  ruleId
69
69
  })
70
70
  ) {
71
- const ctxt = new CallContext({
71
+ const ctxt = CallContext.create({
72
72
  obj: body,
73
73
  args: [body],
74
74
  result: body,
@@ -402,14 +402,15 @@ const agent = [
402
402
  arg: '[false]',
403
403
  default: true,
404
404
  fn: castBoolean,
405
- desc: 'whether to use speedracer for input analysis when enabled',
405
+ desc: 'Should be set to [true] in order to communicate with TeamServer.' +
406
+ 'The place of input analysis depends on the value of agent.node.native_input_analysis',
406
407
  },
407
408
  {
408
409
  name: 'agent.node.native_input_analysis',
409
410
  arg: '[true]',
410
411
  default: false,
411
412
  fn: castBoolean,
412
- desc: 'do agent-native input analysis prior to any external analysis',
413
+ desc: 'if true - input analysis is done via agent-lib, otherwise it is done by SpeedRacer',
413
414
  },
414
415
  {
415
416
  name: 'agent.node.analysis_log_dir',
@@ -59,6 +59,7 @@ utils.httpOnly = function(options) {
59
59
  * @return {Boolean}
60
60
  */
61
61
  utils.vulnerableOption = function({ options, key }) {
62
+ // eslint-disable-next-line no-prototype-builtins
62
63
  return options.hasOwnProperty(key) && options[key] !== true;
63
64
  };
64
65
 
@@ -81,7 +82,7 @@ utils.createSessionContext = function(obj, args, result, source, stackOpts) {
81
82
  }
82
83
 
83
84
  if (!opts) {
84
- return new CallContext({
85
+ return CallContext.create({
85
86
  obj,
86
87
  args,
87
88
  result,
@@ -95,7 +96,7 @@ utils.createSessionContext = function(obj, args, result, source, stackOpts) {
95
96
 
96
97
  args[idx] = opts;
97
98
 
98
- return new CallContext({ obj, args, result });
99
+ return CallContext.create({ obj, args, result });
99
100
  };
100
101
 
101
102
  /**
@@ -48,7 +48,7 @@ function hook() {
48
48
  _.set(data.args, '[0].secret', '[HIDDEN]');
49
49
  }
50
50
 
51
- const ctxt = new CallContext(data);
51
+ const ctxt = CallContext.create(data);
52
52
  // event of when the session was initialized that will be reused for all
53
53
  // session findings
54
54
  const sessionEvent = new SessionEvent({
@@ -21,7 +21,8 @@ const {
21
21
  AGENT_INFO: { SUPPORTED_NPM_VERSIONS },
22
22
  } = require('./constants');
23
23
 
24
- const VERSION_REGEX = /^npm@(\S+)\s+(\S+)$/m;
24
+ const VERSION_REGEX = /^npm@(\S+)\s+(\S+[\s\S]*)$/m;
25
+ const isWin32 = process.platform === 'win32';
25
26
 
26
27
  const execFile = util.promisify(require('child_process').execFile);
27
28
 
@@ -44,6 +45,7 @@ module.exports = async function listInstalled(cwd, logger) {
44
45
  cwd,
45
46
  env: { ...process.env, NODE_OPTIONS: undefined },
46
47
  maxBuffer: 1024 * 1024 * 128,
48
+ shell: isWin32
47
49
  };
48
50
  let stdout;
49
51
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/agent",
3
- "version": "4.25.1",
3
+ "version": "4.25.3",
4
4
  "description": "Node.js security instrumentation by Contrast Security",
5
5
  "keywords": [
6
6
  "security",