@newrelic/security-agent 1.3.0 → 1.5.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ ### v1.5.0 (2024-08-14)
2
+ #### Features
3
+ * Support for Node.js v22.x
4
+ #### Bug fixes
5
+ * Fix for traceId in error reporting
6
+ #### Miscellaneous chores
7
+ * (deps): bumped axios from 1.6.8 to 1.7.4
8
+ * (deps-dev): bumped ws from 7.5.9 to 8.18.0
9
+ #### Continuous integration
10
+ * Added Node.js v22.x to unit tests
11
+
12
+ ### v1.4.0 (2024-06-24)
13
+ #### Features
14
+ * Added support to report application's errors while IAST scanning
15
+ * Support to detect gRPC API endpoints
16
+ #### Bug fixes
17
+ * Remove additional headers added by IAST client
18
+ * Fix for uncaught exception reporting
19
+ #### Miscellaneous chores
20
+ * Updated package.json to bump ws from 8.14.2 to 8.17.1
21
+ * (deps-dev): bump @grpc/grpc-js from 1.9.12 to 1.10.9
22
+ * (deps-dev): bump braces from 3.0.2 to 3.0.3
23
+ * (deps): bump ws from 8.14.2 to 8.17.1
24
+
1
25
  ### v1.3.0 (2024-05-31)
2
26
  #### Features
3
27
  * Added route field in security event for API endpoint mapping
@@ -77,7 +77,7 @@ code, the source code can be found at [https://github.com/newrelic/csec-node-age
77
77
 
78
78
  ### axios
79
79
 
80
- This product includes source derived from [axios](https://github.com/axios/axios) ([v1.6.8](https://github.com/axios/axios/tree/v1.6.8)), distributed under the [MIT License](https://github.com/axios/axios/blob/v1.6.8/LICENSE):
80
+ This product includes source derived from [axios](https://github.com/axios/axios) ([v1.7.4](https://github.com/axios/axios/tree/v1.7.4)), distributed under the [MIT License](https://github.com/axios/axios/blob/v1.7.4/LICENSE):
81
81
 
82
82
  ```
83
83
  # Copyright (c) 2014-present Matt Zabriskie & Collaborators
@@ -588,7 +588,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
588
588
 
589
589
  ### ws
590
590
 
591
- This product includes source derived from [ws](https://github.com/websockets/ws) ([v8.14.2](https://github.com/websockets/ws/tree/v8.14.2)), distributed under the [MIT License](https://github.com/websockets/ws/blob/v8.14.2/LICENSE):
591
+ This product includes source derived from [ws](https://github.com/websockets/ws) ([v8.17.1](https://github.com/websockets/ws/tree/v8.17.1)), distributed under the [MIT License](https://github.com/websockets/ws/blob/v8.17.1/LICENSE):
592
592
 
593
593
  ```
594
594
  Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
@@ -619,7 +619,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
619
619
 
620
620
  ### @grpc/proto-loader
621
621
 
622
- This product includes source derived from [@grpc/proto-loader](https://github.com/grpc/grpc-node) ([v0.7.7](https://github.com/grpc/grpc-node/tree/v0.7.7)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/blob/v0.7.7/LICENSE):
622
+ This product includes source derived from [@grpc/proto-loader](https://github.com/grpc/grpc-node) ([v0.7.13](https://github.com/grpc/grpc-node/tree/v0.7.13)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/blob/v0.7.13/LICENSE):
623
623
 
624
624
  ```
625
625
  Apache License
@@ -62,5 +62,6 @@ module.exports = {
62
62
  UNDEFINED: 'undefined',
63
63
  SELF_FD_PATH: '/proc/self/fd/',
64
64
  NR_CSEC_FUZZ_REQUEST_ID: 'nr-csec-fuzz-request-id',
65
+ ASTERISK: "*"
65
66
 
66
67
  };
@@ -9,11 +9,15 @@ const secUtils = require("../../core/sec-utils");
9
9
  const API = require("../../../nr-security-api");
10
10
  const securityMetaData = require('../../core/security-metadata');
11
11
  const { EVENT_TYPE, EVENT_CATEGORY } = require('../../core/event-constants');
12
- const { NR_CSEC_FUZZ_REQUEST_ID } = require('../../core/constants');
12
+ const { NR_CSEC_FUZZ_REQUEST_ID, DOUBLE_DOLLAR, ATTHERATE, ASTERISK } = require('../../core/constants');
13
13
  const logger = API.getLogger();
14
14
  const semver = require('semver');
15
15
  const grpcutils = require('../../core/grpc-utils');
16
+ const routeManager = require('../../core/route-manager');
16
17
  let serverAddress;
18
+ const ExceptionReporting = require('../../../nr-security-agent/lib/core/ExceptionReporting');
19
+
20
+
17
21
 
18
22
  module.exports.initialize = function initialize(shim, grpc, moduleName) {
19
23
  logger.info('Instrumenting grpc', moduleName);
@@ -118,17 +122,39 @@ function wrapStart(shim, original) {
118
122
  }
119
123
 
120
124
 
121
-
125
+ /**
126
+ * Wrapper for addService method
127
+ * @param {*} shim
128
+ * @param {*} original
129
+ * @returns
130
+ */
122
131
  function wrapAddService(shim, original) {
123
132
  if (!shim.isFunction(original)) {
124
133
  return original
125
134
  }
126
135
  return function wrappedbindAsync() {
136
+ try {
137
+ const stakTrace = secUtils.traceElementForRoute();
138
+ const splittedStack = stakTrace[0].split(DOUBLE_DOLLAR);
139
+ let serviceValues = Object.values(arguments[0]);
140
+ for (const [key, value] of Object.entries(serviceValues)) {
141
+ const routeKey = ASTERISK + ATTHERATE + value.path;
142
+ let finalHandler = value.originalName + ATTHERATE + splittedStack[0];
143
+ routeManager.setRoute(routeKey, finalHandler);
144
+ }
145
+ } catch (error) {
146
+ }
127
147
  const result = original.apply(this, arguments);
128
148
  return result;
129
149
  }
130
150
  }
131
151
 
152
+ /**
153
+ * wrapper for loadPackageDefinition method
154
+ * @param {*} shim
155
+ * @param {*} original
156
+ * @returns
157
+ */
132
158
  function wrapLoadPackageDefinition(shim, original) {
133
159
  if (!shim.isFunction(original)) {
134
160
  return original
@@ -224,6 +250,21 @@ function wrapCallback(shim, mod, method) {
224
250
  }
225
251
  return function wrappedCallback() {
226
252
  const response = arguments[1];
253
+ try {
254
+ const request = requestManager.getRequest(shim);
255
+ if (arguments[0]) {
256
+ if (arguments[0].details && request) {
257
+ ExceptionReporting.generateExceptionReportingEvent(arguments[0].details, request);
258
+ }
259
+ //TODO GRPC response errors reporting
260
+ // if (arguments[0].code && arguments[0].code > 0 && request) {
261
+ // let responseCode = arguments[0].code
262
+ // ExceptionReporting.generate5xxReportingEvent(null, request, responseCode);
263
+ // }
264
+ }
265
+ } catch (error) {
266
+
267
+ }
227
268
  const result = fn.apply(this, arguments);
228
269
  return result;
229
270
 
@@ -254,7 +295,7 @@ function addRequestData(shim, type, path, args) {
254
295
  }
255
296
  data.type = type;
256
297
  data.protocol = 'grpc';
257
- data.isGRPC = true;
298
+ data.isGrpc = true;
258
299
  data.body = JSON.stringify(args.request);
259
300
  data.serverPort = shim.serverPort;
260
301
  const transactionId = segment.transaction.id;
@@ -31,13 +31,16 @@ const isInvalid = require('is-invalid-path');
31
31
  const path = require('path');
32
32
  const fs = require('fs');
33
33
  const semver = require('semver');
34
-
34
+ const ExceptionReporting = require('../../../nr-security-agent/lib/core/ExceptionReporting');
35
35
 
36
36
  const CSEC_SEP = ':IAST:';
37
37
  const find = '{{NR_CSEC_VALIDATOR_HOME_TMP}}';
38
38
  const CSEC_HOME_TMP_CONST = new RegExp(find, 'g');
39
39
  let CSEC_HOME = NRAgent && NRAgent.config.newrelic_home ? NRAgent.config.newrelic_home : NRAgent && NRAgent.config.logging.filepath ? path.dirname(NRAgent.config.logging.filepath) : require('path').join(process.cwd());
40
40
  const CSEC_HOME_TMP = `${CSEC_HOME}/nr-security-home/tmp/language-agent/${process.env.applicationUUID}`
41
+ let lastTransactionId = EMPTY_STRING;
42
+ let uncaughtExceptionReportFlag = false;
43
+
41
44
 
42
45
  /**
43
46
  * Entry point of http module hook
@@ -178,7 +181,7 @@ function parseFuzzheaders(requestData, transactionId) {
178
181
  if (verifiedHash) {
179
182
  logger.debug("Encrypted Data:", encryptedData);
180
183
  logger.debug("Decrypted Data:", decryptedData)
181
- logger.debug("fliesTocreate:",filesToCreate);
184
+ logger.debug("fliesTocreate:", filesToCreate);
182
185
  for (let i = 0; i < filesToCreate.length; i++) {
183
186
  let file = filesToCreate[i].trim();
184
187
  file = file.replace(CSEC_HOME_TMP_CONST, CSEC_HOME_TMP);
@@ -192,11 +195,11 @@ function parseFuzzheaders(requestData, transactionId) {
192
195
  logger.debug(parentDir + ' Already Exists');
193
196
  }
194
197
  fs.closeSync(fs.openSync(file, 'w'));
195
-
198
+
196
199
  requestData.tempFiles = [];
197
200
  requestData.tempFiles.push(file);
198
201
  requestManager.setRequest(transactionId, requestData);
199
-
202
+
200
203
  } catch (error) {
201
204
  logger.debug(error);
202
205
  }
@@ -243,6 +246,7 @@ function addRequestData(shim, request) {
243
246
  data.clientIP = requestIp.getClientIp(request);
244
247
  const transactionId = segment.transaction.id;
245
248
  const storedRequest = requestManager.getRequestFromId(transactionId);
249
+ lastTransactionId = transactionId;
246
250
  if (storedRequest && storedRequest.uri) {
247
251
  data.uri = storedRequest.uri;
248
252
  data.parameterMap = storedRequest.parameterMap;
@@ -396,8 +400,27 @@ function responseHook(resp, req, shim) {
396
400
  }
397
401
  return function wrappedEnd() {
398
402
  const response = this;
399
- responseBodyCompute(response, arguments);
400
403
  const request = requestManager.getRequest(shim);
404
+ const txInfo = lodash.get(response, Object.getOwnPropertySymbols(response).find(symbol => symbol.toString() === 'Symbol(transactionInfo)'));
405
+ try {
406
+ if (txInfo && txInfo.error && request) {
407
+ ExceptionReporting.generateExceptionReportingEvent(txInfo.error, request);
408
+ }
409
+ const responseCode = response.statusCode;
410
+ if (responseCode && parseInt(responseCode / 100) == 5 && request) {
411
+ if (txInfo && txInfo.error) {
412
+ ExceptionReporting.generate5xxReportingEvent(txInfo.error, request, responseCode);
413
+ }
414
+ else {
415
+ ExceptionReporting.generate5xxReportingEvent(null, request, responseCode);
416
+ }
417
+ }
418
+ } catch (error) {
419
+ logger.debug("Error while reporting error", error);
420
+ }
421
+
422
+ responseBodyCompute(response, arguments);
423
+
401
424
  const construct = API.checkForReflectedXSS(request, response.res.body, response.getHeaders());
402
425
  const policy = API.getPolicy();
403
426
  const dynamicScanningFlag = policy.data ? (policy.data.vulnerabilityScan?.enabled && policy.data.vulnerabilityScan.iastScan.enabled) : false;
@@ -565,4 +588,21 @@ function deleteTempFileForIAST(file) {
565
588
  } catch (error) {
566
589
  logger.debug("Unable to delete temporary file", error);
567
590
  }
568
- }
591
+ }
592
+ /**
593
+ * Collect and report the uncaught Exception
594
+ */
595
+ process.on('uncaughtException', (err, origin) => {
596
+ console.error(err);
597
+ if(uncaughtExceptionReportFlag){
598
+ process.exit(1);
599
+ }
600
+ const request = requestManager.getRequestFromId(lastTransactionId);
601
+ const exception = new ExceptionReporting.exceptionReporting(err, request);
602
+ if (exception) {
603
+ API.sendEvent(exception);
604
+ }
605
+ logger.error("An uncaughtException is detected:", err);
606
+ uncaughtExceptionReportFlag = true;
607
+ });
608
+
@@ -0,0 +1,186 @@
1
+ /*
2
+ * Copyright 2023 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: New Relic Software License v1.0
4
+ */
5
+
6
+ const LinkingMetaData = require('./LinkingMetadata');
7
+ const API = require('../../../nr-security-api');
8
+ const NRAgent = API.getNRAgent();
9
+ const { BasicInfo } = require('./event');
10
+ const applicationInfo = require('./applicationinfo').getInstance();
11
+ const exceptionsMap = new Map();
12
+ const logs = require('./logging');
13
+ const logger = logs.getLogger();
14
+ const exceptionReportingInterval = 30000;
15
+ const http = require('http');
16
+ const shaUtil = require('./sha-size-util');
17
+
18
+ const NR_LIB = '/newrelic/lib';
19
+
20
+ const GRPCStatusMap = {
21
+ 0: 'OK',
22
+ 1: 'CANCELLED',
23
+ 2: 'UNKNOWN',
24
+ 3: 'INVALID_ARGUMENT',
25
+ 4: 'DEADLINE_EXCEEDED',
26
+ 5: 'NOT_FOUND',
27
+ 6: 'ALREADY_EXISTS',
28
+ 7: 'PERMISSION_DENIED',
29
+ 8: 'RESOURCE_EXHAUSTED',
30
+ 9: 'FAILED_PRECONDITION',
31
+ 10: 'ABORTED',
32
+ 11: 'OUT_OF_RANGE',
33
+ 12: 'UNIMPLEMENTED',
34
+ 13: 'INTERNAL',
35
+ 14: 'UNAVAILABLE',
36
+ 15: 'DATA_LOSS',
37
+ 16: 'UNAUTHENTICATED',
38
+ }
39
+
40
+ /**
41
+ * constructor function to create createException object
42
+ * @param {*} message
43
+ * @param {*} cause
44
+ * @param {*} stacktrace
45
+ */
46
+ function createException(exception) {
47
+ let exceptionObject = {};
48
+ try {
49
+ exceptionObject.message = exception.message ? exception.message : '';
50
+ exceptionObject.cause = null;
51
+ exceptionObject.type = exception.name ? exception.name : 'Error';
52
+ if (exception.stack) {
53
+ let stacktraceInList = exception.stack.split('\n');
54
+ stacktraceInList = stacktraceInList.slice(1);
55
+ let fileteredStackTrace = [];
56
+ for (const str of stacktraceInList) {
57
+ if (!str.includes(NR_LIB) && str.length > 0) {
58
+ fileteredStackTrace.push(str.trim());
59
+ }
60
+ }
61
+ exceptionObject.stackTrace = fileteredStackTrace;
62
+ }
63
+
64
+ } catch (error) {
65
+ }
66
+
67
+ return exceptionObject;
68
+ }
69
+
70
+ /**
71
+ * constructor function to create exceptionReporting object;
72
+ * @param {*} timestamp
73
+ * @param {*} level
74
+ * @param {*} message
75
+ * @param {*} caller
76
+ * @param {*} exception
77
+ * @param {*} linkingMetadata
78
+ */
79
+ function exceptionReporting(exception, request, responseCode) {
80
+ try {
81
+ BasicInfo.call(this);
82
+ this.jsonVersion = applicationInfo.jsonVersion;
83
+ this.pid = applicationInfo.pid ? applicationInfo.pid : null;
84
+ this.jsonName = 'application-runtime-error';
85
+ this.applicationUUID = applicationInfo.applicationUUID;
86
+ this.policyVersion = applicationInfo.policyVersion;
87
+ this.collectorVersion = applicationInfo.collectorVersion ? applicationInfo.collectorVersion : null;
88
+ this.buildNumber = applicationInfo.buildNumber ? applicationInfo.buildNumber : null;
89
+ this.timestamp = Date.now();
90
+ this.httpRequest = request;
91
+ this.httpRequest.route = request.uri ? request.uri : request.url;
92
+ this.exception = null;
93
+ this.counter = 1;
94
+ this.responseCode = responseCode ? responseCode : null
95
+
96
+ if (exception) {
97
+ this.exception = createException(exception);
98
+ this.category = exception.name;
99
+ this.traceId = shaUtil.getSHA256ForData(this.exception.stackTrace.join('|') + this.category + this.httpRequest.route + this.httpRequest.method);
100
+ }
101
+
102
+ if (responseCode) {
103
+ if (responseCode >= 500) {
104
+ this.category = http.STATUS_CODES[responseCode];
105
+ this.traceId = shaUtil.getSHA256ForData(this.category + this.httpRequest.route + this.httpRequest.method);
106
+ }
107
+ else {
108
+ this.category = GRPCStatusMap[responseCode];
109
+ this.traceId = shaUtil.getSHA256ForData(this.category + this.httpRequest.route + this.httpRequest.method);
110
+ }
111
+ }
112
+
113
+ if (NRAgent) {
114
+ this.linkingMetadata = LinkingMetaData.getLinkingMetadata();
115
+ }
116
+ } catch (error) {
117
+ logger.debug("Error while generating exceptionReporing", error);
118
+ }
119
+
120
+ }
121
+
122
+ function generateExceptionReportingEvent(error, request) {
123
+ try {
124
+ const exception = new exceptionReporting(error, request);
125
+ if(!exception.traceId){
126
+ return;
127
+ }
128
+ let key = exception.exception.type + exception.exception.stackTrace[0];
129
+ if (!exceptionsMap.has(key)) {
130
+ exceptionsMap.set(key, exception);
131
+ }
132
+ else {
133
+ let previousCounter = exceptionsMap.get(key).counter;
134
+ exception.counter = previousCounter ? previousCounter + 1: exception.counter + 1;
135
+ exceptionsMap.set(key, exception);
136
+ }
137
+ } catch (err) {
138
+ logger.debug("Error while generating application-runtime-error:", err);
139
+ }
140
+ }
141
+
142
+ function generate5xxReportingEvent(error, request, responseCode) {
143
+ try {
144
+ const exception = new exceptionReporting(error, request, responseCode);
145
+ if(!exception.traceId){
146
+ return;
147
+ }
148
+ let key = (request.uri ? request.uri : request.url) + responseCode;
149
+ if (!exceptionsMap.has(key)) {
150
+ exceptionsMap.set(key, exception);
151
+ }
152
+ else {
153
+ let previousCounter = exceptionsMap.get(key).counter;
154
+ exception.counter = previousCounter ? previousCounter + 1: exception.counter + 1;
155
+ exceptionsMap.set(key, exception);
156
+ }
157
+ } catch (err) {
158
+ logger.debug("Error while generating 5xx application-runtime-error:", err);
159
+ }
160
+ }
161
+
162
+
163
+
164
+ function startExceptionSendingSchedular() {
165
+ try {
166
+ setInterval(() => {
167
+ const values = Array.from(exceptionsMap.values());
168
+ for (let i = 0; i < values.length; i++) {
169
+ API.sendEvent(values[i]);
170
+ }
171
+ exceptionsMap.clear();
172
+ }, exceptionReportingInterval);
173
+ } catch (error) {
174
+ logger.info("Error in exception reporting schedular");
175
+ }
176
+ logger.info("Exception reporting schedular started");
177
+ }
178
+
179
+
180
+ module.exports = {
181
+ exceptionReporting,
182
+ createException,
183
+ generateExceptionReportingEvent,
184
+ generate5xxReportingEvent,
185
+ startExceptionSendingSchedular
186
+ };
@@ -20,10 +20,11 @@ function logMessageException(exception) {
20
20
  try {
21
21
  exceptionObject.message = exception.message ? exception.message : '';
22
22
  exceptionObject.cause = null;
23
+ exceptionObject.type = exception.name;
23
24
  if (exception.stack) {
24
25
  let stacktraceInList = exception.stack.split('\n');
25
26
  stacktraceInList = stacktraceInList.slice(1);
26
- exceptionObject.stacktrace = stacktraceInList;
27
+ exceptionObject.stackTrace = stacktraceInList;
27
28
  }
28
29
 
29
30
  } catch (error) {
@@ -51,6 +52,8 @@ function logMessage(level, message, caller, exception) {
51
52
  this.applicationUUID = applicationInfo.applicationUUID;
52
53
  this.groupName = applicationInfo.groupName;
53
54
  this.policyVersion = applicationInfo.policyVersion;
55
+ this.collectorVersion = applicationInfo.collectorVersion ? applicationInfo.collectorVersion : null;
56
+ this.buildNumber = applicationInfo.buildNumber ? applicationInfo.buildNumber : null;
54
57
  this.timestamp = Date.now();
55
58
  this.level = level ? level : 'SEVERE';
56
59
  this.message = message;
@@ -12,6 +12,12 @@ const axios = require('axios').default;
12
12
  */
13
13
  function fireRequest (config) {
14
14
  axios.defaults.timeout = 5000;
15
+ axios.defaults.headers.common['user-agent'] = null;
16
+ axios.defaults.headers.common['accept-encoding'] = null;
17
+ axios.defaults.headers.common['connection'] = null;
18
+ axios.defaults.headers.common['sec-ch-ua-platform'] = null;
19
+ axios.defaults.headers.common['accept'] = null;
20
+ axios.defaults.headers.common['content-type'] = null;
15
21
  return axios(config);
16
22
  }
17
23
 
@@ -34,6 +34,7 @@ function BasicInfo() {
34
34
  this.policyVersion = (!lodash.isEmpty(policyManager.getInstance().data)) ? policyManager.getInstance().data.version : 'DEFAULT';
35
35
 
36
36
  if (NRAgent && NRAgent.config) {
37
+ this.accountId = NRAgent.config.account_id;
37
38
  this.entityGuid = NRAgent.config.entity_guid;
38
39
  this.linkingMetadata = LinkingMetaData.getLinkingMetadata();
39
40
  this.linkingMetadata.agentRunId = NRAgent.config.run_id;
@@ -43,7 +43,6 @@ module.exports.getInstance = function () {
43
43
  function HC(info) {
44
44
  BasicInfo.call(this);
45
45
 
46
-
47
46
  this.applicationUUID = info.applicationUUID;
48
47
  this.eventType = 'sec_health_check_lc';
49
48
  if (NRAgent && NRAgent.config) {
@@ -75,6 +75,7 @@ const initialize = function () {
75
75
  let data = require('./apiEndpoints').APIEndPoint(apiEndpoints);
76
76
  logger.debug("All API end points of the application is:",JSON.stringify(data));
77
77
  getDispatcherAndSendEvent(data);
78
+ require('../core/ExceptionReporting').startExceptionSendingSchedular();
78
79
  } catch (error) {
79
80
  logger.debug("Error while processing API endpoints",error);
80
81
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@newrelic/security-agent",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "New Relic Security Agent for Node.js",
5
5
  "main": "index.js",
6
- "jsonVersion": "1.2.0",
6
+ "jsonVersion": "1.2.3",
7
7
  "contributors": [
8
8
  {
9
9
  "name": "Sumit Suthar",
@@ -35,7 +35,7 @@
35
35
  "url": "https://github.com/newrelic/csec-node-agent.git"
36
36
  },
37
37
  "dependencies": {
38
- "axios": "^1.6.8",
38
+ "axios": "^1.7.4",
39
39
  "check-disk-space": "^3.4.0",
40
40
  "content-type": "^1.0.5",
41
41
  "fast-safe-stringify": "^2.1.1",
@@ -55,7 +55,7 @@
55
55
  "unescape": "^1.0.1",
56
56
  "unescape-js": "^1.1.4",
57
57
  "uuid": "^9.0.1",
58
- "ws": "^8.14.2"
58
+ "ws": "^8.17.1"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@grpc/proto-loader": "^0.7.10",