@contrast/assess 1.5.0 → 1.7.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 (40) hide show
  1. package/lib/dataflow/event-factory.js +10 -5
  2. package/lib/dataflow/propagation/index.js +1 -0
  3. package/lib/dataflow/propagation/install/contrast-methods/string.js +5 -1
  4. package/lib/dataflow/propagation/install/decode-uri-component.js +5 -2
  5. package/lib/dataflow/propagation/install/ejs/escape-xml.js +5 -2
  6. package/lib/dataflow/propagation/install/encode-uri-component.js +5 -2
  7. package/lib/dataflow/propagation/install/escape-html.js +7 -4
  8. package/lib/dataflow/propagation/install/escape.js +5 -2
  9. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +5 -2
  10. package/lib/dataflow/propagation/install/mysql-connection-escape.js +5 -2
  11. package/lib/dataflow/propagation/install/pug-runtime-escape.js +5 -2
  12. package/lib/dataflow/propagation/install/querystring/parse.js +8 -3
  13. package/lib/dataflow/propagation/install/sequelize.js +310 -0
  14. package/lib/dataflow/propagation/install/sql-template-strings.js +5 -4
  15. package/lib/dataflow/propagation/install/string/match.js +2 -2
  16. package/lib/dataflow/propagation/install/string/replace.js +13 -4
  17. package/lib/dataflow/propagation/install/unescape.js +5 -2
  18. package/lib/dataflow/propagation/install/validator/methods.js +60 -51
  19. package/lib/dataflow/sinks/common.js +10 -1
  20. package/lib/dataflow/sinks/index.js +34 -1
  21. package/lib/dataflow/sinks/install/child-process.js +150 -13
  22. package/lib/dataflow/sinks/install/express/index.js +29 -0
  23. package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +134 -0
  24. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +113 -75
  25. package/lib/dataflow/sinks/install/fs.js +136 -0
  26. package/lib/dataflow/sinks/install/http.js +46 -17
  27. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +50 -17
  28. package/lib/dataflow/sinks/install/marsdb.js +135 -0
  29. package/lib/dataflow/sinks/install/mongodb.js +322 -0
  30. package/lib/dataflow/sinks/install/mssql.js +19 -10
  31. package/lib/dataflow/sinks/install/mysql.js +138 -0
  32. package/lib/dataflow/sinks/install/postgres.js +37 -23
  33. package/lib/dataflow/sinks/install/sequelize.js +142 -0
  34. package/lib/dataflow/sinks/install/sqlite3.js +20 -10
  35. package/lib/dataflow/sources/handler.js +14 -9
  36. package/lib/dataflow/sources/index.js +4 -1
  37. package/lib/dataflow/sources/install/body-parser1.js +120 -0
  38. package/lib/dataflow/sources/install/cookie-parser1.js +101 -0
  39. package/lib/dataflow/sources/install/express/index.js +28 -0
  40. package/package.json +3 -3
@@ -16,49 +16,56 @@
16
16
  'use strict';
17
17
 
18
18
  const util = require('util');
19
- const { Rule, isString } = require('@contrast/common');
20
- const { patchType } = require('../common');
19
+ const {
20
+ DataflowTag: { UNTRUSTED, SQL_ENCODED, LIMITED_CHARS, CUSTOM_VALIDATED, CUSTOM_ENCODED },
21
+ Rule: { SQL_INJECTION: ruleId },
22
+ isString
23
+ } = require('@contrast/common');
24
+ const { filterSafeTags, patchType } = require('../common');
21
25
 
22
26
  module.exports = function (core) {
23
27
  const {
28
+ config,
24
29
  depHooks,
25
30
  patcher,
26
31
  scopes: { sources },
27
32
  assess: {
28
33
  dataflow: {
29
34
  tracker,
30
- sinks: { isVulnerable, reportFindings },
35
+ sinks: { isVulnerable, isLocked, reportFindings, reportSafePositive },
31
36
  eventFactory: { createSinkEvent },
32
37
  },
33
38
  },
34
39
  } = core;
35
40
 
36
41
  const safeTags = [
37
- 'sql-encoded',
38
- 'limited-chars',
39
- 'custom-validated',
40
- 'custom-encoded',
42
+ SQL_ENCODED,
43
+ LIMITED_CHARS,
44
+ CUSTOM_VALIDATED,
45
+ CUSTOM_ENCODED,
41
46
  ];
42
- const requiredTag = 'untrusted';
47
+
43
48
  const inspect = patcher.unwrap(util.inspect);
44
49
 
45
50
  const postgres = core.assess.dataflow.sinks.postgres = {};
46
51
 
47
- const preHook = (methodSignature, version, mod, obj) => (data) => {
52
+ const preHook = (methodSignature) => (data) => {
48
53
  const assessStore = sources.getStore()?.assess;
49
- if (!assessStore) return;
54
+ if (!assessStore || isLocked(ruleId)) return;
50
55
 
51
56
  const [arg0] = data.args;
52
57
  const query = arg0?.text || arg0;
53
58
  if (!query || !isString(query)) return;
54
59
 
55
60
  const strInfo = tracker.getData(query);
61
+ // todo: if the query isn't tracked but users are sending tracked strings in the values
62
+ // array (args[1]), then shouldn't we report a "safe-positive" event of some kind?
56
63
  if (!strInfo) return;
57
64
 
58
65
  const objValue = methodSignature.includes('native') ? 'pg.native.Client' : 'pg.Client';
59
66
  const arg0Val = inspect(arg0);
60
67
 
61
- if (isVulnerable(requiredTag, safeTags, strInfo.tags)) {
68
+ if (isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
62
69
  const event = createSinkEvent({
63
70
  args: [{
64
71
  tracked: true,
@@ -84,10 +91,20 @@ module.exports = function (core) {
84
91
 
85
92
  if (event) {
86
93
  reportFindings({
87
- ruleId: Rule.SQL_INJECTION,
94
+ ruleId,
88
95
  sinkEvent: event,
89
96
  });
90
97
  }
98
+ } else if (config.assess.safe_positives.enable) {
99
+ reportSafePositive({
100
+ name: methodSignature,
101
+ ruleId,
102
+ safeTags: filterSafeTags(safeTags, strInfo),
103
+ strInfo: {
104
+ value: strInfo.value,
105
+ tags: strInfo.tags,
106
+ }
107
+ });
91
108
  }
92
109
  };
93
110
 
@@ -95,11 +112,11 @@ module.exports = function (core) {
95
112
  const pgClientQueryPatchName = 'pg.Client.prototype.query';
96
113
  depHooks.resolve(
97
114
  { name: 'pg', file: 'lib/client.js' },
98
- (client, version) => {
115
+ (client) => {
99
116
  patcher.patch(client.prototype, 'query', {
100
117
  name: pgClientQueryPatchName,
101
118
  patchType,
102
- pre: preHook('pg/lib/client.prototype.query', version, 'pg', 'Client'),
119
+ pre: preHook('pg/lib/client.prototype.query'),
103
120
  });
104
121
  },
105
122
  );
@@ -107,18 +124,16 @@ module.exports = function (core) {
107
124
  const pgNativeClientQueryPatchName = 'pg.native.Client.prototype.query';
108
125
  depHooks.resolve(
109
126
  { name: 'pg', file: 'lib/native/client.js' },
110
- (client, version) => {
127
+ (client) => {
111
128
  patcher.patch(client.prototype, 'query', {
112
129
  name: pgNativeClientQueryPatchName,
113
130
  patchType,
114
- pre: preHook('pg/lib/native/client.prototype.query', version, 'pg', 'native.Client'),
131
+ pre: preHook('pg/lib/native/client.prototype.query'),
115
132
  });
116
133
  },
117
134
  );
118
135
 
119
- const pgClientPatchName = `${patchType}:${pgClientQueryPatchName}.query`;
120
- const pgNativeClientPatchName = `${patchType}:${pgNativeClientQueryPatchName}.query`;
121
- depHooks.resolve({ name: 'pg-pool' }, (pool, version) => {
136
+ depHooks.resolve({ name: 'pg-pool' }, (pool) => {
122
137
  const name = 'pg-pool.Pool.prototype.query';
123
138
  patcher.patch(pool.prototype, 'query', {
124
139
  name,
@@ -129,14 +144,13 @@ module.exports = function (core) {
129
144
  )?.funcKeys;
130
145
 
131
146
  if (
132
- funcKeys &&
133
- (funcKeys.has(pgClientPatchName) ||
134
- funcKeys.has(pgNativeClientPatchName))
147
+ funcKeys?.has(`${patchType}:${pgClientQueryPatchName}`) ||
148
+ funcKeys?.has(`${patchType}:${pgNativeClientQueryPatchName}`)
135
149
  ) {
136
150
  return;
137
151
  }
138
152
 
139
- preHook(name, version, 'pg-pool', 'Pool')(data);
153
+ preHook(name)(data);
140
154
  },
141
155
  });
142
156
  });
@@ -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
+ };
@@ -14,14 +14,19 @@
14
14
  */
15
15
 
16
16
  'use strict';
17
+
17
18
  const { patchType } = require('../common');
18
- const { Rule, isString } = require('@contrast/common');
19
+ const {
20
+ DataflowTag: { UNTRUSTED, SQL_ENCODED, LIMITED_CHARS, CUSTOM_VALIDATED, CUSTOM_ENCODED },
21
+ Rule: { SQL_INJECTION },
22
+ isString
23
+ } = require('@contrast/common');
19
24
 
20
- const SAFE_TAGS = [
21
- 'sql-encoded',
22
- 'limited-chars',
23
- 'custom-validated',
24
- 'custom-encoded',
25
+ const safeTags = [
26
+ SQL_ENCODED,
27
+ LIMITED_CHARS,
28
+ CUSTOM_VALIDATED,
29
+ CUSTOM_ENCODED,
25
30
  ];
26
31
 
27
32
  module.exports = function (core) {
@@ -32,7 +37,7 @@ module.exports = function (core) {
32
37
  assess: {
33
38
  dataflow: {
34
39
  tracker,
35
- sinks: { isVulnerable, reportFindings },
40
+ sinks: { isVulnerable, isLocked, reportFindings },
36
41
  eventFactory: { createSinkEvent },
37
42
  },
38
43
  },
@@ -40,10 +45,15 @@ module.exports = function (core) {
40
45
 
41
46
  const pre = (name) => (data) => {
42
47
  const store = sources.getStore()?.assess;
43
- 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;
44
54
 
45
55
  const strInfo = tracker.getData(data.args[0]);
46
- if (!strInfo || !isVulnerable('untrusted', SAFE_TAGS, strInfo.tags)) {
56
+ if (!strInfo || !isVulnerable(UNTRUSTED, safeTags, strInfo.tags)) {
47
57
  return;
48
58
  }
49
59
 
@@ -69,7 +79,7 @@ module.exports = function (core) {
69
79
 
70
80
  if (event) {
71
81
  reportFindings({
72
- ruleId: Rule.SQL_INJECTION,
82
+ ruleId: SQL_INJECTION,
73
83
  sinkEvent: event,
74
84
  });
75
85
  }
@@ -15,7 +15,12 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { isString, traverseValues } = require('@contrast/common');
18
+ const {
19
+ InputType,
20
+ DataflowTag,
21
+ isString,
22
+ traverseValues
23
+ } = require('@contrast/common');
19
24
 
20
25
  module.exports = function(core) {
21
26
  const {
@@ -40,15 +45,11 @@ module.exports = function(core) {
40
45
 
41
46
  const stop = value.length - 1;
42
47
  const tags = {
43
- untrusted: [0, stop]
48
+ [DataflowTag.UNTRUSTED]: [0, stop]
44
49
  };
45
50
 
46
- if (inputType === 'header' && key.toLowerCase() === 'referer') {
47
- tags.header = [0, stop];
48
- }
49
-
50
- if (inputType === 'cookie') {
51
- tags.cookie = [0, stop];
51
+ if (inputType === InputType.HEADER && key.toLowerCase() === 'referer') {
52
+ tags[DataflowTag.HEADER] = [0, stop];
52
53
  }
53
54
 
54
55
  return tags;
@@ -57,7 +58,7 @@ module.exports = function(core) {
57
58
  sources.handle = function({
58
59
  context,
59
60
  name,
60
- inputType = 'unknown',
61
+ inputType = InputType.UNKNOWN,
61
62
  stacktraceOpts,
62
63
  data,
63
64
  sourceContext
@@ -71,6 +72,10 @@ module.exports = function(core) {
71
72
  return null;
72
73
  }
73
74
 
75
+ if (!context) {
76
+ context = inputType;
77
+ }
78
+
74
79
  let stack;
75
80
 
76
81
  traverseValues(data, (path, type, value, obj) => {
@@ -17,7 +17,7 @@
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
23
  // API
@@ -27,11 +27,14 @@ module.exports = function(core) {
27
27
  require('./install/http')(core);
28
28
 
29
29
  // frameworks and frameworks specific libraries
30
+ require('./install/express')(core);
30
31
  require('./install/fastify')(core);
31
32
  require('./install/koa')(core);
32
33
 
33
34
  // libraries
35
+ require('./install/body-parser1')(core);
34
36
  require('./install/busboy1')(core);
37
+ require('./install/cookie-parser1')(core);
35
38
  require('./install/formidable1')(core);
36
39
  require('./install/qs6')(core);
37
40
 
@@ -0,0 +1,120 @@
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;
40
+ }
41
+
42
+ if (sourceContext.parsedBody) {
43
+ logger.trace({ name }, 'values already tracked');
44
+ return;
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
+ try {
59
+ assess.dataflow.sources.handle({
60
+ context: 'req.body',
61
+ name,
62
+ inputType,
63
+ stacktraceOpts: {
64
+ constructorOpt: contrastNext
65
+ },
66
+ data: req.body,
67
+ sourceContext,
68
+ });
69
+
70
+ sourceContext.parsedBody = !!Object.keys(req.body).length;
71
+ } catch (err) {
72
+ logger.error({ name, err }, 'unable to handle source');
73
+ }
74
+
75
+ return next(...args);
76
+ };
77
+ };
78
+
79
+ assess.dataflow.sources.bodyParser1Instrumentation = {
80
+ install() {
81
+ depHooks.resolve(
82
+ { name: 'body-parser', version: '>=1.0.0' },
83
+ /** @param {import('body-parser').BodyParser} bodyParser */
84
+ (bodyParser) => {
85
+ bodyParser = patcher.patch(bodyParser, {
86
+ name: 'body-parser',
87
+ patchType,
88
+ post(data) {
89
+ const name = 'body-parser.bodyParser';
90
+ data.result = patcher.patch(data.result, {
91
+ name,
92
+ patchType,
93
+ pre: createPreHook(name),
94
+ });
95
+ },
96
+ });
97
+
98
+ METHODS.forEach((method) => {
99
+ patcher.patch(bodyParser, method, {
100
+ name: `body-parser.${method}`,
101
+ patchType,
102
+ post(data) {
103
+ const name = `body-parser.${method}.${method}Parser`;
104
+ data.result = patcher.patch(data.result, {
105
+ name,
106
+ patchType,
107
+ pre: createPreHook(name)
108
+ });
109
+ },
110
+ });
111
+ });
112
+
113
+ return bodyParser;
114
+ }
115
+ );
116
+ }
117
+ };
118
+
119
+ return assess.dataflow.sources.bodyParser1Instrumentation;
120
+ };
@@ -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
+ };
@@ -0,0 +1,28 @@
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 { callChildComponentMethodsSync } = require('@contrast/common');
19
+
20
+ module.exports = function init(core) {
21
+ core.assess.dataflow.sources.expressInstrumentation = {
22
+ install() {
23
+ callChildComponentMethodsSync(this, 'install');
24
+ }
25
+ };
26
+
27
+ return core.assess.dataflow.sources.expressInstrumentation;
28
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/assess",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -14,8 +14,8 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@contrast/distringuish": "^4.1.0",
17
- "@contrast/scopes": "1.3.0",
18
- "@contrast/common": "1.8.0",
17
+ "@contrast/scopes": "1.4.0",
18
+ "@contrast/common": "1.10.0",
19
19
  "parseurl": "^1.3.3"
20
20
  }
21
21
  }