@contrast/assess 1.6.0 → 1.8.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 (35) hide show
  1. package/lib/dataflow/propagation/index.js +3 -0
  2. package/lib/dataflow/propagation/install/JSON/index.js +33 -0
  3. package/lib/dataflow/propagation/install/JSON/stringify.js +290 -0
  4. package/lib/dataflow/propagation/install/buffer.js +79 -0
  5. package/lib/dataflow/propagation/install/contrast-methods/string.js +5 -1
  6. package/lib/dataflow/propagation/install/encode-uri-component.js +5 -2
  7. package/lib/dataflow/propagation/install/pug-runtime-escape.js +5 -2
  8. package/lib/dataflow/propagation/install/sequelize.js +310 -0
  9. package/lib/dataflow/propagation/install/sql-template-strings.js +5 -4
  10. package/lib/dataflow/propagation/install/string/match.js +2 -2
  11. package/lib/dataflow/propagation/install/string/replace.js +9 -4
  12. package/lib/dataflow/sinks/common.js +10 -1
  13. package/lib/dataflow/sinks/index.js +30 -1
  14. package/lib/dataflow/sinks/install/express/index.js +29 -0
  15. package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +134 -0
  16. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +96 -69
  17. package/lib/dataflow/sinks/install/http.js +20 -5
  18. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +33 -9
  19. package/lib/dataflow/sinks/install/mongodb.js +297 -82
  20. package/lib/dataflow/sinks/install/mssql.js +9 -4
  21. package/lib/dataflow/sinks/install/mysql.js +20 -4
  22. package/lib/dataflow/sinks/install/postgres.js +25 -12
  23. package/lib/dataflow/sinks/install/sequelize.js +142 -0
  24. package/lib/dataflow/sinks/install/sqlite3.js +9 -4
  25. package/lib/dataflow/sources/handler.js +144 -26
  26. package/lib/dataflow/sources/index.js +6 -8
  27. package/lib/dataflow/sources/install/body-parser1.js +133 -0
  28. package/lib/dataflow/sources/install/cookie-parser1.js +101 -0
  29. package/lib/dataflow/sources/install/express/index.js +31 -0
  30. package/lib/dataflow/sources/install/express/params.js +81 -0
  31. package/lib/dataflow/sources/install/express/parsedUrl.js +87 -0
  32. package/lib/dataflow/sources/install/http.js +32 -18
  33. package/lib/dataflow/sources/install/querystring.js +75 -0
  34. package/lib/dataflow/tag-utils.js +68 -1
  35. package/package.json +3 -3
@@ -0,0 +1,142 @@
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
+
16
+ 'use strict';
17
+
18
+ const util = require('util');
19
+ const {
20
+ Rule: { SQL_INJECTION },
21
+ DataflowTag: {
22
+ UNTRUSTED,
23
+ SQL_ENCODED,
24
+ LIMITED_CHARS,
25
+ CUSTOM_VALIDATED,
26
+ CUSTOM_ENCODED,
27
+ },
28
+ } = require('@contrast/common');
29
+ const { patchType, filterSafeTags } = require('../common');
30
+
31
+ module.exports = function (core) {
32
+ const {
33
+ depHooks,
34
+ patcher,
35
+ config,
36
+ scopes: { sources },
37
+ assess: {
38
+ dataflow: {
39
+ tracker,
40
+ sinks: { isVulnerable, runInActiveSink, reportFindings, reportSafePositive },
41
+ eventFactory: { createSinkEvent },
42
+ },
43
+ },
44
+ } = core;
45
+
46
+ const safeTags = [
47
+ SQL_ENCODED,
48
+ LIMITED_CHARS,
49
+ CUSTOM_VALIDATED,
50
+ CUSTOM_ENCODED
51
+ ];
52
+ const requiredTag = UNTRUSTED;
53
+ const inspect = patcher.unwrap(util.inspect);
54
+
55
+ const sequelize = (core.assess.dataflow.sinks.sequelize = {});
56
+
57
+ sequelize.install = function () {
58
+ const sequelizeQueryPatchName = 'sequelize.prototype.query';
59
+ depHooks.resolve({ name: 'sequelize' }, (sequelize) => {
60
+ patcher.patch(sequelize.prototype, 'query', {
61
+ name: sequelizeQueryPatchName,
62
+ patchType,
63
+ around(next, data) {
64
+ const { args, hooked, orig } = data;
65
+ const sourceContext = sources.getStore()?.assess;
66
+ if (!sourceContext || !args[0]) return next();
67
+
68
+ const query = typeof args[0] === 'string' ? args[0] : args[0].query;
69
+
70
+ try {
71
+ const queryInfo = tracker.getData(query);
72
+ const isVulnerableQuery = isVulnerable(requiredTag, safeTags, queryInfo.tags);
73
+
74
+ if (queryInfo && !isVulnerableQuery && config.assess.safe_positives.enable) {
75
+ reportSafePositive({
76
+ name: sequelizeQueryPatchName,
77
+ ruleId: SQL_INJECTION,
78
+ safeTags: filterSafeTags(safeTags, queryInfo),
79
+ strInfo: {
80
+ value: queryInfo?.value,
81
+ tags: queryInfo.tags,
82
+ },
83
+ });
84
+ }
85
+
86
+ if (
87
+ !queryInfo ||
88
+ !isVulnerableQuery
89
+ ) {
90
+ return runInActiveSink(SQL_INJECTION, async () => await next());
91
+ }
92
+
93
+ const sqlValue =
94
+ typeof args[0] === 'string' ? args[0] : inspect(args[0]);
95
+ const inspectedOptions = args[1] ? inspect(args[1]) : '';
96
+ const contextArgs = args[1]
97
+ ? `${sqlValue}, ${inspectedOptions}`
98
+ : sqlValue;
99
+
100
+ const reportedArgs = [{ value: sqlValue, tracked: true }];
101
+ args[1] &&
102
+ reportedArgs.push({ value: inspectedOptions, tracked: false });
103
+
104
+ const event = createSinkEvent({
105
+ context: `sequelize.prototype.query(${contextArgs})`,
106
+ name: sequelizeQueryPatchName,
107
+ history: [queryInfo],
108
+ object: {
109
+ value: 'sequelize.prototype',
110
+ tracked: false,
111
+ },
112
+ args: reportedArgs,
113
+ tags: queryInfo?.tags,
114
+ source: 'P0',
115
+ stacktraceOpts: {
116
+ contructorOpt: hooked,
117
+ prependFrames: [orig],
118
+ },
119
+ });
120
+
121
+ if (event) {
122
+ reportFindings({
123
+ ruleId: SQL_INJECTION,
124
+ sinkEvent: event,
125
+ });
126
+ }
127
+ /* c8 ignore next 3 */
128
+ } catch (err) {
129
+ core.logger.error(
130
+ { name: sequelizeQueryPatchName, err },
131
+ 'assess sink analysis failed'
132
+ );
133
+ }
134
+
135
+ return runInActiveSink(SQL_INJECTION, async () => await next());
136
+ },
137
+ });
138
+ });
139
+ };
140
+
141
+ return sequelize;
142
+ };
@@ -18,7 +18,7 @@
18
18
  const { patchType } = require('../common');
19
19
  const {
20
20
  DataflowTag: { UNTRUSTED, SQL_ENCODED, LIMITED_CHARS, CUSTOM_VALIDATED, CUSTOM_ENCODED },
21
- Rule,
21
+ Rule: { SQL_INJECTION },
22
22
  isString
23
23
  } = require('@contrast/common');
24
24
 
@@ -37,7 +37,7 @@ module.exports = function (core) {
37
37
  assess: {
38
38
  dataflow: {
39
39
  tracker,
40
- sinks: { isVulnerable, reportFindings },
40
+ sinks: { isVulnerable, isLocked, reportFindings },
41
41
  eventFactory: { createSinkEvent },
42
42
  },
43
43
  },
@@ -45,7 +45,12 @@ module.exports = function (core) {
45
45
 
46
46
  const pre = (name) => (data) => {
47
47
  const store = sources.getStore()?.assess;
48
- if (!store || !data.args[0] || !isString(data.args[0])) return;
48
+ if (
49
+ !store ||
50
+ !data.args[0] ||
51
+ !isString(data.args[0]) ||
52
+ isLocked(SQL_INJECTION)
53
+ ) return;
49
54
 
50
55
  const strInfo = tracker.getData(data.args[0]);
51
56
  if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
@@ -74,7 +79,7 @@ module.exports = function (core) {
74
79
 
75
80
  if (event) {
76
81
  reportFindings({
77
- ruleId: Rule.SQL_INJECTION,
82
+ ruleId: SQL_INJECTION,
78
83
  sinkEvent: event,
79
84
  });
80
85
  }
@@ -19,7 +19,6 @@ const {
19
19
  InputType,
20
20
  DataflowTag,
21
21
  isString,
22
- traverseValues
23
22
  } = require('@contrast/common');
24
23
 
25
24
  module.exports = function(core) {
@@ -28,17 +27,17 @@ module.exports = function(core) {
28
27
  dataflow: {
29
28
  sources,
30
29
  tracker,
31
- eventFactory: { createSourceEvent }
30
+ eventFactory
32
31
  }
33
32
  },
34
- createSnapshot,
35
33
  config,
34
+ createSnapshot,
36
35
  logger,
37
36
  } = core;
38
37
 
39
38
  const emptyStack = Object.freeze([]);
40
39
 
41
- sources.createTags = function createTags({ inputType, key, value }) {
40
+ sources.createTags = function createTags({ inputType, fieldName = '', value }) {
42
41
  if (!value?.length) {
43
42
  return null;
44
43
  }
@@ -48,55 +47,93 @@ module.exports = function(core) {
48
47
  [DataflowTag.UNTRUSTED]: [0, stop]
49
48
  };
50
49
 
51
- if (inputType === InputType.HEADER && key.toLowerCase() === 'referer') {
50
+ if (inputType === InputType.HEADER && fieldName.toLowerCase() === 'referer') {
52
51
  tags[DataflowTag.HEADER] = [0, stop];
53
52
  }
54
53
 
55
54
  return tags;
56
55
  };
57
56
 
57
+ sources.createStacktrace = function(stacktraceOpts) {
58
+ return config.assess.stacktraces === 'NONE'
59
+ ? emptyStack
60
+ : createSnapshot(stacktraceOpts)();
61
+ };
62
+
58
63
  sources.handle = function({
59
64
  context,
65
+ keys,
60
66
  name,
61
67
  inputType = InputType.UNKNOWN,
62
68
  stacktraceOpts,
63
69
  data,
64
- sourceContext
70
+ sourceContext,
65
71
  }) {
66
72
  if (!data) return;
67
73
 
68
- const max = config.assess.max_context_source_events;
69
-
70
74
  if (!sourceContext) {
71
75
  core.logger.trace({ inputType, name }, 'skipping assess source handling - no request context');
72
76
  return null;
73
77
  }
74
78
 
79
+ if (!context) {
80
+ context = inputType;
81
+ }
82
+
83
+ const max = config.assess.max_context_source_events;
84
+ let _data = data;
75
85
  let stack;
76
86
 
77
- traverseValues(data, (path, type, value, obj) => {
87
+ if (keys) {
88
+ _data = {};
89
+ for (const key of keys) {
90
+ _data[key] = data[key];
91
+ }
92
+ }
93
+
94
+ function createEvent({ fieldName, pathName, value }) {
95
+ // create the stacktrace once per call to .handle()
96
+ stack || (stack = sources.createStacktrace(stacktraceOpts));
97
+ return eventFactory.createSourceEvent({
98
+ context: `${context}.${pathName}`,
99
+ name,
100
+ fieldName,
101
+ pathName,
102
+ stack,
103
+ inputType,
104
+ tags: sources.createTags({ inputType, fieldName, value }),
105
+ result: { tracked: true, value },
106
+ });
107
+ }
108
+
109
+ if (Buffer.isBuffer(data) && !tracker.getData(data)) {
110
+ const event = createEvent({ pathName: 'body', value: data, fieldName: '' });
111
+ if (event) {
112
+ tracker.track(data, event);
113
+ }
114
+ return;
115
+ }
116
+
117
+ traverse(_data, (path, fieldName, value, obj) => {
118
+ const pathName = path.join('.');
119
+
78
120
  if (sourceContext.sourceEventsCount >= max) {
79
121
  core.logger.trace({ inputType, name }, 'exiting assess source handling - %s max events exceeded', max);
80
122
  return true;
81
123
  }
82
124
 
83
125
  if (isString(value) && value.length) {
84
- stack = stack || config.assess.stacktraces === 'NONE'
85
- ? emptyStack
86
- : createSnapshot(stacktraceOpts)();
87
- const key = path[path.length - 1];
88
- const pathName = path.join('.');
89
- const event = createSourceEvent({
90
- context: `${context}.${pathName}`,
91
- name,
92
- fieldName: key,
93
- pathName,
94
- stack,
95
- inputType,
96
- tags: sources.createTags({ inputType, key, value }),
97
- result: { tracked: true, value },
98
- });
126
+ const strInfo = tracker.getData(value);
127
+
128
+ if (strInfo) {
129
+ // TODO: confirm this "layering-on" approach is what we want
130
+ // when the value is tracked the handler wins out and we "re-tracks" the value with new source
131
+ // event metadata. without this step tracker would complain about value already being tracked.
132
+ // alternatively we could treat this more like a propagation event and update existing metadata.
133
+ value = strInfo.value;
134
+ }
99
135
 
136
+ const event = createEvent({ pathName, value, fieldName });
100
137
  if (!event) {
101
138
  core.logger.warn({ inputType, name, pathName, value }, 'unable to create source event');
102
139
  return;
@@ -104,15 +141,96 @@ module.exports = function(core) {
104
141
 
105
142
  const { extern } = tracker.track(value, event);
106
143
  if (extern) {
107
- logger.trace({ extern, key, name, inputType }, 'tracked');
108
- obj[key] = extern;
144
+ logger.trace({ extern, fieldName, name, inputType }, 'tracked');
145
+ obj[fieldName] = extern;
146
+
109
147
  sourceContext.sourceEventsCount++;
110
148
  }
149
+ } else if (Buffer.isBuffer(value) && !tracker.getData(value)) {
150
+ const event = createEvent({ pathName, value, fieldName });
151
+ if (event) {
152
+ tracker.track(value, event);
153
+ } else {
154
+ core.logger.warn({ inputType, name, pathName, value }, 'unable to create source event');
155
+ }
111
156
  }
112
157
  });
113
158
 
159
+ if (keys) {
160
+ for (const key of keys) {
161
+ data[key] = _data[key];
162
+ }
163
+ }
164
+
114
165
  return data;
115
166
  };
116
167
 
117
168
  return sources;
118
169
  };
170
+
171
+ /**
172
+ * A custom traversal function for handling source value tracking efficiently.
173
+ * Implementation was adapted from traversal methods in @contrast/common.
174
+ * @param {any} target object to traverse
175
+ * @param {function} cb function<path, key, value, obj>
176
+ * @param {string[]} path path of node being visted; constructed of nested keys
177
+ * @param {boolean} halt whether to halt traversal; determined by callback
178
+ * @param {Set} visited used to dedupe circular references
179
+ */
180
+ function traverse(target, cb, path = [], visited = new Set()) {
181
+ if (isTraversable(target)) {
182
+ for (const key in target) {
183
+ path.push(key);
184
+
185
+ const value = target[key];
186
+
187
+ if (visited.has(value)) {
188
+ path.pop();
189
+ break;
190
+ }
191
+
192
+ if (isVisitable(value)) {
193
+ const halt = cb(path, key, value, target) === false;
194
+ if (halt) {
195
+ return;
196
+ }
197
+ }
198
+
199
+ if (isTraversable(value)) {
200
+ visited.add(value);
201
+ traverse(value, cb, path, visited);
202
+ }
203
+
204
+ path.pop();
205
+ }
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Visit strings, buffers, basic objects and arrays.
211
+ * @param {any} value the value to check
212
+ * @returns {boolean}
213
+ */
214
+ function isVisitable(value) {
215
+ if (!value) return false;
216
+
217
+ return value.constructor?.name === 'String' ||
218
+ value.constructor?.name === 'Buffer' ||
219
+ (typeof value === 'object' && !value.constructor);
220
+ }
221
+
222
+ /**
223
+ * The criteria for traversal is a strict as possible. We only traverse plain
224
+ * objects and arrays and objects created via Object.create(null).
225
+ * @param {any} value the value to check
226
+ * @returns {boolean}
227
+ */
228
+ function isTraversable(value) {
229
+ if (!value || typeof value !== 'object') return false;
230
+
231
+ return value.constructor?.name === 'Object' ||
232
+ value.constructor?.name === 'Array' ||
233
+ !value.constructor;
234
+ }
235
+
236
+ module.exports.traverse = traverse;
@@ -17,23 +17,21 @@
17
17
 
18
18
  const { callChildComponentMethodsSync } = require('@contrast/common');
19
19
 
20
- module.exports = function(core) {
20
+ module.exports = function (core) {
21
21
  const sources = core.assess.dataflow.sources = {};
22
22
 
23
- // API
24
23
  require('./handler')(core);
25
- // installers
26
- // general
27
- require('./install/http')(core);
28
24
 
29
- // frameworks and frameworks specific libraries
25
+ require('./install/express')(core);
30
26
  require('./install/fastify')(core);
31
27
  require('./install/koa')(core);
32
-
33
- // libraries
28
+ require('./install/body-parser1')(core);
34
29
  require('./install/busboy1')(core);
30
+ require('./install/cookie-parser1')(core);
35
31
  require('./install/formidable1')(core);
32
+ require('./install/http')(core);
36
33
  require('./install/qs6')(core);
34
+ require('./install/querystring')(core);
37
35
 
38
36
  sources.install = function install() {
39
37
  callChildComponentMethodsSync(sources, 'install');
@@ -0,0 +1,133 @@
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
+
16
+ 'use strict';
17
+
18
+ const { InputType } = require('@contrast/common');
19
+ const { patchType } = require('../common');
20
+
21
+ const METHODS = ['json', 'raw', 'text', 'urlencoded'];
22
+ const INPUT_TYPES = {
23
+ 'body-parser.json.jsonParser': InputType.JSON_VALUE,
24
+ 'body-parser.raw.rawParser': InputType.BODY,
25
+ 'body-parser.text.textParser': InputType.BODY,
26
+ 'body-parser.urlencoded.urlencodedParser': InputType.PARAMETER_VALUE,
27
+ };
28
+
29
+ module.exports = function init(core) {
30
+ const { assess, depHooks, logger, patcher, scopes } = core;
31
+
32
+ const createPreHook = (name) => (data) => {
33
+ const [req, , next] = data.args;
34
+ data.args[2] = function contrastNext(...args) {
35
+ const sourceContext = scopes.sources.getStore()?.assess;
36
+
37
+ if (!sourceContext) {
38
+ logger.error({ name }, 'unable to handle source. Missing `sourceContext`');
39
+ return next(...args);
40
+ }
41
+
42
+ if (sourceContext.parsedBody) {
43
+ logger.trace({ name }, 'values already tracked');
44
+ return next(...args);
45
+ }
46
+
47
+ // when using a specific parser, we know the input type.
48
+ let inputType = INPUT_TYPES[name];
49
+ // when using `bodyParser()`, determine input type by content type.
50
+ if (!inputType) {
51
+ inputType = req.headers?.['content-type']?.includes('json')
52
+ ? InputType.JSON_VALUE
53
+ : req.headers?.['content-type']?.includes('urlencoded')
54
+ ? InputType.PARAMETER_VALUE
55
+ : InputType.BODY;
56
+ }
57
+
58
+ let keys;
59
+ let _data = req.body;
60
+ let context = 'req.body';
61
+ const isString = typeof _data === 'string';
62
+ const isBuffer = Buffer.isBuffer(_data);
63
+
64
+ if (isString || isBuffer) {
65
+ context = 'req';
66
+ keys = ['body'];
67
+ _data = req;
68
+ }
69
+
70
+ try {
71
+ assess.dataflow.sources.handle({
72
+ context,
73
+ data: _data,
74
+ name,
75
+ keys,
76
+ inputType,
77
+ sourceContext,
78
+ stacktraceOpts: {
79
+ constructorOpt: contrastNext
80
+ },
81
+ });
82
+
83
+ sourceContext.parsedBody = !!Object.keys(_data).length;
84
+ } catch (err) {
85
+ logger.error({ name, err }, 'unable to handle source');
86
+ }
87
+
88
+ return next(...args);
89
+ };
90
+ };
91
+
92
+ assess.dataflow.sources.bodyParser1Instrumentation = {
93
+ install() {
94
+ depHooks.resolve(
95
+ { name: 'body-parser', version: '>=1.0.0' },
96
+ /** @param {import('body-parser').BodyParser} bodyParser */
97
+ (bodyParser) => {
98
+ bodyParser = patcher.patch(bodyParser, {
99
+ name: 'body-parser',
100
+ patchType,
101
+ post(data) {
102
+ const name = 'body-parser.bodyParser';
103
+ data.result = patcher.patch(data.result, {
104
+ name,
105
+ patchType,
106
+ pre: createPreHook(name),
107
+ });
108
+ },
109
+ });
110
+
111
+ METHODS.forEach((method) => {
112
+ patcher.patch(bodyParser, method, {
113
+ name: `body-parser.${method}`,
114
+ patchType,
115
+ post(data) {
116
+ const name = `body-parser.${method}.${method}Parser`;
117
+ data.result = patcher.patch(data.result, {
118
+ name,
119
+ patchType,
120
+ pre: createPreHook(name)
121
+ });
122
+ },
123
+ });
124
+ });
125
+
126
+ return bodyParser;
127
+ }
128
+ );
129
+ }
130
+ };
131
+
132
+ return assess.dataflow.sources.bodyParser1Instrumentation;
133
+ };
@@ -0,0 +1,101 @@
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
+
16
+ 'use strict';
17
+
18
+ const { InputType } = require('@contrast/common');
19
+ const { patchType } = require('../common');
20
+
21
+ module.exports = function init(core) {
22
+ const { assess, depHooks, logger, patcher, scopes } = core;
23
+
24
+ assess.dataflow.sources.cookieParser1Instrumentation = {
25
+ install() {
26
+ depHooks.resolve(
27
+ { name: 'cookie-parser', version: '>=1.0.0' },
28
+ /** @param {import('cookie-parser')} cookieParser */
29
+ (cookieParser) =>
30
+ patcher.patch(cookieParser, {
31
+ name: 'cookie-parser',
32
+ patchType,
33
+ post(data) {
34
+ const name = 'cookie-parser.cookieParser';
35
+ data.result = patcher.patch(data.result, {
36
+ name,
37
+ patchType,
38
+ pre(data) {
39
+ const [req, , next] = data.args;
40
+ data.args[2] = function contrastNext(...args) {
41
+ const sourceContext = scopes.sources.getStore()?.assess;
42
+
43
+ if (!sourceContext) {
44
+ logger.error({ name }, 'unable to handle source. Missing `sourceContext`');
45
+ return;
46
+ }
47
+
48
+ if (sourceContext.parsedCookies) {
49
+ logger.trace({ name }, 'cookies already tracked');
50
+ } else if (req.cookies) {
51
+ try {
52
+ assess.dataflow.sources.handle({
53
+ context: 'req.cookies',
54
+ name,
55
+ inputType: InputType.COOKIE_VALUE,
56
+ stacktraceOpts: {
57
+ constructorOpt: contrastNext
58
+ },
59
+ data: req.cookies,
60
+ sourceContext,
61
+ });
62
+
63
+ sourceContext.parsedCookies = !!Object.keys(req.cookies).length;
64
+ } catch (err) {
65
+ logger.error({ name, err }, 'unable to handle source');
66
+ }
67
+ }
68
+
69
+ if (sourceContext.parsedSignedCookies) {
70
+ logger.trace({ name }, 'signedCookies already tracked');
71
+ } else if (req.signedCookies) {
72
+ try {
73
+ assess.dataflow.sources.handle({
74
+ context: 'req.signedCookies',
75
+ name,
76
+ inputType: InputType.COOKIE_VALUE,
77
+ stacktraceOpts: {
78
+ constructorOpt: contrastNext
79
+ },
80
+ data: req.signedCookies,
81
+ sourceContext,
82
+ });
83
+
84
+ sourceContext.parsedSignedCookies = !!Object.keys(req.signedCookies).length;
85
+ } catch (err) {
86
+ logger.error({ name, err }, 'unable to handle source');
87
+ }
88
+ }
89
+
90
+ return next(...args);
91
+ };
92
+ },
93
+ });
94
+ },
95
+ })
96
+ );
97
+ }
98
+ };
99
+
100
+ return assess.dataflow.sources.cookieParser1Instrumentation;
101
+ };