@contrast/agent 4.20.2 → 4.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/VERSION CHANGED
@@ -1 +1 @@
1
- 2.28.20
1
+ 2.28.22
Binary file
@@ -28,7 +28,7 @@ class HapiAssessSources {
28
28
  * Handles emitting of all assess source events
29
29
  */
30
30
  registerSourcesHandler() {
31
- agentEmitter.on(EVENTS.HAPI_PRE_HANDLER, ({ request }) => {
31
+ agentEmitter.on(EVENTS.HAPI_POST_AUTH, ({ request }) => {
32
32
  emitWatchableObjects(request);
33
33
  });
34
34
  }
@@ -1000,6 +1000,12 @@
1000
1000
  "methodName": "isCreditCard",
1001
1001
  "isModule": true
1002
1002
  },
1003
+ "validator.isDate": {
1004
+ "moduleName": "validator",
1005
+ "version": ">=13.0.0",
1006
+ "methodName": "isDecimal",
1007
+ "isModule": true
1008
+ },
1003
1009
  "validator.isDecimal": {
1004
1010
  "moduleName": "validator",
1005
1011
  "version": ">=13.0.0",
@@ -1018,6 +1024,12 @@
1018
1024
  "methodName": "isEAN",
1019
1025
  "isModule": true
1020
1026
  },
1027
+ "validator.isEmail": {
1028
+ "moduleName": "validator",
1029
+ "version": ">=13.0.0",
1030
+ "methodName": "isEmail",
1031
+ "isModule": true
1032
+ },
1021
1033
  "validator.isEthereumAddress": {
1022
1034
  "moduleName": "validator",
1023
1035
  "version": ">=13.0.0",
@@ -39,11 +39,11 @@ const agent = require('../../../agent');
39
39
  * https://docs.google.com/spreadsheets/d/17p5C6NOISNuWi8D-07d8gNg8byQytuwjtgBgzGpfU_w/edit#gid=0
40
40
  *
41
41
  */
42
- module.exports.handle = function() {
42
+ module.exports.handle = function () {
43
43
  const {
44
44
  validators,
45
45
  untrackers,
46
- sanitizers
46
+ sanitizers,
47
47
  } = require('./validator-methods.js');
48
48
 
49
49
  const patchType = PATCH_TYPES.ASSESS_PROPAGATOR;
@@ -62,7 +62,7 @@ module.exports.handle = function() {
62
62
  const patched = patcher.patch(obj, {
63
63
  name,
64
64
  patchType,
65
- post
65
+ post,
66
66
  });
67
67
  // babel adds a self-referential default property so if present in the unpatched
68
68
  // object update the patched object
@@ -89,7 +89,12 @@ module.exports.handle = function() {
89
89
  { name: 'validator', file: `lib/${validator}` },
90
90
  (index, meta) => {
91
91
  function post(data) {
92
- if (data.result && (validator !== 'matches' || (validator === 'matches' && agent.config.assess.trust_custom_validators))) {
92
+ if (
93
+ data.result &&
94
+ (validator !== 'matches' ||
95
+ (validator === 'matches' &&
96
+ agent.config.assess.trust_custom_validators))
97
+ ) {
93
98
  const trackingData = tracker.getData(data.args[0]);
94
99
  if (trackingData) {
95
100
  tagRangeUtil.addInPlace(
@@ -104,7 +109,7 @@ module.exports.handle = function() {
104
109
  signature,
105
110
  tagRanges: trackingData.tagRanges,
106
111
  source: 'O',
107
- target: 'R'
112
+ target: 'R',
108
113
  });
109
114
 
110
115
  event.parents.push(trackingData.event);
@@ -210,4 +215,38 @@ module.exports.handle = function() {
210
215
  }
211
216
  );
212
217
  }
218
+
219
+ moduleHook.resolve({ name: 'validator', file: 'lib/isEmail' }, (isEmail) => {
220
+ const signature = new Signature('validator.isEmail');
221
+
222
+ function post(data) {
223
+ const trackingData = tracker.getData(data.args[0]);
224
+ // The default options for the two fields of interest are:
225
+ // `{ allow_display_name: false, require_display_name: false }`
226
+ // so we can use an empty object as it will also yield
227
+ // falsy values for these two fields
228
+ const options = data.args[1] ? data.args[1] : {};
229
+ if (data.result && trackingData && !options.allow_display_name && !options.require_display_name) {
230
+ tagRangeUtil.addInPlace(
231
+ trackingData.tagRanges,
232
+ new TagRange(0, data.args[0].length - 1, 'limited-chars')
233
+ );
234
+ tagRangeUtil.removeInPlace(trackingData.tagRanges, ['untrusted']);
235
+
236
+ const context = new CallContext(data);
237
+ const event = new PropagationEvent({
238
+ context,
239
+ signature,
240
+ tagRanges: trackingData.tagRanges,
241
+ source: 'O',
242
+ target: 'R',
243
+ });
244
+
245
+ event.parents.push(trackingData.event);
246
+ trackingData.event = event;
247
+ }
248
+ }
249
+
250
+ return patch(isEmail, 'isEmail', post);
251
+ });
213
252
  };
@@ -32,6 +32,7 @@ module.exports = {
32
32
  isBoolean: 'limited-chars',
33
33
  isBtcAddress: 'alphanum-space-hyphen',
34
34
  isCreditCard: 'limited-chars',
35
+ isDate: 'limited-chars',
35
36
  isDecimal: 'limited-chars',
36
37
  // calls toFloat() which calls isFloat() so no need to hook.
37
38
  //isDivisibleBy: 'limited-chars',
@@ -88,5 +89,10 @@ module.exports = {
88
89
  escape: 'html-encoded'
89
90
  // toFloat uses isFloat which is hooked, so no need to do so again
90
91
  // toFloat: 'limited-chars'
92
+ },
93
+ customLogic: {
94
+ // this value is not used and it's added just for storing the information
95
+ // about what's patched in one place
96
+ isEmail: 'limited-chars'
91
97
  }
92
98
  };
@@ -42,6 +42,7 @@ const constants = {
42
42
  HAPI_ROUTES: 'hapi-routes', // used to instrument registered routes
43
43
  HAPI_ADD_ROUTE: 'hapi-add-route', // used to instrument adding a route
44
44
  HAPI_PRE_AUTH: 'hapi-pre-auth', // used to implement hapi onPreAuth hooks
45
+ HAPI_POST_AUTH: 'hapi-post-auth', // uses to implement hapi onPostAuth hooks
45
46
  HAPI_PRE_HANDLER: 'hapi-pre-handler', // used to implement hapi onPreHandler hooks
46
47
  HAPI_FINISHED: 'hapi-response-finished', // used to implement when hapi response is finished
47
48
  HAPI_PRE_RES: 'hapi-pre-response', // used to implement hapi onPreResponse hooks
@@ -162,12 +163,13 @@ class HapiCore {
162
163
  return h.continue;
163
164
  });
164
165
 
165
- server.ext('onPreHandler', function onPreHandler(request, h) {
166
- agentEmitter.emit(constants.EVENTS.HAPI_PRE_HANDLER, {
166
+ server.ext('onPostAuth', function onPostAuth(request, h) {
167
+ agentEmitter.emit(constants.EVENTS.HAPI_POST_AUTH, {
167
168
  request,
168
169
  h,
169
170
  server
170
171
  });
172
+
171
173
  // Update the domain's model of the request with body and params since it has been
172
174
  // fully parsed and validated
173
175
  decorateRequest({
@@ -176,6 +178,17 @@ class HapiCore {
176
178
  parameters: request.params,
177
179
  query: request.query
178
180
  });
181
+
182
+ return h.continue;
183
+ });
184
+
185
+ server.ext('onPreHandler', function onPreHandler(request, h) {
186
+ agentEmitter.emit(constants.EVENTS.HAPI_PRE_HANDLER, {
187
+ request,
188
+ h,
189
+ server
190
+ });
191
+
179
192
  return h.continue;
180
193
  });
181
194
 
@@ -16,7 +16,6 @@ Copyright: 2022 Contrast Security, Inc
16
16
 
17
17
  const cp = require('child_process');
18
18
  const fs = require('fs');
19
- const os = require('os');
20
19
  const path = require('path');
21
20
  // according to https://nodejs.org/api/process.html#process_process the process
22
21
  // global can be required. We're only doing so here so we can mock it out in tests
@@ -37,6 +36,21 @@ const SuccessConnectionState = require('./success-connection-state');
37
36
 
38
37
  const RESEND_WAIT_MS = 100;
39
38
 
39
+ /**
40
+ * We might have the /bin/* files in /target directory if we're in
41
+ * development. Packaged/installed agents will have the executables in a
42
+ * top-level directory, e.g. /node_modules/@contrast/agent/bin/*
43
+ * @returns {'target' | '.'}
44
+ */
45
+ const maybeTargetDir = () => {
46
+ try {
47
+ fs.statSync(path.resolve(__dirname, '..', '..', '..', 'target'));
48
+ return 'target';
49
+ } catch (error) {
50
+ return '.';
51
+ }
52
+ };
53
+
40
54
  class Speedracer {
41
55
  constructor({ agent, logger }) {
42
56
  this.agent = agent;
@@ -155,7 +169,35 @@ class Speedracer {
155
169
  this.logger.info('starting contrast-service');
156
170
  this.startTime = Date.now();
157
171
 
158
- this.serviceProcess = cp.spawn(this.startCommand, this.startOptions);
172
+ const speedracerPath = path.resolve(
173
+ __dirname,
174
+ '..',
175
+ '..',
176
+ '..',
177
+ maybeTargetDir(),
178
+ 'bin',
179
+ `contrast-service-${process.platform}-${process.arch}`
180
+ );
181
+
182
+ try {
183
+ fs.statSync(speedracerPath);
184
+ } catch (error) {
185
+ this.logger.error(
186
+ 'unable to locate a speedracer binary for the current platform (%s) and architecture (%s)',
187
+ process.platform,
188
+ process.arch
189
+ );
190
+ return Promise.reject();
191
+ }
192
+
193
+ this.serviceProcess = cp.spawn(speedracerPath, {
194
+ env: {
195
+ ...process.env,
196
+ CONTRAST_CONFIG_PATH: this.agent.config.configFile
197
+ },
198
+ stdio: 'ignore',
199
+ windowsHide: true
200
+ });
159
201
 
160
202
  this.serviceProcess.on('error', (err) => {
161
203
  this.serviceProcess = null;
@@ -262,39 +304,6 @@ class Speedracer {
262
304
  }
263
305
  }
264
306
 
265
- /**
266
- * The start command for spawning the speedracer
267
- */
268
- get startCommand() {
269
- return path.resolve(
270
- __dirname,
271
- path.join(
272
- '..',
273
- '..',
274
- '..',
275
- utils.maybeTargetDir(),
276
- 'bin',
277
- utils.getOsDir(),
278
- 'contrast-service'
279
- )
280
- );
281
- }
282
-
283
- /**
284
- * The start options for spawning the speedracer child process. We provide the
285
- * service the location of our config file via environment variable.
286
- */
287
- get startOptions() {
288
- return {
289
- env: {
290
- ...process.env,
291
- CONTRAST_CONFIG_PATH: this.agent.config.configFile
292
- },
293
- stdio: 'ignore',
294
- windowsHide: true
295
- };
296
- }
297
-
298
307
  /**
299
308
  * GRPC is enabled when `agent.service.grpc` is not false, e.g., undefined
300
309
  * defaults to enabled, and `agent.service.socket` is not configured
@@ -314,39 +323,4 @@ class Speedracer {
314
323
  }
315
324
  }
316
325
 
317
- const utils = {
318
- /**
319
- * We might have the /bin/* files in /target directory if we're in
320
- * development. Packaged/installed agents will have the executables in a
321
- * top-level directory, e.g. /node_modules/@contrast/agent/bin/*
322
- * @returns {String}
323
- */
324
- maybeTargetDir() {
325
- try {
326
- fs.statSync(
327
- path.resolve(__dirname, path.join('..', '..', '..', 'target'))
328
- );
329
- return 'target';
330
- } catch (error) {
331
- return '';
332
- }
333
- },
334
- /**
335
- * Map platform values to folder names based on OS. The folder names are
336
- * determined by the packaging of the S-R artifacts and how they unzip.
337
- * @returns {String}
338
- */
339
- getOsDir() {
340
- switch (os.platform()) {
341
- case 'linux':
342
- return 'linux';
343
- case 'darwin':
344
- return 'mac';
345
- case 'win32':
346
- return 'windows';
347
- }
348
- }
349
- };
350
-
351
326
  module.exports = Speedracer;
352
- module.exports.utils = utils;
@@ -12,51 +12,63 @@ 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
- const path = require('path');
15
+ 'use strict';
16
+
16
17
  const fs = require('fs');
18
+ const path = require('path');
19
+ const v8 = require('v8');
17
20
  const logger = require('../core/logger')('contrast:heapDump');
18
- const heapUtil = module.exports;
19
- let heapdump;
20
21
 
21
22
  /**
22
- * setup n heap dumps to be created every n seconds after n seconds.
23
+ * @param {string} localPath location from config.path
24
+ */
25
+ const writeHeapSnapshot = function writeHeapSnapshot(localPath) {
26
+ const dumpPath = path.join(process.cwd(), localPath);
27
+
28
+ fs.mkdir(dumpPath, { recursive: true }, (err) => {
29
+ if (err) {
30
+ logger.error('Unable to create directory for heap snapshots: %o', err);
31
+ return;
32
+ }
33
+
34
+ // create dump at ${path}/${time}.heapdump
35
+ const filename = path.format({
36
+ dir: dumpPath,
37
+ name: `${Date.now()}-contrast`,
38
+ ext: '.heapsnapshot',
39
+ });
40
+
41
+ logger.info('Writing heap snapshot at %s', filename);
42
+ v8.writeHeapSnapshot(filename);
43
+ });
44
+ };
45
+
46
+ /**
47
+ * setup x heap dumps to be created every y seconds after z seconds.
23
48
  * @param {Object} config config blob from config/options.js
24
- * must have: window_ms (Number), delay_ms (Number), dumpPath (String), count (Number)
49
+ * @param {boolean} config.enable
50
+ * @param {string} config.path
51
+ * @param {number} config.count x
52
+ * @param {number} config.window_ms y
53
+ * @param {number} config.delay_ms z
25
54
  */
26
- heapUtil.init = function(config) {
55
+ const init = function init(config) {
27
56
  if (!config.enable) return;
28
- // NODE-1200: make this optional based on if config
29
- // bit is flipped
30
- heapdump = require('@contrast/heapdump');
31
57
 
32
58
  setTimeout(() => {
33
59
  let count = 0;
34
- const timeout = setInterval(() => {
60
+
61
+ const interval = setInterval(() => {
35
62
  if (count >= config.count) {
36
- clearInterval(timeout);
63
+ clearInterval(interval);
37
64
  return;
38
65
  }
39
- heapUtil.buildDump(config.path);
66
+
67
+ writeHeapSnapshot(config.path);
40
68
 
41
69
  count++;
42
70
  }, config.window_ms);
43
71
  }, config.delay_ms).unref();
44
72
  };
45
73
 
46
- heapUtil.buildDump = function(dumpPath) {
47
- // create directory in cwd
48
- dumpPath = `${path.join(process.cwd(), dumpPath)}`;
49
- fs.mkdir(dumpPath, (err) => {
50
- if (!err || (err && err.code == 'EEXIST')) {
51
- // create dump at ${path}/${time}.heapdump
52
- const fileName = `${path.join(
53
- dumpPath,
54
- String(Date.now())
55
- )}-contrast.heapsnapshot`;
56
- logger.info(`Building heap snapshot at ${fileName}`);
57
- heapdump.writeSnapshot(fileName);
58
- } else {
59
- logger.error(`Unable to create directory for heap snapshots: ${err}`);
60
- }
61
- });
62
- };
74
+ module.exports = { writeHeapSnapshot, init };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/agent",
3
- "version": "4.20.2",
3
+ "version": "4.22.0",
4
4
  "description": "Node.js security instrumentation by Contrast Security",
5
5
  "keywords": [
6
6
  "security",
@@ -76,13 +76,12 @@
76
76
  "@babel/template": "^7.10.4",
77
77
  "@babel/traverse": "^7.12.1",
78
78
  "@babel/types": "^7.12.1",
79
- "@contrast/agent-lib": "^4.2.0",
80
- "@contrast/distringuish-prebuilt": "^3.0.1",
79
+ "@contrast/agent-lib": "^4.3.0",
80
+ "@contrast/distringuish-prebuilt": "^3.2.0",
81
81
  "@contrast/flat": "^4.1.1",
82
- "@contrast/fn-inspect": "^3.0.0",
83
- "@contrast/heapdump": "^1.1.0",
82
+ "@contrast/fn-inspect": "^3.1.0",
84
83
  "@contrast/protobuf-api": "^3.2.5",
85
- "@contrast/require-hook": "^3.0.0",
84
+ "@contrast/require-hook": "^3.2.1",
86
85
  "@contrast/synchronous-source-maps": "^1.1.0",
87
86
  "amqp-connection-manager": "^3.2.2",
88
87
  "amqplib": "^0.8.0",