@contrast/assess 1.11.0 → 1.12.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 (71) hide show
  1. package/lib/dataflow/index.js +0 -1
  2. package/lib/dataflow/propagation/install/JSON/parse.js +2 -4
  3. package/lib/dataflow/propagation/install/JSON/stringify.js +2 -1
  4. package/lib/dataflow/propagation/install/array-prototype-join.js +2 -1
  5. package/lib/dataflow/propagation/install/buffer.js +2 -4
  6. package/lib/dataflow/propagation/install/contrast-methods/add.js +2 -1
  7. package/lib/dataflow/propagation/install/contrast-methods/string.js +2 -4
  8. package/lib/dataflow/propagation/install/contrast-methods/tag.js +2 -4
  9. package/lib/dataflow/propagation/install/decode-uri-component.js +2 -1
  10. package/lib/dataflow/propagation/install/ejs/escape-xml.js +2 -1
  11. package/lib/dataflow/propagation/install/encode-uri-component.js +2 -1
  12. package/lib/dataflow/propagation/install/escape-html.js +2 -1
  13. package/lib/dataflow/propagation/install/escape.js +2 -1
  14. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +2 -1
  15. package/lib/dataflow/propagation/install/mongoose/schema-map.js +1 -1
  16. package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +1 -1
  17. package/lib/dataflow/propagation/install/mongoose/schema-string.js +2 -4
  18. package/lib/dataflow/propagation/install/mysql-connection-escape.js +2 -1
  19. package/lib/dataflow/propagation/install/path/basename.js +2 -4
  20. package/lib/dataflow/propagation/install/path/join-and-resolve.js +2 -4
  21. package/lib/dataflow/propagation/install/path/normalize.js +2 -4
  22. package/lib/dataflow/propagation/install/pug-runtime-escape.js +2 -1
  23. package/lib/dataflow/propagation/install/querystring/parse.js +2 -1
  24. package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +2 -4
  25. package/lib/dataflow/propagation/install/sequelize.js +2 -4
  26. package/lib/dataflow/propagation/install/sql-template-strings.js +2 -1
  27. package/lib/dataflow/propagation/install/string/concat.js +2 -1
  28. package/lib/dataflow/propagation/install/string/format-methods.js +2 -1
  29. package/lib/dataflow/propagation/install/string/html-methods.js +2 -1
  30. package/lib/dataflow/propagation/install/string/index.js +2 -1
  31. package/lib/dataflow/propagation/install/string/match-all.js +1 -1
  32. package/lib/dataflow/propagation/install/string/match.js +1 -1
  33. package/lib/dataflow/propagation/install/string/replace.js +2 -1
  34. package/lib/dataflow/propagation/install/string/slice.js +2 -1
  35. package/lib/dataflow/propagation/install/string/split.js +2 -1
  36. package/lib/dataflow/propagation/install/string/substring.js +2 -1
  37. package/lib/dataflow/propagation/install/string/trim.js +2 -1
  38. package/lib/dataflow/propagation/install/unescape.js +2 -1
  39. package/lib/dataflow/propagation/install/url/domain-parsers.js +2 -1
  40. package/lib/dataflow/propagation/install/url/parse.js +3 -2
  41. package/lib/dataflow/propagation/install/url/searchParams.js +17 -10
  42. package/lib/dataflow/propagation/install/url/url.js +2 -1
  43. package/lib/dataflow/propagation/install/validator/hooks.js +2 -1
  44. package/lib/dataflow/sinks/install/child-process.js +1 -1
  45. package/lib/dataflow/sinks/install/eval.js +1 -1
  46. package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +2 -2
  47. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +1 -1
  48. package/lib/dataflow/sinks/install/fs.js +1 -1
  49. package/lib/dataflow/sinks/install/function.js +1 -1
  50. package/lib/dataflow/sinks/install/http/request.js +1 -1
  51. package/lib/dataflow/sinks/install/http/server-response.js +1 -1
  52. package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +1 -1
  53. package/lib/dataflow/sinks/install/marsdb.js +1 -1
  54. package/lib/dataflow/sinks/install/mongodb.js +2 -2
  55. package/lib/dataflow/sinks/install/mssql.js +1 -1
  56. package/lib/dataflow/sinks/install/mysql.js +2 -2
  57. package/lib/dataflow/sinks/install/postgres.js +1 -1
  58. package/lib/dataflow/sinks/install/sequelize.js +1 -1
  59. package/lib/dataflow/sinks/install/sqlite3.js +1 -1
  60. package/lib/dataflow/sinks/install/vm.js +1 -1
  61. package/lib/dataflow/sources/handler.js +2 -2
  62. package/lib/dataflow/sources/install/http.js +1 -1
  63. package/lib/dataflow/tracker.js +1 -5
  64. package/lib/{dataflow/event-factory.js → event-factory.js} +57 -1
  65. package/lib/index.js +3 -1
  66. package/lib/session-configuration/common.js +19 -0
  67. package/lib/session-configuration/handlers.js +86 -0
  68. package/lib/session-configuration/index.js +5 -8
  69. package/lib/session-configuration/install/express-session.js +131 -0
  70. package/package.json +2 -2
  71. package/lib/session-configuration/install/http.js +0 -79
@@ -40,6 +40,7 @@ module.exports = function(core) {
40
40
  patcher,
41
41
  scopes: { sources },
42
42
  assess: {
43
+ eventFactory: { createSinkEvent },
43
44
  dataflow: {
44
45
  tracker,
45
46
  sinks: {
@@ -48,7 +49,6 @@ module.exports = function(core) {
48
49
  reportSafePositive,
49
50
  isSafeContentType
50
51
  },
51
- eventFactory: { createSinkEvent },
52
52
  },
53
53
  },
54
54
  } = core;
@@ -39,10 +39,10 @@ module.exports = function(core) {
39
39
  config,
40
40
  scopes: { sources },
41
41
  assess: {
42
+ eventFactory: { createSinkEvent },
42
43
  dataflow: {
43
44
  tracker,
44
45
  sinks: { isVulnerable, reportFindings, reportSafePositive },
45
- eventFactory: { createSinkEvent },
46
46
  },
47
47
  },
48
48
  } = core;
@@ -43,10 +43,10 @@ module.exports = function(core) {
43
43
  patcher,
44
44
  scopes: { sources, instrumentation },
45
45
  assess: {
46
+ eventFactory: { createSinkEvent },
46
47
  dataflow: {
47
48
  tracker,
48
49
  sinks: { isVulnerable, reportFindings },
49
- eventFactory: { createSinkEvent },
50
50
  },
51
51
  },
52
52
  } = core;
@@ -74,10 +74,10 @@ module.exports = function(core) {
74
74
  patcher,
75
75
  scopes: { sources, instrumentation },
76
76
  assess: {
77
+ eventFactory: { createSinkEvent },
77
78
  dataflow: {
78
79
  tracker,
79
- sinks: { isVulnerable, runInActiveSink, isLocked, reportFindings, reportSafePositive },
80
- eventFactory: { createSinkEvent }
80
+ sinks: { isVulnerable, runInActiveSink, isLocked, reportFindings, reportSafePositive }
81
81
  }
82
82
  }
83
83
  } = core;
@@ -39,10 +39,10 @@ module.exports = function(core) {
39
39
  config,
40
40
  scopes: { sources },
41
41
  assess: {
42
+ eventFactory: { createSinkEvent },
42
43
  dataflow: {
43
44
  tracker,
44
45
  sinks: { isVulnerable, isLocked, reportFindings, reportSafePositive },
45
- eventFactory: { createSinkEvent },
46
46
  },
47
47
  },
48
48
  } = core;
@@ -46,10 +46,10 @@ module.exports = function(core) {
46
46
  patcher,
47
47
  scopes: { sources },
48
48
  assess: {
49
+ eventFactory: { createSinkEvent },
49
50
  dataflow: {
50
51
  tracker,
51
- sinks: { isVulnerable, isLocked, reportFindings },
52
- eventFactory: { createSinkEvent },
52
+ sinks: { isVulnerable, isLocked, reportFindings }
53
53
  },
54
54
  },
55
55
  } = core;
@@ -30,10 +30,10 @@ module.exports = function(core) {
30
30
  patcher,
31
31
  scopes: { sources },
32
32
  assess: {
33
+ eventFactory: { createSinkEvent },
33
34
  dataflow: {
34
35
  tracker,
35
36
  sinks: { isVulnerable, isLocked, reportFindings, reportSafePositive },
36
- eventFactory: { createSinkEvent },
37
37
  },
38
38
  },
39
39
  } = core;
@@ -35,10 +35,10 @@ module.exports = function(core) {
35
35
  config,
36
36
  scopes: { sources },
37
37
  assess: {
38
+ eventFactory: { createSinkEvent },
38
39
  dataflow: {
39
40
  tracker,
40
41
  sinks: { isVulnerable, runInActiveSink, reportFindings, reportSafePositive },
41
- eventFactory: { createSinkEvent },
42
42
  },
43
43
  },
44
44
  } = core;
@@ -35,10 +35,10 @@ module.exports = function(core) {
35
35
  patcher,
36
36
  scopes: { sources },
37
37
  assess: {
38
+ eventFactory: { createSinkEvent },
38
39
  dataflow: {
39
40
  tracker,
40
41
  sinks: { isVulnerable, isLocked, reportFindings },
41
- eventFactory: { createSinkEvent },
42
42
  },
43
43
  },
44
44
  } = core;
@@ -48,6 +48,7 @@ module.exports = function(core) {
48
48
  patcher,
49
49
  scopes: { sources, instrumentation },
50
50
  assess: {
51
+ eventFactory: { createSinkEvent },
51
52
  dataflow: {
52
53
  tracker,
53
54
  sinks: {
@@ -57,7 +58,6 @@ module.exports = function(core) {
57
58
  reportFindings,
58
59
  reportSafePositive,
59
60
  },
60
- eventFactory: { createSinkEvent },
61
61
  },
62
62
  },
63
63
  } = core;
@@ -25,10 +25,10 @@ const {
25
25
  module.exports = function(core) {
26
26
  const {
27
27
  assess: {
28
+ eventFactory,
28
29
  dataflow: {
29
30
  sources,
30
- tracker,
31
- eventFactory
31
+ tracker
32
32
  }
33
33
  },
34
34
  config,
@@ -80,7 +80,7 @@ module.exports = function(core) {
80
80
  pre(data) {
81
81
  const [name = '', value] = data.args;
82
82
  if (toLowerCase(name) === 'content-type' && value) {
83
- scopes.sources.getStore().assess.responseData.contentType = value;
83
+ store.assess.responseData.contentType = value;
84
84
  }
85
85
  }
86
86
  });
@@ -21,11 +21,7 @@ const { isString } = require('@contrast/common');
21
21
  module.exports = function tracker(core) {
22
22
  const {
23
23
  assess: {
24
- dataflow: {
25
- eventFactory: {
26
- createdEvents
27
- }
28
- }
24
+ eventFactory: { createdEvents },
29
25
  },
30
26
  logger
31
27
  } = core;
@@ -26,7 +26,7 @@ module.exports = function(core) {
26
26
  scopes: { sources },
27
27
  } = core;
28
28
 
29
- const eventFactory = core.assess.dataflow.eventFactory = {};
29
+ const eventFactory = core.assess.eventFactory = {};
30
30
 
31
31
  eventFactory.createdEvents = new WeakSet();
32
32
 
@@ -214,5 +214,61 @@ module.exports = function(core) {
214
214
  return event;
215
215
  };
216
216
 
217
+ eventFactory.createSessionEvent = function(data) {
218
+ const {
219
+ context,
220
+ name = '',
221
+ moduleName,
222
+ methodName,
223
+ object = { value: null, tracked: false },
224
+ args = [],
225
+ result = { value: null, tracked: false },
226
+ source,
227
+ stacktraceOpts,
228
+ framework,
229
+ options
230
+ } = data;
231
+
232
+ if (!name) {
233
+ logger.debug({ data }, 'no sink event name');
234
+ return null;
235
+ }
236
+
237
+ if (
238
+ (!source || !source.match(annotationRegExp))
239
+ ) {
240
+ logger.debug({ data }, 'malformed or missing sink event source field');
241
+ return null;
242
+ }
243
+
244
+ let stack;
245
+ if (config.assess.stacktraces !== 'NONE') {
246
+ stack = createSnapshot(stacktraceOpts)();
247
+ } else {
248
+ stack = [];
249
+ }
250
+
251
+ const event = {
252
+ args,
253
+ context,
254
+ history: [],
255
+ name,
256
+ moduleName,
257
+ methodName,
258
+ object,
259
+ result,
260
+ source,
261
+ stack,
262
+ tags: {},
263
+ time: Date.now(),
264
+ framework,
265
+ options,
266
+ };
267
+
268
+ eventFactory.createdEvents.add(event);
269
+
270
+ return event;
271
+ };
272
+
217
273
  return eventFactory;
218
274
  };
package/lib/index.js CHANGED
@@ -19,6 +19,7 @@ const { callChildComponentMethodsSync } = require('@contrast/common');
19
19
  const sessionConfiguration = require('./session-configuration');
20
20
  const dataflow = require('./dataflow');
21
21
  const responseScanning = require('./response-scanning');
22
+ const eventFactory = require('./event-factory');
22
23
 
23
24
  module.exports = function assess(core) {
24
25
  if (!core.config.assess.enable) return;
@@ -27,9 +28,10 @@ module.exports = function assess(core) {
27
28
 
28
29
  // Does this order matter? Probably not
29
30
  // 1. dataflow
30
- sessionConfiguration(core);
31
+ eventFactory(core);
31
32
  dataflow(core);
32
33
  responseScanning(core);
34
+ sessionConfiguration(core);
33
35
 
34
36
  // crypto
35
37
  // static (in coordination with rewriter)
@@ -0,0 +1,19 @@
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
+ 'use strict';
16
+
17
+ module.exports = {
18
+ patchType: 'session-configuration'
19
+ };
@@ -0,0 +1,86 @@
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
+
18
+ const {
19
+ Event,
20
+ SessionConfigurationRule: { HTTPONLY, SECURE_FLAG_MISSING },
21
+ } = require('@contrast/common');
22
+
23
+ module.exports = function (core) {
24
+ const {
25
+ assess: { sessionConfiguration },
26
+ messages,
27
+ } = core;
28
+
29
+ const checkCookieValue = (ruleId, sinkEvent, cookieValue, sourceContext) => {
30
+ if (cookieValue.includes(ruleId === HTTPONLY ? 'httponly' : 'secure')) {
31
+ return;
32
+ }
33
+
34
+ sessionConfiguration.reportFindings(sourceContext, {
35
+ ruleId,
36
+ sinkEvent,
37
+ props: {
38
+ evidence: cookieValue,
39
+ },
40
+ });
41
+ };
42
+
43
+ const handleCookie = (
44
+ sourceContext,
45
+ cookieValue,
46
+ ruleId,
47
+ sessionEvent
48
+ ) => {
49
+ if (Array.isArray(cookieValue)) {
50
+ return cookieValue.forEach((value) =>
51
+ checkCookieValue(ruleId, sessionEvent, value, sourceContext)
52
+ );
53
+ }
54
+
55
+ checkCookieValue(ruleId, sessionEvent, cookieValue, sourceContext);
56
+ };
57
+
58
+ sessionConfiguration.handleHttpOnly = function (
59
+ sourceContext,
60
+ cookieValue,
61
+ sessionEvent
62
+ ) {
63
+ handleCookie(sourceContext, cookieValue, HTTPONLY, sessionEvent);
64
+ };
65
+
66
+ sessionConfiguration.handleSecure = function (
67
+ sourceContext,
68
+ cookieValue,
69
+ sessionEvent
70
+ ) {
71
+ handleCookie(sourceContext, cookieValue, SECURE_FLAG_MISSING, sessionEvent);
72
+ };
73
+
74
+ // _sourceContext is unused
75
+ sessionConfiguration.reportFindings = function (
76
+ _sourceContext,
77
+ vulnerabilityMetadata
78
+ ) {
79
+ messages.emit(
80
+ Event.ASSESS_SESSION_CONFIGURATION_FINDING,
81
+ vulnerabilityMetadata
82
+ );
83
+ };
84
+
85
+ return sessionConfiguration;
86
+ };
@@ -15,16 +15,13 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const { callChildComponentMethodsSync, Event } = require('@contrast/common');
18
+ const { callChildComponentMethodsSync } = require('@contrast/common');
19
19
 
20
20
  module.exports = function(core) {
21
- const { messages } = core;
22
- const sessionConfiguration = core.assess.sessionConfiguration = {
23
- reportFindings(_sourceContext, vulnerabilityMetadata) {
24
- messages.emit(Event.ASSESS_SESSION_CONFIGURATION_FINDING, vulnerabilityMetadata);
25
- },
26
- };
27
- require('./install/http')(core);
21
+ const sessionConfiguration = core.assess.sessionConfiguration = {};
22
+
23
+ require('./handlers')(core);
24
+ require('./install/express-session')(core);
28
25
 
29
26
  sessionConfiguration.install = function() {
30
27
  callChildComponentMethodsSync(sessionConfiguration, 'install');
@@ -0,0 +1,131 @@
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
+ 'use strict';
16
+
17
+ const util = require('util');
18
+ const { toLowerCase } = require('@contrast/common');
19
+ const { patchType } = require('../common');
20
+
21
+ module.exports = function (core) {
22
+ const {
23
+ assess: {
24
+ eventFactory: { createSessionEvent },
25
+ sessionConfiguration: {
26
+ handleHttpOnly,
27
+ handleSecure,
28
+ },
29
+ },
30
+ depHooks,
31
+ patcher,
32
+ scopes: { sources },
33
+ } = core;
34
+
35
+ const expressSession = core.assess.sessionConfiguration.expressSession = {};
36
+
37
+ const inspect = patcher.unwrap(util.inspect);
38
+
39
+ expressSession.install = function () {
40
+ return depHooks.resolve({ name: 'express-session' }, (session) => {
41
+ // Return the hooked function as the export.
42
+ const hooked = patcher.patch(session, {
43
+ name: 'express.hookedSessionConstructor',
44
+ patchType,
45
+ post(data) {
46
+ const options = data.args[0];
47
+
48
+ // obfuscate the cookie secret
49
+ if (Array.isArray(data.args) && data.args[0] && data.args[0].secret) {
50
+ data.args[0].secret = '[HIDDEN]';
51
+ }
52
+
53
+ const { cookie } = options || {};
54
+ const hasOwnPropertyHttpOnly = cookie && Object.prototype.hasOwnProperty.call(
55
+ cookie,
56
+ 'httpOnly'
57
+ );
58
+
59
+ // httpOnly is true by default if it's not provided
60
+ const checkForHTTPOnly =
61
+ cookie && hasOwnPropertyHttpOnly
62
+ ? !(cookie.httpOnly === true)
63
+ : false;
64
+
65
+ // secure is false by default
66
+ const checkForSecure = cookie ? !(cookie.secure === true) : true;
67
+
68
+ // skip instrumentation since the options are set correctly
69
+ if (!checkForHTTPOnly && !checkForSecure) return;
70
+
71
+ const sessionEvent = createSessionEvent({
72
+ args: [{
73
+ tracked: false,
74
+ value: inspect(options),
75
+ }],
76
+ context: `expressSession(${inspect(data.args)})`,
77
+ history: [],
78
+ name: 'express.hookedSessionConstructor',
79
+ moduleName: 'express-session',
80
+ methodName: '',
81
+ object: {
82
+ tracked: false,
83
+ value: 'Express.Response',
84
+ },
85
+ result: {
86
+ tracked: false,
87
+ value: undefined,
88
+ },
89
+ source: 'P0',
90
+ stacktraceOpts: {
91
+ constructorOpt: data.hooked,
92
+ },
93
+ framework: 'express',
94
+ options,
95
+ });
96
+
97
+ patcher.patch(data, 'result', {
98
+ name: 'express-session.middleware',
99
+ patchType,
100
+ pre(data) {
101
+ const [, res] = data.args;
102
+
103
+ const sourceContext = sources.getStore()?.assess;
104
+ if (!sourceContext) return;
105
+
106
+ patcher.patch(res, 'setHeader', {
107
+ name: 'http.setHeader',
108
+ patchType,
109
+ pre({ args: [key, value] }) {
110
+ if (toLowerCase(key) !== 'set-cookie') return;
111
+
112
+ if (checkForHTTPOnly) {
113
+ handleHttpOnly(sourceContext, value, sessionEvent);
114
+ }
115
+
116
+ if (checkForSecure) {
117
+ handleSecure(sourceContext, value, sessionEvent);
118
+ }
119
+ }
120
+ });
121
+ },
122
+ });
123
+ }
124
+ });
125
+
126
+ return hooked;
127
+ });
128
+ };
129
+
130
+ return expressSession;
131
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/assess",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -13,7 +13,7 @@
13
13
  "node": ">= 14.15.0"
14
14
  },
15
15
  "dependencies": {
16
- "@contrast/distringuish": "^4.1.0",
16
+ "@contrast/distringuish": "^4.4.0",
17
17
  "@contrast/scopes": "1.4.0",
18
18
  "@contrast/common": "1.14.0",
19
19
  "parseurl": "^1.3.3"
@@ -1,79 +0,0 @@
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
-
18
- const { SessionConfigurationRule, split, toLowerCase } = require('@contrast/common');
19
-
20
- module.exports = function(core) {
21
- const {
22
- depHooks,
23
- patcher,
24
- scopes: { sources },
25
- assess: {
26
- sessionConfiguration: {
27
- reportFindings
28
- }
29
- }
30
- } = core;
31
- const http = core.assess.sessionConfiguration.httpInstrumentation = {};
32
-
33
- const patchType = 'session-configuration';
34
-
35
- http.install = function() {
36
- [
37
- { name: 'http', responseObj: 'ServerResponse' },
38
- { name: 'https', responseObj: 'ServerResponse' },
39
- { name: 'http2', responseObj: 'Http2ServerResponse' }
40
- ].forEach(({ name, responseObj }) => {
41
- depHooks.resolve({ name }, (module) => {
42
- patcher.patch(module[responseObj].prototype, 'setHeader', {
43
- name: `${name}.${responseObj}.prototype.setHeader`,
44
- patchType,
45
- post(data) {
46
- const sourceContext = sources.getStore()?.assess;
47
- if (!sourceContext) return;
48
-
49
- const [key, val] = data.args;
50
- if (key === 'Set-Cookie') {
51
- const [cookies] = val;
52
- const parsedCookies = split(toLowerCase(cookies), '; ');
53
-
54
- if (!parsedCookies.includes('httponly')) {
55
- reportFindings(sourceContext, {
56
- ruleId: SessionConfigurationRule.HTTPONLY,
57
- props: {
58
- evidence: cookies
59
- }
60
- });
61
- }
62
-
63
- if (!parsedCookies.includes('secure')) {
64
- reportFindings(sourceContext, {
65
- ruleId: SessionConfigurationRule.SECURE_FLAG_MISSING,
66
- props: {
67
- evidence: cookies
68
- }
69
- });
70
- }
71
- }
72
- }
73
- });
74
- });
75
- });
76
- };
77
-
78
- return http;
79
- };