@contrast/assess 1.28.1 → 1.29.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 (56) hide show
  1. package/lib/crypto-analysis/install/crypto.js +2 -2
  2. package/lib/dataflow/propagation/install/JSON/parse-fn.js +5 -5
  3. package/lib/dataflow/propagation/install/JSON/parse.js +1 -1
  4. package/lib/dataflow/propagation/install/JSON/stringify.js +17 -9
  5. package/lib/dataflow/propagation/install/array-prototype-join.js +7 -6
  6. package/lib/dataflow/propagation/install/buffer.js +60 -2
  7. package/lib/dataflow/propagation/install/ejs/template.js +3 -3
  8. package/lib/dataflow/propagation/install/joi/boolean.js +3 -1
  9. package/lib/dataflow/propagation/install/joi/expression.js +3 -1
  10. package/lib/dataflow/propagation/install/joi/keys.js +5 -4
  11. package/lib/dataflow/propagation/install/joi/number.js +3 -1
  12. package/lib/dataflow/propagation/install/joi/string-schema.js +1 -5
  13. package/lib/dataflow/propagation/install/joi/utils.js +9 -5
  14. package/lib/dataflow/propagation/install/joi/values.js +3 -6
  15. package/lib/dataflow/propagation/install/mongoose/schema-map.js +2 -2
  16. package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +2 -2
  17. package/lib/dataflow/propagation/install/mongoose/schema-string.js +2 -2
  18. package/lib/dataflow/propagation/install/path/basename.js +2 -2
  19. package/lib/dataflow/propagation/install/path/common.js +5 -5
  20. package/lib/dataflow/propagation/install/path/format.js +2 -2
  21. package/lib/dataflow/propagation/install/path/join-and-resolve.js +2 -2
  22. package/lib/dataflow/propagation/install/querystring/parse.js +4 -3
  23. package/lib/dataflow/propagation/install/send.js +2 -2
  24. package/lib/dataflow/propagation/install/string/concat.js +3 -3
  25. package/lib/dataflow/propagation/install/string/index.js +3 -2
  26. package/lib/dataflow/propagation/install/string/match-all.js +0 -1
  27. package/lib/dataflow/propagation/install/string/match.js +2 -2
  28. package/lib/dataflow/propagation/install/string/replace.js +6 -6
  29. package/lib/dataflow/propagation/install/string/slice.js +2 -2
  30. package/lib/dataflow/propagation/install/string/split.js +2 -2
  31. package/lib/dataflow/propagation/install/string/substring.js +2 -2
  32. package/lib/dataflow/sinks/index.js +1 -0
  33. package/lib/dataflow/sinks/install/child-process.js +3 -3
  34. package/lib/dataflow/sinks/install/fs.js +2 -2
  35. package/lib/dataflow/sinks/install/function.js +2 -2
  36. package/lib/dataflow/sinks/install/restify.js +208 -0
  37. package/lib/dataflow/sinks/install/vm.js +4 -4
  38. package/lib/dataflow/sources/handler.js +2 -2
  39. package/lib/dataflow/sources/index.js +1 -0
  40. package/lib/dataflow/sources/install/http.js +4 -4
  41. package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.js +85 -0
  42. package/lib/dataflow/sources/install/restify/index.js +32 -0
  43. package/lib/dataflow/sources/install/restify/jsonBodyParser.js +109 -0
  44. package/lib/dataflow/sources/install/restify/router.js +77 -0
  45. package/lib/dataflow/tag-utils.js +4 -4
  46. package/lib/dataflow/tracker.js +1 -0
  47. package/lib/event-factory.js +3 -3
  48. package/lib/get-policy.js +2 -2
  49. package/lib/index.d.ts +18 -0
  50. package/lib/make-source-context.js +2 -2
  51. package/lib/response-scanning/handlers/index.js +10 -10
  52. package/lib/response-scanning/handlers/utils.js +19 -12
  53. package/lib/response-scanning/install/http.js +9 -59
  54. package/lib/session-configuration/install/express-session.js +2 -2
  55. package/lib/session-configuration/install/fastify-cookie.js +2 -2
  56. package/package.json +4 -4
@@ -15,7 +15,8 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { callChildComponentMethodsSync, split } = require('@contrast/common');
18
+ const { callChildComponentMethodsSync } = require('@contrast/common');
19
+ const { StringPrototypeSplit } = require('@contrast/common');
19
20
  const { getAdjustedUntrackedValue } = require('../../../tag-utils');
20
21
 
21
22
  module.exports = function(core) {
@@ -40,7 +41,7 @@ module.exports = function(core) {
40
41
  };
41
42
 
42
43
  function patchCustomMatcher(matcherFn, objInfo, methodArg, name, patchType) {
43
- const [, , methodName] = split(name, '.');
44
+ const [, , methodName] = StringPrototypeSplit.call(name, '.');
44
45
 
45
46
  return patcher.patch(matcherFn, {
46
47
  name,
@@ -14,7 +14,6 @@
14
14
  */
15
15
 
16
16
  'use strict';
17
-
18
17
  const { InstrumentationType: { PROPAGATOR } } = require('../../../../constants');
19
18
  const { createSubsetTags } = require('../../../tag-utils');
20
19
  const { patchType } = require('../../common');
@@ -14,7 +14,7 @@
14
14
  */
15
15
 
16
16
  'use strict';
17
- const { join } = require('@contrast/common');
17
+ const { ArrayPrototypeJoin } = require('@contrast/common');
18
18
  const { patchType } = require('../../common');
19
19
  const { createSubsetTags, getAdjustedUntrackedValue } = require('../../../tag-utils');
20
20
 
@@ -59,7 +59,7 @@ module.exports = function(core) {
59
59
  args,
60
60
  tags,
61
61
  result: {
62
- value: join(result),
62
+ value: ArrayPrototypeJoin.call(result),
63
63
  tracked: false,
64
64
  },
65
65
  stacktraceOpts: {
@@ -17,9 +17,9 @@
17
17
 
18
18
  const {
19
19
  DataflowTag: { UNTRUSTED },
20
- match: origMatch,
21
- join,
22
- substring
20
+ StringPrototypeMatch,
21
+ ArrayPrototypeJoin,
22
+ StringPrototypeSubstring,
23
23
  } = require('@contrast/common');
24
24
  const { InstrumentationType: { PROPAGATOR } } = require('../../../../constants');
25
25
  const {
@@ -73,7 +73,7 @@ module.exports = function(core) {
73
73
  replace: str.substring(str.indexOf(match) + match.length, str.length)
74
74
  }
75
75
  ].forEach(({ regex, replace }) => {
76
- if (ret && origMatch(ret, regex)) {
76
+ if (ret && StringPrototypeMatch.call(ret, regex)) {
77
77
  // If the match string is tracked, we can actually use the patched replace
78
78
  // to keep track of its tag ranges
79
79
  if (tracker.getData(replace)) {
@@ -87,7 +87,7 @@ module.exports = function(core) {
87
87
  const numberedGroupMatches = replacementType !== 'function' && replacement.match(/\$[1-9][0-9]|\$[1-9]/g);
88
88
  if (numberedGroupMatches) {
89
89
  numberedGroupMatches.forEach((numberedGroup) => {
90
- const group = Number(substring(numberedGroup, 1));
90
+ const group = Number(StringPrototypeSubstring.call(numberedGroup, 1));
91
91
  ret = origReplace.call(ret, numberedGroup, captureGroups[group - 1] || '');
92
92
  });
93
93
  }
@@ -187,7 +187,7 @@ module.exports = function(core) {
187
187
  name,
188
188
  moduleName: 'String',
189
189
  methodName: 'prototype.replace',
190
- context: `'${obj}'.replace(${join(args.map(a => a.value))})`,
190
+ context: `'${obj}'.replace(${ArrayPrototypeJoin.call(args.map(a => a.value))})`,
191
191
  history: Array.from(data._history),
192
192
  object: {
193
193
  value: obj,
@@ -13,7 +13,7 @@
13
13
  * way not consistent with the End User License Agreement.
14
14
  */
15
15
  'use strict';
16
- const { join } = require('@contrast/common');
16
+ const { ArrayPrototypeJoin } = require('@contrast/common');
17
17
  const { InstrumentationType: { PROPAGATOR } } = require('../../../../constants');
18
18
  const { createSubsetTags, getAdjustedUntrackedValue } = require('../../../tag-utils');
19
19
  const { patchType } = require('../../common');
@@ -80,7 +80,7 @@ module.exports = function(core) {
80
80
  name,
81
81
  moduleName: 'String',
82
82
  methodName: 'prototype.slice',
83
- context: `'${objInfo.value}'.slice(${join(args.map(a => a.value), ', ')})`,
83
+ context: `'${objInfo.value}'.slice(${ArrayPrototypeJoin.call(args.map(a => a.value), ', ')})`,
84
84
  history: [objInfo],
85
85
  object: {
86
86
  value: obj,
@@ -15,7 +15,7 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { join } = require('@contrast/common');
18
+ const { ArrayPrototypeJoin } = require('@contrast/common');
19
19
  const { InstrumentationType: { PROPAGATOR } } = require('../../../../constants');
20
20
  const { createSubsetTags, getAdjustedUntrackedValue } = require('../../../tag-utils');
21
21
  const { patchType } = require('../../common');
@@ -63,7 +63,7 @@ module.exports = function(core) {
63
63
  name,
64
64
  moduleName: 'String',
65
65
  methodName: 'prototype.split',
66
- context: `'${objInfo.value}'.split(${join(args.map(a => a.value))})`,
66
+ context: `'${objInfo.value}'.split(${ArrayPrototypeJoin.call(args.map(a => a.value))})`,
67
67
  history: [objInfo],
68
68
  object: {
69
69
  value: obj,
@@ -15,7 +15,7 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { join } = require('@contrast/common');
18
+ const { ArrayPrototypeJoin } = require('@contrast/common');
19
19
  const { InstrumentationType: { PROPAGATOR } } = require('../../../../constants');
20
20
  const { createSubsetTags, getAdjustedUntrackedValue } = require('../../../tag-utils');
21
21
  const { patchType } = require('../../common');
@@ -90,7 +90,7 @@ module.exports = function(core) {
90
90
  name,
91
91
  moduleName: 'String',
92
92
  methodName: 'prototype.substring',
93
- context: `'${objInfo.value}'.substring(${join(args.map(a => a.value))})`,
93
+ context: `'${objInfo.value}'.substring(${ArrayPrototypeJoin.call(args.map(a => a.value))})`,
94
94
  history: [objInfo],
95
95
  object: {
96
96
  value: obj,
@@ -82,6 +82,7 @@ module.exports = function (core) {
82
82
  require('./install/mysql')(core);
83
83
  require('./install/node-serialize')(core);
84
84
  require('./install/postgres')(core);
85
+ require('./install/restify')(core);
85
86
  require('./install/sequelize')(core);
86
87
  require('./install/sqlite3')(core);
87
88
  require('./install/vm')(core);
@@ -16,7 +16,7 @@
16
16
  'use strict';
17
17
  const {
18
18
  DataflowTag: { UNTRUSTED },
19
- join,
19
+ ArrayPrototypeJoin,
20
20
  Rule: { CMD_INJECTION: ruleId },
21
21
  isString,
22
22
  } = require('@contrast/common');
@@ -81,7 +81,7 @@ module.exports = function(core) {
81
81
  name,
82
82
  moduleName: 'child_process',
83
83
  methodName: method,
84
- context: `child_process.${method}(${join(args.map((a, idx) => idx === 0 ? `'${a.value}'` : a.value), ', ')})`,
84
+ context: `child_process.${method}(${ArrayPrototypeJoin.call(args.map((a, idx) => idx === 0 ? `'${a.value}'` : a.value), ', ')})`,
85
85
  history: [strInfo],
86
86
  object: {
87
87
  value: 'child_process',
@@ -167,7 +167,7 @@ module.exports = function(core) {
167
167
  name,
168
168
  moduleName: 'child_process',
169
169
  methodName: method,
170
- context: `child_process.${method}(${join(args.map((a, idx) => idx === 0 ? `'${a.value}'` : a.value), ', ')})`,
170
+ context: `child_process.${method}(${ArrayPrototypeJoin.call(args.map((a, idx) => idx === 0 ? `'${a.value}'` : a.value), ', ')})`,
171
171
  history: [trackedArgs[vulnerableArgIdx]],
172
172
  object: {
173
173
  value: 'child_process',
@@ -26,7 +26,7 @@ const {
26
26
  FS_METHODS,
27
27
  Rule: { PATH_TRAVERSAL: ruleId },
28
28
  isString,
29
- join,
29
+ ArrayPrototypeJoin,
30
30
  } = require('@contrast/common');
31
31
  const { InstrumentationType: { RULE } } = require('../../../constants');
32
32
 
@@ -87,7 +87,7 @@ module.exports = function(core) {
87
87
  name,
88
88
  moduleName,
89
89
  methodName: fullMethodName || methodName,
90
- context: `${name}(${join(
90
+ context: `${name}(${ArrayPrototypeJoin.call(
91
91
  args.map((a) => inspect(a.value)),
92
92
  ', '
93
93
  )})`,
@@ -17,7 +17,7 @@
17
17
 
18
18
  const {
19
19
  isString,
20
- join,
20
+ ArrayPrototypeJoin,
21
21
  DataflowTag: {
22
22
  UNTRUSTED,
23
23
  CUSTOM_ENCODED_TRUST_BOUNDARY_VIOLATION,
@@ -114,7 +114,7 @@ module.exports = function (core) {
114
114
  });
115
115
  const event = createSinkEvent({
116
116
  name,
117
- context: `${name}(${join(
117
+ context: `${name}(${ArrayPrototypeJoin.call(
118
118
  args.map((a) => a.inspectedValue),
119
119
  ', '
120
120
  )})`,
@@ -0,0 +1,208 @@
1
+ /*
2
+ * Copyright: 2024 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 {
19
+ Rule: { UNVALIDATED_REDIRECT: ruleId },
20
+ DataflowTag: {
21
+ UNTRUSTED,
22
+ CUSTOM_ENCODED,
23
+ CUSTOM_VALIDATED,
24
+ HTML_ENCODED,
25
+ LIMITED_CHARS,
26
+ URL_ENCODED,
27
+ },
28
+ isString,
29
+ ArrayPrototypeJoin,
30
+ } = require('@contrast/common');
31
+ const { InstrumentationType: { RULE } } = require('../../../constants');
32
+ const { createAppendTags } = require('../../tag-utils');
33
+ const { patchType } = require('../common');
34
+
35
+ /**
36
+ * @param {{
37
+ * assess: import('@contrast/assess').Assess,
38
+ * config: import('@contrast/config').Config,
39
+ * logger: import('@contrast/logger').Logger,
40
+ * }} core
41
+ * @returns {import('@contrast/common').Installable}
42
+ */
43
+ module.exports = function(core) {
44
+ const {
45
+ depHooks,
46
+ patcher,
47
+ assess: {
48
+ getSourceContext,
49
+ eventFactory: { createSinkEvent },
50
+ dataflow: {
51
+ tracker,
52
+ sinks: { isVulnerable, reportFindings /*reportSafePositive*/ }
53
+ },
54
+ inspect,
55
+ },
56
+ } = core;
57
+
58
+ const OPTION_FIELDS = ['hostname', 'pathname'];
59
+ const SAFE_TAGS = [
60
+ `excluded:${ruleId}`,
61
+ CUSTOM_ENCODED,
62
+ CUSTOM_VALIDATED,
63
+ HTML_ENCODED,
64
+ LIMITED_CHARS,
65
+ URL_ENCODED,
66
+ ];
67
+
68
+ function createCommonEventData(data) {
69
+ return {
70
+ name: 'restify.Response.redirect',
71
+ moduleName: 'restify',
72
+ methodName: 'Response.redirect',
73
+ object: {
74
+ tracked: false,
75
+ value: 'restify.Response',
76
+ },
77
+ result: {
78
+ tracked: false,
79
+ value: undefined,
80
+ },
81
+ stacktraceOpts: {
82
+ constructorOpt: data.hooked,
83
+ prependFrames: [data.orig]
84
+ },
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Called when tracked values are on options object argument.
90
+ * Will return the args coerced to strings values, and the adjusted ranges.
91
+ * @returns {}
92
+ */
93
+ function getAdjustedValues(origArgs, vulns, vulnArgIdx) {
94
+ let result = '{...';
95
+ let tags;
96
+
97
+ vulns.forEach((vuln, i) => {
98
+ const sep = i == 0 ? '' : ',';
99
+ result += `${sep}'${vuln.path[vuln.path.length - 1]}':'`;
100
+ tags = createAppendTags(tags, vuln.strInfo.tags, result.length);
101
+ result += `${vuln.strInfo.value}'`;
102
+ });
103
+
104
+ result += '...}';
105
+
106
+ const args = origArgs.map((a, i) => i == vulnArgIdx ?
107
+ { tracked: true, value: result } :
108
+ { tracked: false, value: a?.constructor?.name || typeof a });
109
+
110
+ return { args, tags };
111
+ }
112
+
113
+ return core.assess.dataflow.sinks.restify = {
114
+ install() {
115
+ // restify adds functionality to the built-in response via this patch function.
116
+ // once it returns the request, it'll have been decorated with redirect() method.
117
+ depHooks.resolve({ name: 'restify', file: 'lib/response.js' }, (responsePatch) => patcher.patch(responsePatch, {
118
+ name: 'restify.response.patch',
119
+ patchType,
120
+ post(data) {
121
+ patcher.patch(data.args[0].prototype, 'redirect', {
122
+ patchType,
123
+ name: 'restify.Response.redirect',
124
+ pre(data) {
125
+ if (!getSourceContext(RULE, ruleId)) return;
126
+
127
+ let vulnArgIdx;
128
+ let vulnArgIsString = true;
129
+
130
+ if (isString(data.args[0])) {
131
+ vulnArgIdx = 0; // res.redirect(url, next)
132
+ } else if (isString(data.args[1])) {
133
+ vulnArgIdx = 1; // res.redirect(code, url, next)
134
+ } else if (data.args[0] && typeof data.args[0] === 'object') {
135
+ vulnArgIdx = 0; // res.redirect(options, next)
136
+ vulnArgIsString = false;
137
+ } else {
138
+ return; // unknown call signature
139
+ }
140
+
141
+ if (vulnArgIsString) {
142
+ const strInfo = tracker.getData(data.args[vulnArgIdx]);
143
+ if (strInfo && isVulnerable(UNTRUSTED, SAFE_TAGS, strInfo.tags)) {
144
+ const args = data.args.map((arg, idx) => ({
145
+ tracked: idx === vulnArgIdx,
146
+ value: idx === vulnArgIdx ?
147
+ inspect(strInfo.value) :
148
+ (arg?.constructor?.name ?? typeof strInfo.value)
149
+ }));
150
+
151
+ const sinkEvent = createSinkEvent({
152
+ args,
153
+ context: `res.redirect(${ArrayPrototypeJoin.call(args.map((a) => a.value))})`,
154
+ history: [strInfo],
155
+ tags: createAppendTags(null, strInfo.tags, 1), // offset by 1 for formatting as string
156
+ source: `P${vulnArgIdx}`,
157
+ ...createCommonEventData(data),
158
+ });
159
+
160
+ if (sinkEvent) {
161
+ reportFindings({ ruleId, sinkEvent });
162
+ }
163
+ }
164
+ } else {
165
+ const options = data.args[vulnArgIdx];
166
+ const vulns = [];
167
+
168
+ for (const option of OPTION_FIELDS) {
169
+ const strInfo = tracker.getData(options?.[option]);
170
+ if (strInfo && isVulnerable(UNTRUSTED, SAFE_TAGS, strInfo.tags)) {
171
+ vulns.push({ path: [option], strInfo });
172
+ }
173
+ }
174
+
175
+ if (typeof options.query == 'object') {
176
+ for (const [key, value] of Object.entries(options.query)) {
177
+ const strInfo = tracker.getData(value);
178
+ if (strInfo && isVulnerable(UNTRUSTED, SAFE_TAGS, strInfo.tags)) {
179
+ vulns.push({ path: ['query', key], strInfo });
180
+ }
181
+ }
182
+ }
183
+
184
+ if (vulns.length) {
185
+ // so events are not duplicated
186
+ const history = Array.from(new Set(vulns.map((v) => v.strInfo)));
187
+ const { tags, args } = getAdjustedValues(data.args, vulns, vulnArgIdx);
188
+ const sinkEvent = createSinkEvent({
189
+ args,
190
+ context: `res.redirect(${ArrayPrototypeJoin.call(args.map((a) => a.value))})`,
191
+ history,
192
+ tags,
193
+ source: 'P0',
194
+ ...createCommonEventData(data),
195
+
196
+ });
197
+ if (sinkEvent) {
198
+ reportFindings({ ruleId, sinkEvent });
199
+ }
200
+ }
201
+ }
202
+ }
203
+ });
204
+ }
205
+ }));
206
+ }
207
+ };
208
+ };
@@ -27,8 +27,8 @@ const {
27
27
  Rule: { UNSAFE_CODE_EXECUTION: ruleId },
28
28
  isNonEmptyObject,
29
29
  isString,
30
- join,
31
- split,
30
+ ArrayPrototypeJoin,
31
+ StringPrototypeSplit,
32
32
  traverseValues,
33
33
  } = require('@contrast/common');
34
34
  const { InstrumentationType: { RULE } } = require('../../../constants');
@@ -144,7 +144,7 @@ module.exports = function (core) {
144
144
  function around(next, { args: origArgs, hooked, orig, name }) {
145
145
  if (!getSourceContext(RULE, ruleId) || isLocked(ruleId)) return next();
146
146
 
147
- const methodPath = split(name, '.');
147
+ const methodPath = StringPrototypeSplit.call(name, '.');
148
148
  const method = methodPath[methodPath.length - 1];
149
149
  const idxsToCheck = method === 'runInNewContext' ? [0, 1] : [0];
150
150
  const argsInfo = accumulateArgsInfo(origArgs, idxsToCheck);
@@ -204,7 +204,7 @@ module.exports = function (core) {
204
204
  if (vulnerableArg) {
205
205
  const event = createSinkEvent({
206
206
  name,
207
- context: `${name}(${join(
207
+ context: `${name}(${ArrayPrototypeJoin.call(
208
208
  argsInfo.map((a) => a.ctxValue),
209
209
  ', '
210
210
  )})`,
@@ -19,7 +19,7 @@ const {
19
19
  InputType,
20
20
  DataflowTag,
21
21
  isString,
22
- join,
22
+ ArrayPrototypeJoin,
23
23
  } = require('@contrast/common');
24
24
 
25
25
  module.exports = function (core) {
@@ -137,7 +137,7 @@ module.exports = function (core) {
137
137
  }
138
138
 
139
139
  traverse(_data, (path, fieldName, value, obj) => {
140
- const pathName = join(path, '.');
140
+ const pathName = ArrayPrototypeJoin.call(path, '.');
141
141
 
142
142
  if (sourceContext.sourceEventsCount >= max) {
143
143
  logger.trace({ inputType, sourceName: name }, 'exiting assess source handling - %s max events exceeded', max);
@@ -26,6 +26,7 @@ module.exports = function (core) {
26
26
  require('./install/fastify')(core);
27
27
  require('./install/hapi')(core);
28
28
  require('./install/koa')(core);
29
+ require('./install/restify')(core);
29
30
  require('./install/body-parser1')(core);
30
31
  require('./install/busboy')(core);
31
32
  require('./install/cookie-parser1')(core);
@@ -15,7 +15,7 @@
15
15
 
16
16
  'use strict';
17
17
  const { patchType } = require('../common');
18
- const { toLowerCase, InputType } = require('@contrast/common');
18
+ const { StringPrototypeToLowerCase, InputType } = require('@contrast/common');
19
19
 
20
20
  /**
21
21
  * @param {{
@@ -65,13 +65,13 @@ module.exports = function (core) {
65
65
  const key = obj[i];
66
66
  const value = obj[i + 1];
67
67
 
68
- if (toLowerCase(key) === 'content-type') {
68
+ if (StringPrototypeToLowerCase.call(key) === 'content-type') {
69
69
  store.assess.responseData.contentType = value;
70
70
  }
71
71
  }
72
72
  } else if (typeof obj === 'object') {
73
73
  for (const [key, value] of Object.entries(obj)) {
74
- if (toLowerCase(key) === 'content-type') {
74
+ if (StringPrototypeToLowerCase.call(key) === 'content-type') {
75
75
  store.assess.responseData.contentType = value;
76
76
  }
77
77
  }
@@ -85,7 +85,7 @@ module.exports = function (core) {
85
85
  patchType,
86
86
  pre(data) {
87
87
  const [name = '', value] = data.args;
88
- if (toLowerCase(name) === 'content-type' && scopes.sources.getStore()?.assess && value) {
88
+ if (StringPrototypeToLowerCase.call(name) === 'content-type' && scopes.sources.getStore()?.assess && value) {
89
89
  store.assess.responseData.contentType = value;
90
90
  }
91
91
  }
@@ -0,0 +1,85 @@
1
+ /*
2
+ * Copyright: 2024 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 { InputType: { BODY } } = require('@contrast/common');
19
+ const { InstrumentationType: { SOURCE } } = require('../../../../constants');
20
+ const { patchType } = require('../../common');
21
+
22
+ module.exports = function init(core) {
23
+ const {
24
+ logger,
25
+ depHooks,
26
+ patcher,
27
+ assess: {
28
+ dataflow: { sources },
29
+ getSourceContext,
30
+ },
31
+ } = core;
32
+
33
+ return core.assess.dataflow.sources.restifyInstrumentation.fieldedTextBodyParser = {
34
+ install() {
35
+ depHooks.resolve(
36
+ { name: 'restify', file: 'lib/plugins/fieldedTextBodyParser.js', version: '>=8' },
37
+ (fieldedTextBodyParser) => patcher.patch(fieldedTextBodyParser, {
38
+ name: 'restify.plugins.fieldedTextBodyParser',
39
+ patchType,
40
+ post(data) {
41
+ data.result = patcher.patch(data.result, {
42
+ name: 'restify.plugins.fieldedTextBodyParser.parseFieldedText',
43
+ patchType,
44
+ pre(data) {
45
+ const { args: [req, , next], name, funcKey } = data;
46
+ data.args[2] = function contrastNext(...args) {
47
+ const sourceContext = getSourceContext(SOURCE);
48
+
49
+ if (!sourceContext) {
50
+ logger.error({ funcKey }, 'unable to handle source. Missing `sourceContext`');
51
+ return next(...args);
52
+ }
53
+
54
+ if (sourceContext.parsedBody) {
55
+ logger.trace({ funcKey }, 'values already tracked');
56
+ return next(...args);
57
+ }
58
+
59
+ try {
60
+ sources.handle({
61
+ context: 'req.body',
62
+ data: req.body,
63
+ inputType: BODY,
64
+ name,
65
+ stacktraceOpts: {
66
+ constructorOpt: contrastNext,
67
+ },
68
+ sourceContext,
69
+ });
70
+
71
+ sourceContext.parsedBody = true;
72
+ } catch (err) {
73
+ logger.error({ err, funcKey }, 'unable to handle Restify source');
74
+ }
75
+
76
+ return next(...args);
77
+ };
78
+ },
79
+ });
80
+ }
81
+ }),
82
+ );
83
+ },
84
+ };
85
+ };
@@ -0,0 +1,32 @@
1
+ /*
2
+ * Copyright: 2024 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 { callChildComponentMethodsSync } = require('@contrast/common');
19
+
20
+ module.exports = function init(core) {
21
+ core.assess.dataflow.sources.restifyInstrumentation = {
22
+ install() {
23
+ callChildComponentMethodsSync(this, 'install');
24
+ },
25
+ };
26
+
27
+ require('./fieldedTextBodyParser')(core);
28
+ require('./jsonBodyParser')(core);
29
+ require('./router')(core);
30
+
31
+ return core.assess.dataflow.sources.restifyInstrumentation;
32
+ };