@contrast/agent-bundle 5.46.0 → 5.47.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 (178) hide show
  1. package/README.md +1 -1
  2. package/node_modules/@contrast/agent/README.md +1 -1
  3. package/node_modules/@contrast/agent/package.json +12 -12
  4. package/node_modules/@contrast/agentify/package.json +15 -15
  5. package/node_modules/@contrast/architecture-components/package.json +5 -5
  6. package/node_modules/@contrast/assess/lib/{session-configuration → configuration-analysis}/common.js +1 -1
  7. package/node_modules/@contrast/assess/lib/{session-configuration → configuration-analysis}/handlers.js +23 -10
  8. package/node_modules/@contrast/assess/lib/{session-configuration → configuration-analysis}/index.js +6 -4
  9. package/node_modules/@contrast/assess/lib/configuration-analysis/install/apollo-server.js +92 -0
  10. package/node_modules/@contrast/assess/lib/{session-configuration → configuration-analysis}/install/express-session.js +2 -2
  11. package/node_modules/@contrast/assess/lib/{session-configuration → configuration-analysis}/install/fastify-cookie.js +2 -2
  12. package/node_modules/@contrast/assess/lib/configuration-analysis/install/graphql-yoga.js +90 -0
  13. package/node_modules/@contrast/assess/lib/{session-configuration → configuration-analysis}/install/hapi.js +2 -2
  14. package/node_modules/@contrast/assess/lib/{session-configuration → configuration-analysis}/install/koa.js +3 -3
  15. package/node_modules/@contrast/assess/lib/dataflow/propagation/install/string/substring.js +1 -1
  16. package/node_modules/@contrast/assess/lib/dataflow/sources/handler.js +9 -2
  17. package/node_modules/@contrast/assess/lib/dataflow/sources/index.js +2 -0
  18. package/node_modules/@contrast/assess/lib/dataflow/sources/install/fastify-websocket.js +63 -0
  19. package/node_modules/@contrast/assess/lib/dataflow/sources/install/http.js +42 -38
  20. package/node_modules/@contrast/assess/lib/dataflow/sources/install/koa/index.js +1 -1
  21. package/node_modules/@contrast/assess/lib/dataflow/sources/install/koa/koa-bodyparsers.js +76 -48
  22. package/node_modules/@contrast/assess/lib/dataflow/sources/install/koa/koa-multer.js +1 -1
  23. package/node_modules/@contrast/assess/lib/dataflow/sources/install/koa/koa-routers.js +2 -2
  24. package/node_modules/@contrast/assess/lib/dataflow/sources/install/koa/{koa2.js → koa.js} +3 -3
  25. package/node_modules/@contrast/assess/lib/dataflow/sources/install/socket.io.js +80 -0
  26. package/node_modules/@contrast/assess/lib/index.d.ts +4 -3
  27. package/node_modules/@contrast/assess/lib/index.js +1 -1
  28. package/node_modules/@contrast/assess/lib/policy.js +2 -2
  29. package/node_modules/@contrast/assess/package.json +12 -12
  30. package/node_modules/@contrast/common/lib/constants.d.ts +12 -4
  31. package/node_modules/@contrast/common/lib/constants.js +16 -7
  32. package/node_modules/@contrast/common/lib/types.d.ts +5 -1
  33. package/node_modules/@contrast/common/package.json +1 -1
  34. package/node_modules/@contrast/config/lib/common.js +1 -0
  35. package/node_modules/@contrast/config/lib/options.js +7 -1
  36. package/node_modules/@contrast/config/package.json +3 -3
  37. package/node_modules/@contrast/core/package.json +5 -5
  38. package/node_modules/@contrast/deadzones/package.json +5 -5
  39. package/node_modules/@contrast/dep-hooks/lib/package-finder.d.ts +2 -2
  40. package/node_modules/@contrast/dep-hooks/lib/package-finder.js +3 -2
  41. package/node_modules/@contrast/dep-hooks/package.json +4 -4
  42. package/node_modules/@contrast/esm-hooks/README.md +2 -2
  43. package/node_modules/@contrast/esm-hooks/package.json +6 -6
  44. package/node_modules/@contrast/instrumentation/package.json +5 -5
  45. package/node_modules/@contrast/library-analysis/lib/install/library-reporting/dep.json +149 -149
  46. package/node_modules/@contrast/library-analysis/lib/install/library-reporting/index.js +2 -11
  47. package/node_modules/@contrast/library-analysis/lib/install/library-reporting/utils.js +2 -0
  48. package/node_modules/@contrast/library-analysis/lib/install/library-usage/index.js +3 -1
  49. package/node_modules/@contrast/library-analysis/lib/util.js +0 -2
  50. package/node_modules/@contrast/library-analysis/package.json +4 -4
  51. package/node_modules/@contrast/logger/package.json +3 -3
  52. package/node_modules/@contrast/metrics/package.json +6 -6
  53. package/node_modules/@contrast/patcher/package.json +2 -2
  54. package/node_modules/@contrast/protect/lib/error-handlers/index.js +1 -1
  55. package/node_modules/@contrast/protect/lib/error-handlers/install/{koa2.js → koa.js} +4 -4
  56. package/node_modules/@contrast/protect/lib/index.d.ts +1 -1
  57. package/node_modules/@contrast/protect/lib/input-analysis/index.js +2 -3
  58. package/node_modules/@contrast/protect/lib/input-analysis/install/koa-bodyparsers.js +92 -0
  59. package/node_modules/@contrast/protect/lib/input-analysis/install/{koa2.js → koa.js} +5 -5
  60. package/node_modules/@contrast/protect/package.json +11 -11
  61. package/node_modules/@contrast/reporter/lib/reporters/contrast-ui/endpoints/application-activity/translations.js +6 -10
  62. package/node_modules/@contrast/reporter/lib/reporters/contrast-ui/endpoints/routes-observed.js +4 -0
  63. package/node_modules/@contrast/reporter/lib/reporters/contrast-ui/endpoints/traces/index.d.ts +1 -1
  64. package/node_modules/@contrast/reporter/lib/reporters/contrast-ui/endpoints/traces/index.js +1 -1
  65. package/node_modules/@contrast/reporter/lib/reporters/contrast-ui/endpoints/traces/translations.d.ts +1 -1
  66. package/node_modules/@contrast/reporter/lib/reporters/contrast-ui/endpoints/traces/translations.js +22 -9
  67. package/node_modules/@contrast/reporter/lib/reporters/file.js +1 -1
  68. package/node_modules/@contrast/reporter/package.json +6 -6
  69. package/node_modules/@contrast/rewriter/package.json +5 -5
  70. package/node_modules/@contrast/route-coverage/lib/index.d.ts +0 -2
  71. package/node_modules/@contrast/route-coverage/lib/index.js +10 -1
  72. package/node_modules/@contrast/route-coverage/lib/install/express/express5.js +16 -1
  73. package/node_modules/@contrast/route-coverage/lib/install/fastify.js +25 -15
  74. package/node_modules/@contrast/route-coverage/lib/install/graphql.js +6 -1
  75. package/node_modules/@contrast/route-coverage/lib/install/koa.js +1 -1
  76. package/node_modules/@contrast/route-coverage/lib/install/socket.io.js +127 -0
  77. package/node_modules/@contrast/route-coverage/package.json +8 -8
  78. package/node_modules/@contrast/scopes/package.json +5 -5
  79. package/node_modules/@contrast/sec-obs/package.json +9 -9
  80. package/node_modules/@contrast/sources/lib/index.js +65 -22
  81. package/node_modules/@contrast/sources/lib/index.test.js +78 -33
  82. package/node_modules/@contrast/sources/lib/source-info.js +1 -10
  83. package/node_modules/@contrast/sources/package.json +3 -3
  84. package/node_modules/@contrast/telemetry/package.json +5 -5
  85. package/node_modules/@types/node/README.md +1 -1
  86. package/node_modules/@types/node/assert.d.ts +37 -2
  87. package/node_modules/@types/node/buffer.buffer.d.ts +9 -0
  88. package/node_modules/@types/node/buffer.d.ts +8 -4
  89. package/node_modules/@types/node/child_process.d.ts +65 -42
  90. package/node_modules/@types/node/cluster.d.ts +4 -5
  91. package/node_modules/@types/node/crypto.d.ts +1079 -338
  92. package/node_modules/@types/node/dgram.d.ts +9 -8
  93. package/node_modules/@types/node/diagnostics_channel.d.ts +0 -2
  94. package/node_modules/@types/node/dns.d.ts +1 -1
  95. package/node_modules/@types/node/events.d.ts +1 -1
  96. package/node_modules/@types/node/fs/promises.d.ts +39 -21
  97. package/node_modules/@types/node/fs.d.ts +104 -87
  98. package/node_modules/@types/node/globals.d.ts +2 -0
  99. package/node_modules/@types/node/globals.typedarray.d.ts +19 -0
  100. package/node_modules/@types/node/http.d.ts +66 -27
  101. package/node_modules/@types/node/http2.d.ts +178 -52
  102. package/node_modules/@types/node/https.d.ts +91 -62
  103. package/node_modules/@types/node/index.d.ts +2 -0
  104. package/node_modules/@types/node/inspector.d.ts +24 -0
  105. package/node_modules/@types/node/inspector.generated.d.ts +181 -0
  106. package/node_modules/@types/node/net.d.ts +12 -11
  107. package/node_modules/@types/node/os.d.ts +14 -3
  108. package/node_modules/@types/node/package.json +3 -3
  109. package/node_modules/@types/node/perf_hooks.d.ts +6 -8
  110. package/node_modules/@types/node/process.d.ts +12 -23
  111. package/node_modules/@types/node/readline/promises.d.ts +1 -1
  112. package/node_modules/@types/node/sea.d.ts +9 -0
  113. package/node_modules/@types/node/sqlite.d.ts +119 -10
  114. package/node_modules/@types/node/stream/consumers.d.ts +2 -2
  115. package/node_modules/@types/node/stream/web.d.ts +6 -55
  116. package/node_modules/@types/node/stream.d.ts +38 -23
  117. package/node_modules/@types/node/string_decoder.d.ts +2 -2
  118. package/node_modules/@types/node/test.d.ts +29 -3
  119. package/node_modules/@types/node/tls.d.ts +90 -66
  120. package/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +10 -2
  121. package/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +16 -0
  122. package/node_modules/@types/node/ts5.6/index.d.ts +2 -0
  123. package/node_modules/@types/node/ts5.7/index.d.ts +2 -0
  124. package/node_modules/@types/node/url.d.ts +2 -2
  125. package/node_modules/@types/node/util.d.ts +12 -3
  126. package/node_modules/@types/node/v8.d.ts +38 -5
  127. package/node_modules/@types/node/vm.d.ts +169 -88
  128. package/node_modules/@types/node/wasi.d.ts +1 -1
  129. package/node_modules/@types/node/web-globals/crypto.d.ts +32 -0
  130. package/node_modules/@types/node/web-globals/streams.d.ts +22 -0
  131. package/node_modules/@types/node/worker_threads.d.ts +76 -1
  132. package/node_modules/@types/node/zlib.d.ts +25 -24
  133. package/node_modules/axios/CHANGELOG.md +403 -357
  134. package/node_modules/axios/README.md +80 -49
  135. package/node_modules/axios/dist/axios.js +121 -46
  136. package/node_modules/axios/dist/axios.js.map +1 -1
  137. package/node_modules/axios/dist/axios.min.js +2 -2
  138. package/node_modules/axios/dist/axios.min.js.map +1 -1
  139. package/node_modules/axios/dist/browser/axios.cjs +126 -57
  140. package/node_modules/axios/dist/browser/axios.cjs.map +1 -1
  141. package/node_modules/axios/dist/esm/axios.js +126 -57
  142. package/node_modules/axios/dist/esm/axios.js.map +1 -1
  143. package/node_modules/axios/dist/esm/axios.min.js +2 -2
  144. package/node_modules/axios/dist/esm/axios.min.js.map +1 -1
  145. package/node_modules/axios/dist/node/axios.cjs +346 -97
  146. package/node_modules/axios/dist/node/axios.cjs.map +1 -1
  147. package/node_modules/axios/index.d.cts +4 -0
  148. package/node_modules/axios/index.d.ts +4 -0
  149. package/node_modules/axios/lib/adapters/adapters.js +85 -40
  150. package/node_modules/axios/lib/adapters/fetch.js +1 -1
  151. package/node_modules/axios/lib/adapters/http.js +220 -42
  152. package/node_modules/axios/lib/core/InterceptorManager.js +1 -1
  153. package/node_modules/axios/lib/core/mergeConfig.js +4 -4
  154. package/node_modules/axios/lib/env/data.js +1 -1
  155. package/node_modules/axios/lib/helpers/HttpStatusCode.js +6 -0
  156. package/node_modules/axios/lib/helpers/bind.js +7 -0
  157. package/node_modules/axios/lib/helpers/cookies.js +24 -13
  158. package/node_modules/axios/package.json +9 -4
  159. package/node_modules/detect-libc/lib/filesystem.js +1 -1
  160. package/node_modules/detect-libc/package.json +3 -1
  161. package/node_modules/node-abi/abi_registry.json +12 -2
  162. package/node_modules/node-abi/package.json +3 -6
  163. package/node_modules/semver/classes/range.js +1 -0
  164. package/node_modules/semver/classes/semver.js +19 -5
  165. package/node_modules/semver/internal/identifiers.js +4 -0
  166. package/node_modules/semver/package.json +3 -3
  167. package/node_modules/undici-types/agent.d.ts +1 -0
  168. package/node_modules/undici-types/diagnostics-channel.d.ts +0 -1
  169. package/node_modules/undici-types/errors.d.ts +5 -15
  170. package/node_modules/undici-types/eventsource.d.ts +6 -1
  171. package/node_modules/undici-types/index.d.ts +4 -1
  172. package/node_modules/undici-types/interceptors.d.ts +5 -0
  173. package/node_modules/undici-types/package.json +1 -1
  174. package/node_modules/undici-types/snapshot-agent.d.ts +5 -3
  175. package/node_modules/undici-types/webidl.d.ts +82 -21
  176. package/package.json +3 -3
  177. package/node_modules/@contrast/protect/lib/input-analysis/install/koa-body5.js +0 -63
  178. package/node_modules/@contrast/protect/lib/input-analysis/install/koa-bodyparser4.js +0 -64
@@ -15,7 +15,11 @@
15
15
  // @ts-check
16
16
  'use strict';
17
17
 
18
- const { callChildComponentMethodsSync, Event } = require('@contrast/common');
18
+ const {
19
+ callChildComponentMethodsSync,
20
+ Event,
21
+ RouteType,
22
+ } = require('@contrast/common');
19
23
 
20
24
  /**
21
25
  * @param {import('.').Core & {
@@ -41,6 +45,8 @@ module.exports = function init(core) {
41
45
  const id = routeIdentifier(info.method, info.signature);
42
46
  if (routeInfo.get(id)) return;
43
47
 
48
+ if (!info.type) info.type = RouteType.HTTP;
49
+
44
50
  logger.trace({ info }, 'Discovered new route:');
45
51
  routeInfo.set(id, info);
46
52
  },
@@ -100,9 +106,11 @@ module.exports = function init(core) {
100
106
 
101
107
  recentlyObserved.add(route.signature);
102
108
  logger.trace({ info }, 'Observed route:');
109
+
103
110
  // these events need source correlation
104
111
  messages.emit(Event.ROUTE_COVERAGE_OBSERVATION, {
105
112
  ...route,
113
+ type: info.type ?? route.type ?? RouteType.HTTP,
106
114
  sourceInfo: store?.sourceInfo,
107
115
  });
108
116
  },
@@ -119,6 +127,7 @@ module.exports = function init(core) {
119
127
  require('./install/hapi')(core);
120
128
  require('./install/koa')(core);
121
129
  require('./install/restify')(core);
130
+ core.initComponentSync(require('./install/socket.io'));
122
131
 
123
132
  messages.on(Event.SERVER_LISTENING, () => {
124
133
  // we wait to report in timers event loop phase, this way we can
@@ -20,6 +20,7 @@ const {
20
20
  set,
21
21
  isString,
22
22
  Event,
23
+ RouteType,
23
24
  primordials: {
24
25
  ArrayPrototypeJoin,
25
26
  StringPrototypeSubstring,
@@ -310,12 +311,14 @@ class ExpressInstrumentation {
310
311
  const method = StringPrototypeToLowerCase.call(data.args[0].method || '');
311
312
  const template = ArrayPrototypeJoin.call(store.templateSegments, '') || '/';
312
313
 
314
+
313
315
  if (instance[kMetaKey]?.observables?.[template]) {
314
316
  self.observe({
315
317
  url: data.args[0].originalUrl,
316
318
  normalizedUrl: template,
317
319
  method,
318
320
  signature: instance[kMetaKey].observables[template],
321
+ type: instance[kMetaKey].routeType,
319
322
  });
320
323
  } else {
321
324
  core.logger.error({
@@ -335,7 +338,7 @@ class ExpressInstrumentation {
335
338
  }
336
339
 
337
340
  discover(info) {
338
- const { method, observables } = info;
341
+ const { method, observables, routeType } = info;
339
342
  if (!method || !observables) return;
340
343
 
341
344
  for (const [normalizedUrl, signature] of Object.entries(observables)) {
@@ -344,6 +347,7 @@ class ExpressInstrumentation {
344
347
  normalizedUrl,
345
348
  method,
346
349
  signature,
350
+ type: routeType,
347
351
  framework: 'express',
348
352
  });
349
353
  }
@@ -383,11 +387,22 @@ class ExpressInstrumentation {
383
387
  // mounted routers aren't discoverable since they themselves don't
384
388
  // represent routes, they dispatch to sub routers/route handlers.
385
389
  if (value.name != 'router' && value.handle?.name != 'router') {
390
+ let routeType;
391
+ if (value[kMetaKey]?.method == 'use') {
392
+ // if the handler is registered via `use` method, there are no
393
+ // associated HTTP methods. this use case is considered middleware.
394
+ if (!this.core.config.getEffectiveValue('assess.report_middleware_routes')) return;
395
+ routeType = RouteType.MIDDLEWARE;
396
+ } else {
397
+ routeType = RouteType.HTTP;
398
+ }
399
+
386
400
  // `value` is a terminal Layer with observable signatures.
387
401
  // emit discovery after appending metadata.
388
402
  if (value[kMetaKey]) {
389
403
  const observables = this.generateObservables(metas, value.handle);
390
404
  if (observables) {
405
+ value[kMetaKey].routeType = routeType;
391
406
  if (!value[kMetaKey].observables) {
392
407
  value[kMetaKey].observables = observables;
393
408
  } else {
@@ -15,7 +15,10 @@
15
15
  'use strict';
16
16
 
17
17
  const { getFastifyMethods } = require('../utils/methods');
18
- const { primordials: { StringPrototypeToLowerCase, StringPrototypeSplit } } = require('@contrast/common');
18
+ const {
19
+ primordials: { StringPrototypeToLowerCase, StringPrototypeSplit },
20
+ RouteType,
21
+ } = require('@contrast/common');
19
22
  const { patchType } = require('./../utils/route-info');
20
23
 
21
24
  // Spec: https://contrast.atlassian.net/wiki/spaces/NOD/pages/3454861621/Node.js+Agent+Route+Signatures#Fastify
@@ -35,7 +38,7 @@ module.exports = function init(core) {
35
38
  return route?.[kRoutePrefix];
36
39
  }
37
40
 
38
- function createRouteInfo(method, url, fullyDeclared) {
41
+ function createRouteInfo(method, url, fullyDeclared, type) {
39
42
  method = StringPrototypeToLowerCase.call(method);
40
43
 
41
44
  const signature = fullyDeclared
@@ -47,6 +50,7 @@ module.exports = function init(core) {
47
50
  url,
48
51
  method,
49
52
  normalizedUrl: url,
53
+ type,
50
54
  framework: 'fastify'
51
55
  };
52
56
  return routeInfo;
@@ -54,14 +58,18 @@ module.exports = function init(core) {
54
58
 
55
59
  function patchHandler(route, handle, routeInfo) {
56
60
  const pre = ({ args }) => {
57
- const [req] = args;
58
-
59
- // todo(NODE-3793): support for @fastify/websocket
60
- if (req.constructor.name == 'WebSocket') return;
61
-
61
+ let req, type;
62
+ if (args[0]?.constructor?.name == 'WebSocket') {
63
+ req = args[1];
64
+ type = RouteType.MESSAGE_BROKER;
65
+ } else {
66
+ req = args[0];
67
+ type = RouteType.HTTP;
68
+ }
62
69
  const method = StringPrototypeToLowerCase.call(req.raw?.method);
63
70
  const [url] = StringPrototypeSplit.call(req.url, /\?/);
64
- routeCoverage.observe({ ...routeInfo, url, method });
71
+
72
+ routeCoverage.observe({ ...routeInfo, method, type, url });
65
73
  };
66
74
 
67
75
  const name = `fastify.route.${handle}`;
@@ -81,24 +89,26 @@ module.exports = function init(core) {
81
89
  }
82
90
  }
83
91
 
84
- function discoverAndPatch(method, path, handler, handle, methods, fullyDeclared) {
92
+ function discoverAndPatch(method, path, routeObj, handle, methods, fullyDeclared) {
93
+ const type = routeObj?.options?.websocket ? RouteType.MESSAGE_BROKER : RouteType.HTTP;
94
+
85
95
  if (Array.isArray(method)) {
86
96
  // If all valid methods are included in `method` then .all shorthand was most likely used
87
97
  if (methods.every(m => method.includes(m))) {
88
- const routeInfo = createRouteInfo('all', path, fullyDeclared);
98
+ const routeInfo = createRouteInfo('all', path, fullyDeclared, type);
89
99
  routeCoverage.discover(routeInfo);
90
- patchHandler(handler, handle, routeInfo);
100
+ patchHandler(routeObj, handle, routeInfo);
91
101
  } else {
92
102
  method.forEach((verb) => {
93
- const routeInfo = createRouteInfo(verb, path, fullyDeclared);
103
+ const routeInfo = createRouteInfo(verb, path, fullyDeclared, type);
94
104
  routeCoverage.discover(routeInfo);
95
- patchHandler(handler, handle, routeInfo);
105
+ patchHandler(routeObj, handle, routeInfo);
96
106
  });
97
107
  }
98
108
  } else {
99
- const routeInfo = createRouteInfo(method, path, fullyDeclared);
109
+ const routeInfo = createRouteInfo(method, path, fullyDeclared, type);
100
110
  routeCoverage.discover(routeInfo);
101
- patchHandler(handler, handle, routeInfo);
111
+ patchHandler(routeObj, handle, routeInfo);
102
112
  }
103
113
  }
104
114
 
@@ -14,7 +14,10 @@
14
14
  */
15
15
  'use strict';
16
16
 
17
- const { primordials: { ArrayPrototypeJoin } } = require('@contrast/common');
17
+ const {
18
+ RouteType,
19
+ primordials: { ArrayPrototypeJoin },
20
+ } = require('@contrast/common');
18
21
  const { patchType } = require('./../utils/route-info');
19
22
 
20
23
  module.exports = function init(core) {
@@ -61,6 +64,7 @@ module.exports = function init(core) {
61
64
  method,
62
65
  normalizedUrl,
63
66
  signature,
67
+ type: RouteType.HTTP, // todo: extend existing types
64
68
  framework: 'graphql',
65
69
  });
66
70
  });
@@ -77,6 +81,7 @@ module.exports = function init(core) {
77
81
  method: store.sourceInfo?.method,
78
82
  normalizedUrl,
79
83
  signature,
84
+ type: RouteType.HTTP, // todo: extend existing types
80
85
  url: normalizedUrl,
81
86
  framework: 'graphql',
82
87
  });
@@ -36,7 +36,7 @@ module.exports = function init(core) {
36
36
 
37
37
  return core.routeCoverage.koa = {
38
38
  install() {
39
- depHooks.resolve({ name: 'koa', version: '>=2.3.0 <3' }, (Koa) => {
39
+ depHooks.resolve({ name: 'koa', version: '>=2.3.0 <4' }, (Koa) => {
40
40
  // Koa uses its own routing library @koa/router to define routes before
41
41
  // mounting them on the app with .use so instrumenting use and traversing
42
42
  // the constructed routes is the more technically correct approach than
@@ -0,0 +1,127 @@
1
+ /*
2
+ * Copyright: 2025 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 { set, RouteType, primordials } = require('@contrast/common');
18
+ const Core = require('@contrast/core/lib/ioc/core');
19
+ const { patchType } = require('../utils/route-info');
20
+
21
+ const COMPONENT_NAME = 'routeCoverage.socketio';
22
+ const FRAMEWORK = 'socket.io';
23
+ const kServerMountPath = Symbol('cs:socket.io.path');
24
+ const kServerRouteSignature = Symbol('cs:socket.io.route-signature');
25
+
26
+ module.exports = Core.makeComponent({
27
+ name: COMPONENT_NAME,
28
+ factory: (core) => new SocketIORouteCoverage(core),
29
+ });
30
+
31
+ class SocketIORouteCoverage {
32
+ constructor(core) {
33
+ Object.defineProperty(this, 'core', { value: core });
34
+ set(core, COMPONENT_NAME, this);
35
+ }
36
+
37
+ makePath(mountPath) {
38
+ // we append trailing "/" since that's part of socket.io protocol
39
+ return mountPath.endsWith('/') ? mountPath : `${mountPath}/`;
40
+ }
41
+
42
+ makeSignature(mountPath) {
43
+ return `Socket.IO ${mountPath}`;
44
+ }
45
+
46
+ install() {
47
+ const self = this;
48
+ const {
49
+ depHooks,
50
+ patcher,
51
+ routeCoverage,
52
+ } = this.core;
53
+
54
+ depHooks.resolve(
55
+ { name: 'socket.io', version: '4' },
56
+ /** @param {import('socket.io-4')} xport the exported socket.io module */
57
+ (xport) => {
58
+ patcher.patch(xport.Server.prototype, 'initEngine', {
59
+ name: 'socket.io.Server.prototype.initEngine',
60
+ patchType,
61
+ post(data) {
62
+ if (!this._path) return;
63
+ const [httpServer] = data.args;
64
+ const path = self.makePath(this._path);
65
+ const signature = self.makeSignature(path);
66
+
67
+ this.eio[kServerMountPath] = path;
68
+ this.eio[kServerRouteSignature] = signature;
69
+
70
+ routeCoverage.discover({
71
+ framework: FRAMEWORK,
72
+ signature,
73
+ method: 'all',
74
+ normalizedUri: path,
75
+ type: RouteType.MESSAGE_BROKER,
76
+ url: path,
77
+ });
78
+
79
+ // handle observation for HTTP polling; this doesn't emit when upgrading to ws://
80
+ httpServer.on(
81
+ 'request',
82
+ /** @param {import('http').IncomingMessage} req */
83
+ (req /*, res */) => {
84
+ if (req.url?.startsWith?.(path)) {
85
+ routeCoverage.observe({
86
+ framework: FRAMEWORK,
87
+ method: primordials.StringPrototypeToLowerCase.call(req.method),
88
+ normalizedUrl: path,
89
+ signature,
90
+ type: RouteType.MESSAGE_BROKER,
91
+ url: path,
92
+ });
93
+ }
94
+ });
95
+ }
96
+ });
97
+ }
98
+ );
99
+
100
+ // this is to handle observations for websocket upgrades; 1 upgrade request counts as single observation
101
+ depHooks.resolve(
102
+ { name: 'engine.io', version: '6' },
103
+ /** @param {import('engine.io')} xport the exported engine.io module */
104
+ (xport) => {
105
+ patcher.patch(xport.Server.prototype, 'onWebSocket', {
106
+ name: 'engine.io.Server.prototype.onWebSocket',
107
+ patchType,
108
+ pre(data) {
109
+ const eioServer = this;
110
+ const [req] = data.args;
111
+
112
+ if (!eioServer[kServerMountPath] || !eioServer[kServerRouteSignature]) return;
113
+
114
+ routeCoverage.observe({
115
+ framework: FRAMEWORK,
116
+ method: req.method,
117
+ normalizedUrl: eioServer[kServerMountPath],
118
+ signature: eioServer[kServerRouteSignature],
119
+ type: RouteType.MESSAGE_BROKER,
120
+ url: eioServer[kServerMountPath],
121
+ });
122
+ },
123
+ });
124
+ }
125
+ );
126
+ }
127
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/route-coverage",
3
- "version": "1.50.0",
3
+ "version": "1.51.0",
4
4
  "description": "Handles route discovery and observation",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -20,14 +20,14 @@
20
20
  "test": "bash ../scripts/test.sh"
21
21
  },
22
22
  "dependencies": {
23
- "@contrast/common": "1.37.0",
24
- "@contrast/config": "1.53.0",
25
- "@contrast/core": "1.58.0",
26
- "@contrast/dep-hooks": "1.27.0",
23
+ "@contrast/common": "1.38.0",
24
+ "@contrast/config": "1.54.0",
25
+ "@contrast/core": "1.59.0",
26
+ "@contrast/dep-hooks": "1.28.0",
27
27
  "@contrast/fn-inspect": "^5.0.2",
28
- "@contrast/logger": "1.31.0",
29
- "@contrast/patcher": "1.30.0",
30
- "@contrast/scopes": "1.28.0",
28
+ "@contrast/logger": "1.32.0",
29
+ "@contrast/patcher": "1.31.0",
30
+ "@contrast/scopes": "1.29.0",
31
31
  "semver": "^7.6.0"
32
32
  }
33
33
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/scopes",
3
- "version": "1.28.0",
3
+ "version": "1.29.0",
4
4
  "description": "Handles AsyncLocalStorage scopes",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -20,9 +20,9 @@
20
20
  "test": "bash ../scripts/test.sh"
21
21
  },
22
22
  "dependencies": {
23
- "@contrast/core": "1.58.0",
24
- "@contrast/dep-hooks": "1.27.0",
25
- "@contrast/logger": "1.31.0",
26
- "@contrast/patcher": "1.30.0"
23
+ "@contrast/core": "1.59.0",
24
+ "@contrast/dep-hooks": "1.28.0",
25
+ "@contrast/logger": "1.32.0",
26
+ "@contrast/patcher": "1.31.0"
27
27
  }
28
28
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/sec-obs",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Contrast service providing framework-agnostic Observability support",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -17,14 +17,14 @@
17
17
  "test": "bash ../scripts/test.sh"
18
18
  },
19
19
  "dependencies": {
20
- "@contrast/common": "1.37.0",
21
- "@contrast/config": "1.53.0",
22
- "@contrast/core": "1.58.0",
23
- "@contrast/dep-hooks": "1.27.0",
24
- "@contrast/logger": "1.31.0",
25
- "@contrast/patcher": "1.30.0",
26
- "@contrast/rewriter": "1.35.0",
27
- "@contrast/scopes": "1.28.0",
20
+ "@contrast/common": "1.38.0",
21
+ "@contrast/config": "1.54.0",
22
+ "@contrast/core": "1.59.0",
23
+ "@contrast/dep-hooks": "1.28.0",
24
+ "@contrast/logger": "1.32.0",
25
+ "@contrast/patcher": "1.31.0",
26
+ "@contrast/rewriter": "1.36.0",
27
+ "@contrast/scopes": "1.29.0",
28
28
  "@opentelemetry/api": "^1.9.0",
29
29
  "@opentelemetry/exporter-metrics-otlp-http": "^0.57.1",
30
30
  "@opentelemetry/exporter-trace-otlp-http": "^0.57.1",
@@ -23,6 +23,11 @@ const NormalizedUriMapper = require('./normalized-uri-mapper');
23
23
  const { HttpSourceInfo } = require('./source-info');
24
24
 
25
25
  const componentName = 'sources';
26
+ const SourceType = {
27
+ HTTP: 'HTTP',
28
+ WEBSOCKET: 'WEBSOCKET',
29
+ };
30
+
26
31
 
27
32
  module.exports = Core.makeComponent({
28
33
  name: componentName,
@@ -48,20 +53,40 @@ class Sources {
48
53
  const { _hooks, _normalizedUriMapper, core } = this;
49
54
 
50
55
  return function (next, data) {
51
- const { args: [event, req, res] } = data;
52
-
53
- if (event !== 'request') {
54
- if (event === 'listening') {
55
- // take a snapshot of Perf.all at this point. this will get logged
56
- // at some point on the perf interval timer.
57
- core.Perf.mark('listening');
58
- core.messages.emit(Event.SERVER_LISTENING, { type: serverType, server: data.obj });
59
- }
56
+ const { args: [event, req, resOrSocket] } = data;
57
+
58
+ if (event === 'listening') {
59
+ // take a snapshot of Perf.all at this point. this will get logged
60
+ // at some point on the perf interval timer.
61
+ core.Perf.mark('listening');
62
+ core.messages.emit(Event.SERVER_LISTENING, { type: serverType, server: data.obj });
60
63
  return next();
61
64
  }
62
65
 
63
- core.Perf.requestCount += 1;
66
+ const isUpgrade = event === 'upgrade';
67
+ let protocol = serverType == 'http' ? 'http' : 'https';
68
+
69
+ if (isUpgrade) {
70
+ for (let i = 0; i < req.rawHeaders.length; i += 2) {
71
+ if (req.rawHeaders[i].toLowerCase?.() == 'upgrade') {
72
+ protocol = req.rawHeaders[i + 1]?.toLowerCase?.();
73
+ break;
74
+ }
75
+ }
76
+ }
77
+
78
+ // For HTTP servers we run the "request" and "upgrade" events in a source
79
+ // scope. We only support "websocket" upgrades currently, but this can be
80
+ // extended. Support for non-HTTP sources is expected and will require new
81
+ // instrumentation and possibly model alterations.
82
+ if (
83
+ event !== 'request' &&
84
+ (!isUpgrade || (isUpgrade && protocol !== 'websocket'))
85
+ ) {
86
+ return next();
87
+ }
64
88
 
89
+ // websocket sources are http
65
90
  const sourceInfo = new HttpSourceInfo({
66
91
  serverType,
67
92
  raw: req,
@@ -69,20 +94,38 @@ class Sources {
69
94
  });
70
95
  const store = { sourceInfo };
71
96
 
72
- onFinished(res, (/* err, req */) => {
73
- core.messages.emit(Event.RESPONSE_FINISH, store);
74
- });
97
+ if (isUpgrade) {
98
+ core.patcher.patch(resOrSocket, 'emit', {
99
+ name: `${serverType}.socket.emit`,
100
+ patchType: 'sources',
101
+ around(next) {
102
+ return core.scopes.sources.run(store, next);
103
+ },
104
+ });
105
+ } else {
106
+ onFinished(resOrSocket, (/* err, req */) => {
107
+ core.messages.emit(Event.RESPONSE_FINISH, store);
108
+ });
109
+ }
110
+
111
+ core.Perf.requestCount += 1;
75
112
 
76
113
  return core.scopes.sources.run(store, () => {
77
- if (_hooks._events.onSource) {
78
- _hooks.emit('onSource', {
79
- // future: non-http sources will have their own type
80
- sourceType: 'HTTP',
81
- store,
82
- incomingMessage: req,
83
- serverResponse: res,
84
- });
85
- }
114
+ const sourceType = protocol == 'websocket' ? SourceType.WEBSOCKET : SourceType.HTTP;
115
+ const eventArg = {
116
+ sourceType,
117
+ store,
118
+ incomingMessage: req,
119
+ };
120
+
121
+ if (sourceType == SourceType.HTTP)
122
+ eventArg.serverResponse = resOrSocket;
123
+
124
+ else if (sourceType == SourceType.WEBSOCKET)
125
+ eventArg.socket = resOrSocket;
126
+
127
+ if (_hooks._events.onSource)
128
+ _hooks.emit('onSource', eventArg);
86
129
 
87
130
  return next();
88
131
  });