@contrast/assess 1.18.0 → 1.19.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 (47) hide show
  1. package/lib/constants.js +26 -0
  2. package/lib/crypto-analysis/common.js +20 -0
  3. package/lib/crypto-analysis/index.js +44 -0
  4. package/lib/crypto-analysis/install/crypto.js +151 -0
  5. package/lib/crypto-analysis/install/math.js +99 -0
  6. package/lib/dataflow/propagation/install/JSON/parse.js +12 -11
  7. package/lib/dataflow/propagation/install/JSON/stringify.js +1 -1
  8. package/lib/dataflow/propagation/install/ejs/escape-xml.js +2 -2
  9. package/lib/dataflow/propagation/install/ejs/index.js +1 -0
  10. package/lib/dataflow/propagation/install/ejs/template.js +77 -0
  11. package/lib/dataflow/propagation/install/util-format.js +9 -3
  12. package/lib/dataflow/sinks/install/child-process.js +20 -14
  13. package/lib/dataflow/sinks/install/eval.js +16 -14
  14. package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +14 -8
  15. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +12 -5
  16. package/lib/dataflow/sinks/install/fs.js +7 -7
  17. package/lib/dataflow/sinks/install/function.js +8 -12
  18. package/lib/dataflow/sinks/install/http/request.js +16 -8
  19. package/lib/dataflow/sinks/install/http/server-response.js +11 -2
  20. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +15 -8
  21. package/lib/dataflow/sinks/install/libxmljs.js +15 -10
  22. package/lib/dataflow/sinks/install/marsdb.js +13 -8
  23. package/lib/dataflow/sinks/install/mongodb.js +25 -15
  24. package/lib/dataflow/sinks/install/mssql.js +20 -9
  25. package/lib/dataflow/sinks/install/mysql.js +15 -8
  26. package/lib/dataflow/sinks/install/node-serialize.js +15 -17
  27. package/lib/dataflow/sinks/install/postgres.js +17 -4
  28. package/lib/dataflow/sinks/install/sequelize.js +16 -9
  29. package/lib/dataflow/sinks/install/sqlite3.js +20 -7
  30. package/lib/dataflow/sinks/install/vm.js +19 -17
  31. package/lib/dataflow/sources/install/http.js +14 -42
  32. package/lib/dataflow/sources/install/koa/index.js +1 -0
  33. package/lib/dataflow/sources/install/koa/koa-multer.js +102 -0
  34. package/lib/dataflow/sources/install/multer1.js +25 -51
  35. package/lib/dataflow/sources/install/querystring.js +1 -4
  36. package/lib/event-factory.js +47 -0
  37. package/lib/get-policy.js +68 -0
  38. package/lib/get-source-context.js +62 -0
  39. package/lib/index.d.ts +50 -0
  40. package/lib/index.js +20 -19
  41. package/lib/make-source-context.js +74 -0
  42. package/lib/response-scanning/handlers/index.js +55 -28
  43. package/lib/response-scanning/install/http.js +13 -7
  44. package/lib/rule-scopes.js +48 -0
  45. package/lib/session-configuration/handlers.js +4 -3
  46. package/lib/session-configuration/install/express-session.js +8 -2
  47. package/package.json +2 -2
@@ -17,8 +17,7 @@
17
17
 
18
18
  const { patchType } = require('../common');
19
19
  const {
20
- Rule: { SQL_INJECTION },
21
- isString,
20
+ Rule: { SQL_INJECTION: ruleId },
22
21
  DataflowTag: {
23
22
  CUSTOM_ENCODED_SQL_INJECTION,
24
23
  CUSTOM_ENCODED,
@@ -28,8 +27,10 @@ const {
28
27
  LIMITED_CHARS,
29
28
  UNTRUSTED
30
29
  },
31
- inspect
30
+ isString,
31
+ inspect,
32
32
  } = require('@contrast/common');
33
+ const { InstrumentationType: { RULE } } = require('../../../constants');
33
34
 
34
35
  const safeTags = [
35
36
  CUSTOM_ENCODED_SQL_INJECTION,
@@ -40,12 +41,19 @@ const safeTags = [
40
41
  LIMITED_CHARS,
41
42
  ];
42
43
 
44
+ /**
45
+ * @param {{
46
+ * assess: import('@contrast/assess').Assess,
47
+ * config: import('@contrast/config').Config,
48
+ * }} core
49
+ * @returns {import('@contrast/common').Installable}
50
+ */
43
51
  module.exports = function(core) {
44
52
  const {
45
53
  depHooks,
46
54
  patcher,
47
- scopes: { sources },
48
55
  assess: {
56
+ getSourceContext,
49
57
  eventFactory: { createSinkEvent },
50
58
  dataflow: {
51
59
  tracker,
@@ -65,11 +73,10 @@ module.exports = function(core) {
65
73
  }
66
74
 
67
75
  const pre = (module, file, obj, method) => (data) => {
68
- const store = sources.getStore()?.assess;
69
76
  if (
70
- !store ||
77
+ !getSourceContext(RULE, ruleId) ||
71
78
  !data.args[0] ||
72
- isLocked(SQL_INJECTION)
79
+ isLocked(ruleId)
73
80
  ) return;
74
81
 
75
82
  const val = getValueFromArgs(data.args);
@@ -106,7 +113,7 @@ module.exports = function(core) {
106
113
 
107
114
  if (event) {
108
115
  reportFindings({
109
- ruleId: SQL_INJECTION,
116
+ ruleId,
110
117
  sinkEvent: event,
111
118
  });
112
119
  }
@@ -15,21 +15,29 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { patchType } = require('../common');
19
18
  const {
20
- Rule: { UNTRUSTED_DESERIALIZATION },
19
+ Rule: { UNTRUSTED_DESERIALIZATION: ruleId },
21
20
  isString,
22
21
  DataflowTag: {
23
22
  UNTRUSTED
24
23
  }
25
24
  } = require('@contrast/common');
25
+ const { InstrumentationType: { RULE } } = require('../../../constants');
26
+ const { patchType } = require('../common');
26
27
 
28
+ /**
29
+ * @param {{
30
+ * assess: import('@contrast/assess').Assess,
31
+ * config: import('@contrast/config').Config,
32
+ * }} core
33
+ * @returns {import('@contrast/common').Installable}
34
+ */
27
35
  module.exports = function(core) {
28
36
  const {
29
37
  depHooks,
30
38
  patcher,
31
- scopes: { sources, instrumentation },
32
39
  assess: {
40
+ getSourceContext,
33
41
  eventFactory: { createSinkEvent },
34
42
  dataflow: {
35
43
  tracker,
@@ -45,23 +53,13 @@ module.exports = function(core) {
45
53
  name: 'node-serialize.unserialize',
46
54
  patchType,
47
55
  pre(data) {
48
- const store = sources.getStore()?.assess;
49
- if (
50
- !store ||
51
- !data.args[0] ||
52
- instrumentation.isLocked()
53
- ) return;
56
+ if (!getSourceContext(RULE, ruleId) || !data.args[0]) return;
54
57
 
55
58
  const [input] = data.args;
56
-
57
- if (!isString(input)) {
58
- return;
59
- }
59
+ if (!isString(input)) return;
60
60
 
61
61
  const strInfo = tracker.getData(input);
62
- if (!strInfo || !isVulnerable(UNTRUSTED, [], strInfo.tags)) {
63
- return;
64
- }
62
+ if (!strInfo || !isVulnerable(UNTRUSTED, [], strInfo.tags)) return;
65
63
 
66
64
  const sinkEvent = createSinkEvent({
67
65
  name: 'node-serialize.unserialize',
@@ -89,7 +87,7 @@ module.exports = function(core) {
89
87
 
90
88
  if (sinkEvent) {
91
89
  reportFindings({
92
- ruleId: UNTRUSTED_DESERIALIZATION,
90
+ ruleId,
93
91
  sinkEvent,
94
92
  });
95
93
  }
@@ -17,19 +17,33 @@
17
17
 
18
18
  const util = require('util');
19
19
  const {
20
- DataflowTag: { UNTRUSTED, SQL_ENCODED, LIMITED_CHARS, CUSTOM_VALIDATED, CUSTOM_ENCODED },
20
+ DataflowTag: {
21
+ CUSTOM_VALIDATED,
22
+ CUSTOM_ENCODED,
23
+ LIMITED_CHARS,
24
+ SQL_ENCODED,
25
+ UNTRUSTED,
26
+ },
21
27
  Rule: { SQL_INJECTION: ruleId },
22
28
  isString,
23
29
  } = require('@contrast/common');
30
+ const { InstrumentationType: { RULE } } = require('../../../constants');
24
31
  const { filterSafeTags, patchType } = require('../common');
25
32
 
33
+ /**
34
+ * @param {{
35
+ * assess: import('@contrast/assess').Assess,
36
+ * config: import('@contrast/config').Config,
37
+ * }} core
38
+ * @returns {import('@contrast/common').Installable}
39
+ */
26
40
  module.exports = function(core) {
27
41
  const {
28
42
  config,
29
43
  depHooks,
30
44
  patcher,
31
- scopes: { sources },
32
45
  assess: {
46
+ getSourceContext,
33
47
  eventFactory: { createSinkEvent },
34
48
  dataflow: {
35
49
  tracker,
@@ -50,8 +64,7 @@ module.exports = function(core) {
50
64
  const postgres = core.assess.dataflow.sinks.postgres = {};
51
65
 
52
66
  const preHook = (methodSignature) => (data) => {
53
- const assessStore = sources.getStore()?.assess;
54
- if (!assessStore || isLocked(ruleId)) return;
67
+ if (!getSourceContext(RULE, ruleId) || isLocked(ruleId)) return;
55
68
 
56
69
  const [arg0] = data.args;
57
70
  const query = arg0?.text || arg0;
@@ -17,7 +17,7 @@
17
17
 
18
18
  const util = require('util');
19
19
  const {
20
- Rule: { SQL_INJECTION },
20
+ Rule: { SQL_INJECTION: ruleId },
21
21
  DataflowTag: {
22
22
  UNTRUSTED,
23
23
  SQL_ENCODED,
@@ -26,15 +26,23 @@ const {
26
26
  CUSTOM_ENCODED,
27
27
  },
28
28
  } = require('@contrast/common');
29
+ const { InstrumentationType: { RULE } } = require('../../../constants');
29
30
  const { patchType, filterSafeTags } = require('../common');
30
31
 
32
+ /**
33
+ * @param {{
34
+ * assess: import('@contrast/assess').Assess,
35
+ * config: import('@contrast/config').Config,
36
+ * }} core
37
+ * @returns {import('@contrast/common').Installable}
38
+ */
31
39
  module.exports = function(core) {
32
40
  const {
33
41
  depHooks,
34
42
  patcher,
35
43
  config,
36
- scopes: { sources },
37
44
  assess: {
45
+ getSourceContext,
38
46
  eventFactory: { createSinkEvent },
39
47
  dataflow: {
40
48
  tracker,
@@ -61,10 +69,9 @@ module.exports = function(core) {
61
69
  name: sequelizeQueryPatchName,
62
70
  patchType,
63
71
  around(next, data) {
64
- const { args, hooked, orig } = data;
65
- const sourceContext = sources.getStore()?.assess;
66
- if (!sourceContext || !args[0]) return next();
72
+ if (!getSourceContext(RULE, ruleId) || !data.args[0]) return next();
67
73
 
74
+ const { args, hooked, orig } = data;
68
75
  const query = typeof args[0] === 'string' ? args[0] : args[0].query;
69
76
 
70
77
  try {
@@ -74,7 +81,7 @@ module.exports = function(core) {
74
81
  if (queryInfo && !isVulnerableQuery && config.assess.safe_positives.enable) {
75
82
  reportSafePositive({
76
83
  name: sequelizeQueryPatchName,
77
- ruleId: SQL_INJECTION,
84
+ ruleId,
78
85
  safeTags: filterSafeTags(safeTags, queryInfo),
79
86
  strInfo: {
80
87
  value: queryInfo?.value,
@@ -87,7 +94,7 @@ module.exports = function(core) {
87
94
  !queryInfo ||
88
95
  !isVulnerableQuery
89
96
  ) {
90
- return runInActiveSink(SQL_INJECTION, async () => await next());
97
+ return runInActiveSink(ruleId, async () => await next());
91
98
  }
92
99
 
93
100
  const sqlValue =
@@ -122,7 +129,7 @@ module.exports = function(core) {
122
129
 
123
130
  if (event) {
124
131
  reportFindings({
125
- ruleId: SQL_INJECTION,
132
+ ruleId,
126
133
  sinkEvent: event,
127
134
  });
128
135
  }
@@ -134,7 +141,7 @@ module.exports = function(core) {
134
141
  );
135
142
  }
136
143
 
137
- return runInActiveSink(SQL_INJECTION, async () => await next());
144
+ return runInActiveSink(ruleId, async () => await next());
138
145
  },
139
146
  });
140
147
  });
@@ -17,10 +17,17 @@
17
17
 
18
18
  const { patchType } = require('../common');
19
19
  const {
20
- DataflowTag: { UNTRUSTED, SQL_ENCODED, LIMITED_CHARS, CUSTOM_VALIDATED, CUSTOM_ENCODED },
21
- Rule: { SQL_INJECTION },
20
+ DataflowTag: {
21
+ CUSTOM_VALIDATED,
22
+ CUSTOM_ENCODED,
23
+ LIMITED_CHARS,
24
+ SQL_ENCODED,
25
+ UNTRUSTED,
26
+ },
27
+ Rule: { SQL_INJECTION: ruleId },
22
28
  isString
23
29
  } = require('@contrast/common');
30
+ const { InstrumentationType: { RULE } } = require('../../../constants');
24
31
 
25
32
  const safeTags = [
26
33
  SQL_ENCODED,
@@ -29,12 +36,19 @@ const safeTags = [
29
36
  CUSTOM_ENCODED,
30
37
  ];
31
38
 
39
+ /**
40
+ * @param {{
41
+ * assess: import('@contrast/assess').Assess,
42
+ * config: import('@contrast/config').Config,
43
+ * }} core
44
+ * @returns {import('@contrast/common').Installable}
45
+ */
32
46
  module.exports = function(core) {
33
47
  const {
34
48
  depHooks,
35
49
  patcher,
36
- scopes: { sources },
37
50
  assess: {
51
+ getSourceContext,
38
52
  eventFactory: { createSinkEvent },
39
53
  dataflow: {
40
54
  tracker,
@@ -44,12 +58,11 @@ module.exports = function(core) {
44
58
  } = core;
45
59
 
46
60
  const pre = (name, method) => (data) => {
47
- const store = sources.getStore()?.assess;
48
61
  if (
49
- !store ||
62
+ !getSourceContext(RULE, ruleId) ||
50
63
  !data.args[0] ||
51
64
  !isString(data.args[0]) ||
52
- isLocked(SQL_INJECTION)
65
+ isLocked(ruleId)
53
66
  ) return;
54
67
 
55
68
  const strInfo = tracker.getData(data.args[0]);
@@ -83,7 +96,7 @@ module.exports = function(core) {
83
96
 
84
97
  if (event) {
85
98
  reportFindings({
86
- ruleId: SQL_INJECTION,
99
+ ruleId,
87
100
  sinkEvent: event,
88
101
  });
89
102
  }
@@ -16,7 +16,6 @@
16
16
  'use strict';
17
17
  const { patchType, filterSafeTags } = require('../common');
18
18
  const {
19
- isString,
20
19
  DataflowTag: {
21
20
  UNTRUSTED,
22
21
  CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
@@ -25,14 +24,17 @@ const {
25
24
  CUSTOM_VALIDATED,
26
25
  LIMITED_CHARS,
27
26
  },
28
- Rule: { UNSAFE_CODE_EXECUTION },
29
- isNonEmptyObject,
27
+ Rule: { UNSAFE_CODE_EXECUTION: ruleId },
30
28
  inspect,
31
- traverseValues,
29
+ isNonEmptyObject,
30
+ isString,
32
31
  join,
33
32
  split,
33
+ traverseValues,
34
34
  } = require('@contrast/common');
35
+ const { InstrumentationType: { RULE } } = require('../../../constants');
35
36
  const { createAdjustedQueryTags } = require('../../tag-utils');
37
+
36
38
  const safeTags = [
37
39
  CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
38
40
  CUSTOM_ENCODED,
@@ -41,13 +43,20 @@ const safeTags = [
41
43
  LIMITED_CHARS,
42
44
  ];
43
45
 
46
+ /**
47
+ * @param {{
48
+ * assess: import('@contrast/assess').Assess,
49
+ * config: import('@contrast/config').Config,
50
+ * }} core
51
+ * @returns {import('@contrast/common').Installable}
52
+ */
44
53
  module.exports = function (core) {
45
54
  const {
46
55
  config,
47
56
  depHooks,
48
57
  patcher,
49
- scopes: { sources, instrumentation },
50
58
  assess: {
59
+ getSourceContext,
51
60
  eventFactory: { createSinkEvent },
52
61
  dataflow: {
53
62
  tracker,
@@ -133,14 +142,7 @@ module.exports = function (core) {
133
142
  }
134
143
 
135
144
  function around(next, { args: origArgs, hooked, orig, name }) {
136
- const store = sources.getStore()?.assess;
137
- if (
138
- !store ||
139
- instrumentation.isLocked() ||
140
- isLocked(UNSAFE_CODE_EXECUTION)
141
- ) {
142
- return next();
143
- }
145
+ if (!getSourceContext(RULE, ruleId) || isLocked(ruleId)) return next();
144
146
 
145
147
  const methodPath = split(name, '.');
146
148
  const method = methodPath[methodPath.length - 1];
@@ -189,13 +191,13 @@ module.exports = function (core) {
189
191
 
190
192
  reportSafePositive({
191
193
  name,
192
- ruleId: UNSAFE_CODE_EXECUTION,
194
+ ruleId,
193
195
  safeTags: Array.from(foundSafeTags),
194
196
  strInfo,
195
197
  });
196
198
 
197
199
  return name === 'vm.runInNewContext'
198
- ? runInActiveSink(UNSAFE_CODE_EXECUTION, () => next())
200
+ ? runInActiveSink(ruleId, () => next())
199
201
  : next();
200
202
  }
201
203
 
@@ -233,14 +235,14 @@ module.exports = function (core) {
233
235
 
234
236
  if (event) {
235
237
  reportFindings({
236
- ruleId: UNSAFE_CODE_EXECUTION,
238
+ ruleId,
237
239
  sinkEvent: event,
238
240
  });
239
241
  }
240
242
  }
241
243
 
242
244
  return name === 'vm.runInNewContext'
243
- ? runInActiveSink(UNSAFE_CODE_EXECUTION, () => next())
245
+ ? runInActiveSink(ruleId, () => next())
244
246
  : next();
245
247
  }
246
248
 
@@ -17,12 +17,17 @@
17
17
  const { patchType } = require('../common');
18
18
  const { toLowerCase, InputType } = require('@contrast/common');
19
19
 
20
+ /**
21
+ * @param {{
22
+ * assess: import('@contrast/assess').Assess,
23
+ * }} core
24
+ */
20
25
  module.exports = function(core) {
21
26
  const {
22
- scopes,
27
+ assess: { dataflow, makeSourceContext },
23
28
  instrumentation: { instrument },
24
- assess: { dataflow },
25
29
  patcher,
30
+ scopes,
26
31
  } = core;
27
32
 
28
33
  const logger = core.logger.child('contrast:assess');
@@ -34,19 +39,20 @@ module.exports = function(core) {
34
39
  function around(next, data) {
35
40
  const [type] = data.args;
36
41
 
37
- if (type !== 'request') {
38
- return next();
39
- }
42
+ if (type !== 'request') return next();
40
43
 
41
44
  try {
42
45
  const [, req, res] = data.args;
43
46
  const store = scopes.sources.getStore();
44
47
 
45
48
  if (!store) {
46
- logger.debug('cannot acquire store for assess request handling');
47
- return next();
49
+ // this would indicate that sources did not install correctly
50
+ throw new Error('async request store not found');
48
51
  }
49
52
 
53
+ store.assess = makeSourceContext(req, res);
54
+ if (!store.assess) return;
55
+
50
56
  patcher.patch(res, 'writeHead', {
51
57
  name: 'write-head',
52
58
  patchType,
@@ -86,27 +92,7 @@ module.exports = function(core) {
86
92
  });
87
93
  }
88
94
 
89
- let uriPath, queries;
90
- const ix = req.url.indexOf('?');
91
-
92
- if (ix >= 0) {
93
- uriPath = req.url.slice(0, ix);
94
- queries = req.url.slice(ix + 1);
95
- } else {
96
- uriPath = req.url;
97
- queries = '';
98
- }
99
-
100
- const headers = {};
101
95
  const sourceName = 'ClientRequest';
102
-
103
- store.assess = {
104
- responseData: {},
105
- sourceEventsCount: 0,
106
- propagationEventsCount: 0,
107
- findings: {},
108
- };
109
-
110
96
  const sourceInfo = {
111
97
  name: sourceName,
112
98
  stacktraceOpts: {
@@ -141,24 +127,10 @@ module.exports = function(core) {
141
127
 
142
128
  for (let i = 0; i < req.rawHeaders.length; i += 2) {
143
129
  const header = toLowerCase(req.rawHeaders[i]);
144
- headers[header] = req.rawHeaders[i + 1];
145
130
  req.rawHeaders[i + 1] = req.headers[header];
146
131
  }
147
-
148
- const contentType = headers['content-type'] && toLowerCase(headers['content-type']);
149
-
150
- store.assess.reqData = {
151
- ip: req.socket.remoteAddress,
152
- httpVersion: req.httpVersion,
153
- method: req.method,
154
- headers,
155
- uriPath,
156
- queries,
157
- contentType,
158
- };
159
-
160
132
  } catch (err) {
161
- logger.error({ err }, 'Error during assess request handling');
133
+ logger.error({ err }, 'Error during Assess request handling');
162
134
  }
163
135
 
164
136
  setImmediate(() => {
@@ -22,6 +22,7 @@ module.exports = function(core) {
22
22
 
23
23
  require('./koa2')(core);
24
24
  require('./koa-bodyparsers')(core);
25
+ require('./koa-multer')(core);
25
26
  require('./koa-routers')(core);
26
27
 
27
28
  koaSources.install = function install() {
@@ -0,0 +1,102 @@
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
+ const { patchType } = require('../../common');
18
+ const { InputType } = require('@contrast/common');
19
+
20
+ module.exports = (core) => {
21
+ const {
22
+ depHooks,
23
+ logger,
24
+ patcher,
25
+ scopes,
26
+ assess: { dataflow: { sources } },
27
+ } = core;
28
+
29
+ function handler(req, constructorOpt) {
30
+ const sourceContext = scopes.sources.getStore()?.assess;
31
+ if (!sourceContext) return;
32
+
33
+ function handle(context, data, key) {
34
+ try {
35
+ sources.handle({
36
+ context,
37
+ data,
38
+ keys: [key],
39
+ name: 'multer',
40
+ inputType: InputType.BODY,
41
+ sourceContext,
42
+ stacktraceOpts: {
43
+ constructorOpt,
44
+ },
45
+ });
46
+ } catch (err) {
47
+ logger.error({ err }, 'error handling Koa multer Assess dataflow %s.%s source', context, key);
48
+ }
49
+ }
50
+
51
+ if (req.file) {
52
+ handle('req', req, 'file');
53
+ }
54
+
55
+ if (Array.isArray(req.files)) {
56
+ for (let i = 0; i < req.files.length; i++) {
57
+ handle('req.files', req.files[i], i);
58
+ }
59
+ }
60
+
61
+ if (req.body && Object.keys(req.body).length) {
62
+ handle('req', req, 'body');
63
+ }
64
+ }
65
+
66
+ function install() {
67
+ ['koa-multer', '@koa/multer'].forEach((name) => {
68
+ depHooks.resolve(
69
+ { name }, (_export) => {
70
+ const origMulter = _export;
71
+ return patcher.patch(_export, {
72
+ name,
73
+ patchType,
74
+ post(data) {
75
+ const { args, hooked } = data;
76
+ const instance = origMulter.apply(this, args);
77
+ const origMake = instance._makeMiddleware;
78
+ instance._makeMiddleware = function _makeMiddleware(...args) {
79
+ const origMulterMiddleware = origMake.apply(this, args);
80
+ return function multerMiddleware(req, res, origNext) {
81
+
82
+ const next = function(...args) {
83
+ handler(req, hooked);
84
+ return origNext.apply(this, args);
85
+ };
86
+ return origMulterMiddleware.apply(this, [req, res, next]);
87
+ };
88
+ };
89
+ data.result = instance;
90
+ }
91
+ });
92
+ }
93
+ );
94
+ });
95
+ }
96
+
97
+ const koaMulterInstrumentation = sources.koaInstrumentation.koaMulter = {
98
+ install
99
+ };
100
+
101
+ return koaMulterInstrumentation;
102
+ };