@contrast/agent 4.19.4 → 4.19.5

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.
@@ -37,6 +37,7 @@ const KEYS = {
37
37
  DEFEND: 'defend',
38
38
  FASTIFY_HANDLER_RESOLVED: 'fastify.xss.handler.resolved',
39
39
  FASTIFY_REPLY_SEND_STATE: 'fastify.xss.reply.send.state',
40
+ FINALHANDLER_CB_INDEX: 'finalHandlerCbIndex',
40
41
  HAPI_CALLER: 'hapi.caller',
41
42
  INPUT_EXCLUSIONS: 'defend.exclusions',
42
43
  KOA_CTX: 'koa.ctx',
@@ -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 semver = require('semver');
16
18
  const util = require('util');
17
19
 
@@ -19,6 +21,8 @@ const {
19
21
  AGENT_INFO: { SUPPORTED_NPM_VERSIONS }
20
22
  } = require('./constants');
21
23
 
24
+ const VERSION_REGEX = /^npm@(\S+)\s+(\S+)$/m;
25
+
22
26
  const execFile = util.promisify(require('child_process').execFile);
23
27
 
24
28
  /**
@@ -36,41 +40,61 @@ const execFile = util.promisify(require('child_process').execFile);
36
40
  */
37
41
  module.exports = async function listInstalled(cwd, logger) {
38
42
  const env = { ...process.env, NODE_OPTIONS: undefined };
39
- const args = ['--silent', 'ls', '--json', '--prod', '--long'];
43
+ const args = ['ls', '--json', '--prod', '--long'];
44
+ let stdout;
40
45
 
41
46
  try {
42
- const { stdout: version } = await execFile('npm', ['--version'], {
47
+ const result = await execFile('npm', ['help'], {
43
48
  cwd,
44
49
  env,
45
- shell: true
50
+ shell: true,
46
51
  });
52
+ stdout = result.stdout;
53
+ } catch (err) {
54
+ logger.debug('`npm` returned an error: %o', err);
55
+ // If npm encounters any errors whatsoever it will return with a non-zero
56
+ // exit code but still output the relevant information to stdout.
57
+ // If an even worse error occurs, we may not be able to parse stdout.
58
+ stdout = err.stdout || '';
59
+ }
60
+
61
+ const [, version, location] = stdout.match(VERSION_REGEX) || [];
62
+ if (!version)
63
+ throw new Error(
64
+ 'Unable to locate `npm`. Please enable debug level logs for more information.'
65
+ );
47
66
 
48
- if (semver.gte(version, '7.0.0')) args.push('--all');
49
- logger.debug('using npm version %s', version.trim());
50
- if (!semver.satisfies(version, SUPPORTED_NPM_VERSIONS))
51
- logger.warn(
52
- 'the installed version of npm can cause unexpected behavior. please install a version that satisfies %s',
53
- SUPPORTED_NPM_VERSIONS
54
- );
67
+ logger.debug('using npm version %s at %s', version, location);
55
68
 
56
- const { stdout: list } = await execFile('npm', args, {
69
+ if (semver.gte(version, '7.0.0')) args.push('--all');
70
+ if (!semver.satisfies(version, SUPPORTED_NPM_VERSIONS))
71
+ logger.warn(
72
+ 'The installed version of npm (%s at %s) can cause unexpected behavior. Please install a version that satisfies %s',
73
+ version,
74
+ location,
75
+ SUPPORTED_NPM_VERSIONS
76
+ );
77
+
78
+ try {
79
+ const result = await execFile('npm', args, {
57
80
  cwd,
58
81
  env,
59
82
  shell: true,
60
- maxBuffer: 1024 * 1024 * 128
83
+ maxBuffer: 1024 * 1024 * 128,
61
84
  });
62
85
 
63
- return JSON.parse(list);
86
+ stdout = result.stdout;
64
87
  } catch (err) {
65
- // If app has unmet dependencies the command above will throw an ELSPROBLEMS
66
- // error but still output all the dependencies it finds to stdout.
67
- // If an even worse error occurs, we may not be able to parse stdout.
68
- try {
69
- logger.trace('`npm ls` returned an error: %o', err);
70
- return JSON.parse(err.stdout);
71
- } catch (parseErr) {
72
- logger.trace('parsing the output of `npm ls` failed. %o', parseErr);
73
- throw err;
74
- }
88
+ logger.debug('`npm ls` returned an error: %o', err);
89
+ stdout = err.stdout || '';
90
+ }
91
+
92
+ try {
93
+ return JSON.parse(stdout);
94
+ } catch (err) {
95
+ logger.trace('parsing the output of `npm ls` failed: %o', err);
96
+ throw new Error(
97
+ '`npm ls` failed to provide a list of installed dependencies. Please enable debug level logs for more information.'
98
+ );
75
99
  }
76
100
  };
@@ -12,12 +12,16 @@ 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 ProtectSink = require('./sinks');
16
- const ProectSource = require('./sources');
18
+ const ProtectSource = require('./sources');
19
+ const utils = require('./utils');
17
20
 
18
21
  module.exports = class ExpressInstrumentation {
19
22
  constructor() {
23
+ utils.install();
20
24
  new ProtectSink();
21
- new ProectSource();
25
+ new ProtectSource();
22
26
  }
23
27
  };
@@ -0,0 +1,60 @@
1
+ /**
2
+ Copyright: 2022 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
+ 'use strict';
16
+
17
+ const patcher = require('../../hooks/patcher');
18
+ const moduleHook = require('../../hooks/require');
19
+ const { PATCH_TYPES } = require('../../constants');
20
+ const { AsyncStorage, KEYS } = require('../../core/async-storage');
21
+
22
+ module.exports.install = function () {
23
+ moduleHook.resolve({ name: 'finalhandler' }, (finalhandler) =>
24
+ patcher.patch(finalhandler, {
25
+ name: 'finalHandler',
26
+ patchType: PATCH_TYPES.FRAMEWORK,
27
+ post(data) {
28
+ data.result = patcher.patch(data.result, {
29
+ name: 'finalHandler.returnedFunction',
30
+ patchType: PATCH_TYPES.FRAMEWORK,
31
+ pre(data) {
32
+ const req = AsyncStorage.get(KEYS.REQ);
33
+
34
+ if (!req || !req.__onFinished || !req.__onFinished.queue) {
35
+ data.queueLength = 0;
36
+ return;
37
+ }
38
+ data.queueLength = req.__onFinished.queue.length;
39
+ },
40
+ post(data) {
41
+ if (!('queueLength' in data)) {
42
+ return;
43
+ }
44
+ const req = AsyncStorage.get(KEYS.REQ);
45
+
46
+ if (
47
+ req &&
48
+ req.__onFinished &&
49
+ req.__onFinished.queue &&
50
+ req.__onFinished.queue.length &&
51
+ req.__onFinished.queue.length - data.queueLength == 1
52
+ ) {
53
+ AsyncStorage.set(KEYS.FINALHANDLER_CB_INDEX, data.queueLength);
54
+ }
55
+ },
56
+ });
57
+ },
58
+ })
59
+ );
60
+ };
@@ -36,6 +36,8 @@ const headerValidators = require('./validators');
36
36
  const UserInputKit = require('../reporter/models/utils/user-input-kit');
37
37
  const UserInputFactory = require('../reporter/models/utils/user-input-factory');
38
38
  const blockRequest = require('../util/block-request');
39
+ const { AsyncStorage, KEYS } = require('../core/async-storage');
40
+
39
41
 
40
42
  const evalOptions = { preferWorthWatching: true };
41
43
 
@@ -219,17 +221,32 @@ class ProtectService {
219
221
  if (!rules) {
220
222
  return {};
221
223
  }
222
- // also, if content-type has multipart...
223
- const bodyBuffer = Buffer.concat(chunks);
224
224
 
225
- const findings = this.agentLib.scoreRequestUnknownBody(
225
+ let bodyData = '';
226
+
227
+ if (Array.isArray(chunks)) {
228
+ if (typeof chunks[0] == 'string') {
229
+ const bodyStr = ''.concat('', ...chunks);
230
+ bodyData = Buffer.from(bodyStr).toString('base64');
231
+ } else if (Buffer.isBuffer(chunks[0])) {
232
+ const bodyBuffer = Buffer.concat(chunks);
233
+ bodyData = Uint8Array.from(bodyBuffer);
234
+ } else {
235
+ logger.error('Invalid chunk type');
236
+ }
237
+ } else {
238
+ logger.error('Invalid chunk type');
239
+ }
240
+
241
+ // also, if content-type has multipart...
242
+ const findings = this.agentLib.scoreRequestBody(
226
243
  rules,
227
- bodyBuffer,
244
+ bodyData,
228
245
  evalOptions
229
246
  );
230
247
 
231
248
  // store body buffer on findings for nosqli sink.
232
- findings.bodyBuffer = bodyBuffer;
249
+ findings.bodyBuffer = bodyData;
233
250
  return findings;
234
251
  }
235
252
 
@@ -489,6 +506,11 @@ class ProtectService {
489
506
  * @returns {Boolean} false which halts executing of original method
490
507
  */
491
508
  handleBlockAtPerimeter(res) {
509
+ const finalHandlerCbIndex = AsyncStorage.get(KEYS.FINALHANDLER_CB_INDEX);
510
+ if (finalHandlerCbIndex || finalHandlerCbIndex == 0) {
511
+ const req = AsyncStorage.get(KEYS.REQ);
512
+ req.__onFinished && req.__onFinished.queue && req.__onFinished.queue.splice(finalHandlerCbIndex, 1);
513
+ }
492
514
  blockRequest(res);
493
515
  // halts further execution of user code
494
516
  return false;
@@ -1135,6 +1157,7 @@ class ProtectService {
1135
1157
  * @param {Rule[]} rules Rules from which to build findings
1136
1158
  * @returns {Object[]} The findings from the rules
1137
1159
  */
1160
+ // eslint-disable-next-line default-param-last
1138
1161
  createFindings(rules = [], samples) {
1139
1162
  const findings = [];
1140
1163
  const speedracer = this.reporter.speedracer &&
@@ -42,8 +42,14 @@ function RawRequest(data = {}) {
42
42
  function RawRequestWithBody({ requestId, bodyStr, chunks, buffer }) {
43
43
  let _body = '';
44
44
 
45
- if (chunks) {
46
- _body = Uint8Array.from(Buffer.concat(chunks));
45
+ if (Array.isArray(chunks)) {
46
+ if (typeof chunks[0] == 'string') {
47
+ const bStr = ''.concat('', ...chunks);
48
+ _body = Buffer.from(bStr).toString('base64');
49
+ } else {
50
+ const bBuffer = Buffer.concat(chunks);
51
+ _body = Uint8Array.from(bBuffer);
52
+ }
47
53
  } else if (buffer) {
48
54
  _body = Uint8Array.from(buffer);
49
55
  } else if (bodyStr) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/agent",
3
- "version": "4.19.4",
3
+ "version": "4.19.5",
4
4
  "description": "Node.js security instrumentation by Contrast Security",
5
5
  "keywords": [
6
6
  "security",
@@ -76,7 +76,7 @@
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.0.0",
79
+ "@contrast/agent-lib": "^4.2.0",
80
80
  "@contrast/distringuish-prebuilt": "^2.2.0",
81
81
  "@contrast/flat": "^4.1.1",
82
82
  "@contrast/fn-inspect": "^2.4.4",
@@ -121,7 +121,7 @@
121
121
  "@contrast/screener-service": "^1.12.9",
122
122
  "@hapi/boom": "file:test/mock/boom",
123
123
  "@hapi/hapi": "file:test/mock/hapi",
124
- "@ls-lint/ls-lint": "^1.8.1",
124
+ "@ls-lint/ls-lint": "^1.11.2",
125
125
  "@typescript-eslint/eslint-plugin": "^5.12.1",
126
126
  "@typescript-eslint/parser": "^5.12.1",
127
127
  "ajv": "^8.5.0",