@newrelic/security-agent 1.4.0 → 2.0.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 (25) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/THIRD_PARTY_NOTICES.md +19 -2
  3. package/lib/instrumentation-security/core/constants.js +8 -1
  4. package/lib/instrumentation-security/core/event-constants.js +5 -3
  5. package/lib/instrumentation-security/core/request-manager.js +3 -1
  6. package/lib/instrumentation-security/core/sec-utils.js +1 -0
  7. package/lib/instrumentation-security/hooks/fs/nr-fs.js +6 -2
  8. package/lib/instrumentation-security/hooks/http/nr-http.js +123 -5
  9. package/lib/nr-security-agent/lib/core/Auth-headers.js +1 -0
  10. package/lib/nr-security-agent/lib/core/ExceptionReporting.js +7 -3
  11. package/lib/nr-security-agent/lib/core/Policy.js +0 -12
  12. package/lib/nr-security-agent/lib/core/agent.js +2 -1
  13. package/lib/nr-security-agent/lib/core/applicationinfo.js +2 -2
  14. package/lib/nr-security-agent/lib/core/commonUtils.js +11 -43
  15. package/lib/nr-security-agent/lib/core/connections/websocket/proxy-util.js +61 -0
  16. package/lib/nr-security-agent/lib/core/connections/websocket/response/IASTUtils.js +6 -4
  17. package/lib/nr-security-agent/lib/core/connections/websocket/response/fuzz-request-handler.js +0 -2
  18. package/lib/nr-security-agent/lib/core/connections/websocket/websocket.js +15 -12
  19. package/lib/nr-security-agent/lib/core/event.js +2 -2
  20. package/lib/nr-security-agent/lib/core/health-check.js +2 -1
  21. package/lib/nr-security-agent/lib/core/sec-agent-constants.js +2 -3
  22. package/lib/nr-security-agent/lib/core/sec-util.js +35 -3
  23. package/lib/nr-security-agent/lib/core/websocket-client.js +4 -4
  24. package/package.json +10 -5
  25. package/lib/nr-security-agent/lib/core/statusUtils.js +0 -165
package/CHANGELOG.md CHANGED
@@ -1,3 +1,32 @@
1
+ ### v2.0.0 (2024-09-20)
2
+ ### ⚠ BREAKING CHANGES
3
+ * Dropped support for Node.js v16
4
+ * Dropped functionality to generate snapshot file
5
+ #### Features
6
+ * Support to honour proxy settings via config
7
+ * Support for secure cookie security event generation
8
+ * Report error to Error Inbox upon connection failure to Security Engine
9
+ * Support to detect application and server path
10
+ * Functionality to truncate Incoming HTTP request upto default limit
11
+ * Dropped support for Node.js v16
12
+ * Dropped functionality to generate snapshot file
13
+ #### Bug fixes
14
+ * Handling for empty data in IAST fuzzing header
15
+ * Added identifiers in events
16
+ * Fix for file integrity security event generation
17
+ * Fix for missing identifiers in iast-data-request JSON
18
+
19
+ ### v1.5.0 (2024-08-14)
20
+ #### Features
21
+ * Support for Node.js v22.x
22
+ #### Bug fixes
23
+ * Fix for traceId in error reporting
24
+ #### Miscellaneous chores
25
+ * (deps): bumped axios from 1.6.8 to 1.7.4
26
+ * (deps-dev): bumped ws from 7.5.9 to 8.18.0
27
+ #### Continuous integration
28
+ * Added Node.js v22.x to unit tests
29
+
1
30
  ### v1.4.0 (2024-06-24)
2
31
  #### Features
3
32
  * Added support to report application's errors while IAST scanning
@@ -21,6 +21,7 @@ code, the source code can be found at [https://github.com/newrelic/csec-node-age
21
21
  * [find-package-json](#find-package-json)
22
22
  * [hash.js](#hashjs)
23
23
  * [html-entities](#html-entities)
24
+ * [https-proxy-agent](#https-proxy-agent)
24
25
  * [is-invalid-path](#is-invalid-path)
25
26
  * [js-yaml](#js-yaml)
26
27
  * [jsonschema](#jsonschema)
@@ -77,7 +78,7 @@ code, the source code can be found at [https://github.com/newrelic/csec-node-age
77
78
 
78
79
  ### axios
79
80
 
80
- This product includes source derived from [axios](https://github.com/axios/axios) ([v1.7.2](https://github.com/axios/axios/tree/v1.7.2)), distributed under the [MIT License](https://github.com/axios/axios/blob/v1.7.2/LICENSE):
81
+ 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
82
 
82
83
  ```
83
84
  # Copyright (c) 2014-present Matt Zabriskie & Collaborators
@@ -239,6 +240,22 @@ THE SOFTWARE.
239
240
 
240
241
  ```
241
242
 
243
+ ### https-proxy-agent
244
+
245
+ This product includes source derived from [https-proxy-agent](https://github.com/TooTallNate/node-https-proxy-agent) ([v5.0.1](https://github.com/TooTallNate/node-https-proxy-agent/tree/v5.0.1)), distributed under the [MIT License](https://github.com/TooTallNate/node-https-proxy-agent/blob/v5.0.1/README.md):
246
+
247
+ ```
248
+ MIT License
249
+
250
+ Copyright (c) <year> <copyright holders>
251
+
252
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
253
+
254
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
255
+
256
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
257
+ ```
258
+
242
259
  ### is-invalid-path
243
260
 
244
261
  This product includes source derived from [is-invalid-path](https://github.com/jonschlinkert/is-invalid-path) ([v1.0.2](https://github.com/jonschlinkert/is-invalid-path/tree/v1.0.2)), distributed under the [MIT License](https://github.com/jonschlinkert/is-invalid-path/blob/v1.0.2/LICENSE):
@@ -588,7 +605,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
588
605
 
589
606
  ### ws
590
607
 
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):
608
+ This product includes source derived from [ws](https://github.com/websockets/ws) ([v8.18.0](https://github.com/websockets/ws/tree/v8.18.0)), distributed under the [MIT License](https://github.com/websockets/ws/blob/v8.18.0/LICENSE):
592
609
 
593
610
  ```
594
611
  Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
@@ -62,6 +62,13 @@ 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
+ ASTERISK: "*",
66
+ COMMA_SPACE: ", ",
67
+ SEMI_COLON: ';',
68
+ EQUAL: '=',
69
+ SECURE: 'secure',
70
+ HTTPONLY: 'httponly',
71
+ SAMESITE: 'samesite',
72
+ STRICT: 'strict'
66
73
 
67
74
  };
@@ -20,9 +20,10 @@ const EVENT_TYPE = {
20
20
  UNVALIDATED_REDIRECT: 'UNVALIDATED_REDIRECT',
21
21
  REFLECTED_XSS: 'REFLECTED_XSS',
22
22
  XPATH: 'XPATH',
23
- LDAP: 'LDAP'
23
+ LDAP: 'LDAP',
24
+ SECURE_COOKIE: 'SECURE_COOKIE'
24
25
  }
25
- const EVENT_CATEGORY = {
26
+ const EVENT_CATEGORY = {
26
27
  MYSQL: 'MYSQL',
27
28
  POSTGRES: 'POSTGRES',
28
29
  ORACLE: 'ORACLE',
@@ -40,7 +41,8 @@ const EVENT_CATEGORY = {
40
41
  UNVALIDATED_REDIRECT: 'UNVALIDATED_REDIRECT',
41
42
  REFLECTED_XSS: 'REFLECTED_XSS',
42
43
  XPATH: 'XPATH',
43
- LDAP: 'LDAP'
44
+ LDAP: 'LDAP',
45
+ SECURE_COOKIE: 'SECURE_COOKIE'
44
46
  }
45
47
 
46
48
  module.exports = {
@@ -48,13 +48,14 @@ function getRequest(shim) {
48
48
  * @param {*} shim
49
49
  * @param {*} data
50
50
  */
51
- function updateRequestBody(shim, data) {
51
+ function updateRequestBody(shim, data, isTruncated) {
52
52
  const segment = shim.getActiveSegment();
53
53
  if (segment) {
54
54
  const transactionId = segment.transaction.id;
55
55
  const requestData = getRequestFromId(transactionId);
56
56
  if (requestData) {
57
57
  requestData.body = data;
58
+ requestData.dataTruncated = isTruncated;
58
59
  setRequest(transactionId, requestData);
59
60
  }
60
61
  }
@@ -65,6 +66,7 @@ function updateRequestBody(shim, data) {
65
66
  const requestData = getRequestFromId(traceId);
66
67
  if (requestData) {
67
68
  requestData.body = data;
69
+ requestData.dataTruncated = isTruncated;
68
70
  setRequest(traceId, requestData)
69
71
  }
70
72
  }
@@ -194,6 +194,7 @@ function addRequestData(shim, request) {
194
194
  });
195
195
  }
196
196
  data.clientIP = requestIp.getClientIp(request);
197
+ data.dataTruncated = false;
197
198
  const transactionId = segment.transaction.id;
198
199
  const storedRequest = requestManager.getRequestFromId(transactionId);
199
200
  if (storedRequest && storedRequest.uri) {
@@ -25,6 +25,7 @@ const RENAME = 'rename';
25
25
  const FSReqCallback = 'FSReqCallback';
26
26
  const FSReqWrap = 'FSReqWrap';
27
27
  const OBJECT = 'object';
28
+ const semver = require('semver');
28
29
 
29
30
  const functionsProbableToFA = [
30
31
  "fstat",
@@ -37,7 +38,7 @@ const functionsProbableToFA = [
37
38
  "realpath",
38
39
  ];
39
40
 
40
- const functionProbableToFI = [
41
+ let functionProbableToFI = [
41
42
  "rename",
42
43
  "ftruncate",
43
44
  "rmdir",
@@ -214,6 +215,9 @@ function probableToFAHooks(shim, mod, moduleName) {
214
215
  * @param {*} moduleName
215
216
  */
216
217
  function probableToFIHooks(shim, mod, moduleName) {
218
+ if (semver.satisfies(process.version, '>19.0.0')) {
219
+ functionProbableToFI.push('writeFileSync');
220
+ }
217
221
  functionProbableToFI.forEach(function (fun) {
218
222
  shim.wrap(mod, fun, function makeFIWrapper(shim, fn) {
219
223
  logger.debug(`Instrumenting ${moduleName}.${fun}`);
@@ -228,7 +232,7 @@ function probableToFIHooks(shim, mod, moduleName) {
228
232
  parameters[0] = path.resolve(parameters[0]);
229
233
  parameters[1] = path.resolve(parameters[1]);
230
234
  } catch (error) {
231
-
235
+
232
236
  }
233
237
  let absoluteParameters = [parameters[0]];
234
238
  if (fun === COPY_FILE || fun === RENAME) {
@@ -5,7 +5,7 @@
5
5
 
6
6
  module.exports = initialize
7
7
  const requestManager = require('../../core/request-manager');
8
- const { NR_CSEC_FUZZ_REQUEST_ID, QUESTION_MARK, EMPTY_STRING, UTF8, CONTENT_TYPE, TEXT_HTML, APPLICATION_JSON, APPLICATION_XML, APPLICATION_XHTML, TEXT_PLAIN, APPLICATION_X_FORM_URLENCODED, MULTIPART_FORM_DATA, COMMA } = require('../../core/constants');
8
+ const { NR_CSEC_FUZZ_REQUEST_ID, QUESTION_MARK, EMPTY_STRING, UTF8, CONTENT_TYPE, TEXT_HTML, APPLICATION_JSON, APPLICATION_XML, APPLICATION_XHTML, TEXT_PLAIN, APPLICATION_X_FORM_URLENCODED, MULTIPART_FORM_DATA, COMMA, SEMI_COLON, EQUAL, SECURE, HTTPONLY, SAMESITE, STRICT } = require('../../core/constants');
9
9
  const ARRAY_TYPE = 'Array';
10
10
  const STRING_TYPE = 'string';
11
11
  const BUFFER_TYPE = 'Buffer';
@@ -40,6 +40,7 @@ let CSEC_HOME = NRAgent && NRAgent.config.newrelic_home ? NRAgent.config.newreli
40
40
  const CSEC_HOME_TMP = `${CSEC_HOME}/nr-security-home/tmp/language-agent/${process.env.applicationUUID}`
41
41
  let lastTransactionId = EMPTY_STRING;
42
42
  let uncaughtExceptionReportFlag = false;
43
+ const requestBodyLimit = 500;
43
44
 
44
45
 
45
46
  /**
@@ -172,8 +173,12 @@ function parseFuzzheaders(requestData, transactionId) {
172
173
  const additionalData = fuzzheaders[NR_CSEC_FUZZ_REQUEST_ID].split(CSEC_SEP);
173
174
  logger.debug('AdditionalData:', additionalData);
174
175
  if (additionalData.length >= 8) {
175
- let encryptedData = additionalData[6];
176
- let hashVerifier = additionalData[7];
176
+ let encryptedData = additionalData[6].trim();
177
+ let hashVerifier = additionalData[7].trim();
178
+
179
+ if (lodash.isEmpty(encryptedData) || lodash.isEmpty(hashVerifier)) {
180
+ return;
181
+ }
177
182
  let decryptedData = secUtils.decryptData(encryptedData);
178
183
  let verifiedHash = secUtils.hashVerifier(decryptedData, hashVerifier);
179
184
  logger.debug("verifiedHash:", verifiedHash);
@@ -244,6 +249,7 @@ function addRequestData(shim, request) {
244
249
  });
245
250
  }
246
251
  data.clientIP = requestIp.getClientIp(request);
252
+ data.dataTruncated = false;
247
253
  const transactionId = segment.transaction.id;
248
254
  const storedRequest = requestManager.getRequestFromId(transactionId);
249
255
  lastTransactionId = transactionId;
@@ -363,7 +369,21 @@ function onDataHook(shim, mod) {
363
369
  const requestData = requestManager.getRequestFromId(transactionId);
364
370
  if (requestData) {
365
371
  data = requestData.body ? requestData.body.concat(chunk.toString()) : chunk.toString();
366
- requestManager.updateRequestBody(shim, data);
372
+ requestManager.updateRequestBody(shim, data, false);
373
+ try {
374
+ const contentLength = Buffer.byteLength(data, 'utf8');
375
+ let bodyLimit = requestBodyLimit;
376
+ if (!isNaN(bodyLimit)) {
377
+ bodyLimit = bodyLimit * 1000;
378
+ if (contentLength && contentLength > bodyLimit) {
379
+ data = truncateStringToBytes(data, bodyLimit);
380
+ requestManager.updateRequestBody(shim, data, true);
381
+ }
382
+ }
383
+
384
+ } catch (error) {
385
+ logger.error("Error while truncating request body", error);
386
+ }
367
387
  }
368
388
 
369
389
  }
@@ -418,6 +438,11 @@ function responseHook(resp, req, shim) {
418
438
  } catch (error) {
419
439
  logger.debug("Error while reporting error", error);
420
440
  }
441
+ try {
442
+ secureCookieCheck(response, shim);
443
+ } catch (error) {
444
+ logger.debug("Error while generating secure cookie event");
445
+ }
421
446
 
422
447
  responseBodyCompute(response, arguments);
423
448
 
@@ -594,7 +619,7 @@ function deleteTempFileForIAST(file) {
594
619
  */
595
620
  process.on('uncaughtException', (err, origin) => {
596
621
  console.error(err);
597
- if(uncaughtExceptionReportFlag){
622
+ if (uncaughtExceptionReportFlag) {
598
623
  process.exit(1);
599
624
  }
600
625
  const request = requestManager.getRequestFromId(lastTransactionId);
@@ -606,3 +631,96 @@ process.on('uncaughtException', (err, origin) => {
606
631
  uncaughtExceptionReportFlag = true;
607
632
  });
608
633
 
634
+ /**
635
+ * utility to parse response headers for cookie check
636
+ * @param {*} headersString
637
+ * @returns
638
+ */
639
+ function responseRawHeaderParsing(headersString) {
640
+ let setCookieValue;
641
+ try {
642
+ const headersArray = headersString.split('\r\n');
643
+ for (const header of headersArray) {
644
+ if (header.toLowerCase().startsWith('set-cookie:')) {
645
+ setCookieValue = header.trim().replace('set-cookie:', '');
646
+ break;
647
+ }
648
+ }
649
+ } catch (error) {
650
+
651
+ }
652
+ return setCookieValue;
653
+ }
654
+
655
+ /**
656
+ * Utility to check insecure cookie settings
657
+ * @param {*} response
658
+ * @param {*} shim
659
+ */
660
+ function secureCookieCheck(response, shim) {
661
+ let cookieHeader = response.getHeader('set-cookie') ? response.getHeader('set-cookie') : responseRawHeaderParsing(response._header);
662
+ let cookieHeaderList = [];
663
+ if (!cookieHeader) {
664
+ return;
665
+ }
666
+ if (typeof cookieHeader === 'string') {
667
+ cookieHeaderList.push(cookieHeader)
668
+ }
669
+ else {
670
+ cookieHeaderList = cookieHeaderList.concat(cookieHeader);
671
+ }
672
+ let csec_request = requestManager.getRequest(shim);
673
+ try {
674
+ let parameters = [];
675
+ for (const cookieString of cookieHeaderList) {
676
+ if (cookieString) {
677
+ const keyValuePairs = cookieString.split(SEMI_COLON);
678
+ let params = {
679
+ "isHttpOnly": false,
680
+ "isSecure": false,
681
+ "isSameSiteStrict": false,
682
+ };
683
+ for (let i = 0; i < keyValuePairs.length; i++) {
684
+ const pair = keyValuePairs[i];
685
+ const [key, value] = pair.trim().split(EQUAL);
686
+ if (i == 0) {
687
+ params.name = key;
688
+ params.value = value;
689
+ }
690
+
691
+ if (key.toLowerCase() === SECURE) {
692
+ params.isSecure = true;
693
+ }
694
+ else if (key.toLowerCase() === HTTPONLY) {
695
+ params.isHttpOnly = true;
696
+ }
697
+ else if (key.toLowerCase() === SAMESITE && value.toLowerCase() === STRICT) {
698
+ params.isSameSiteStrict = true;
699
+ }
700
+ }
701
+ parameters.push(params);
702
+ }
703
+ }
704
+ const traceObject = secUtils.getTraceObject(shim);
705
+ const secMetadata = securityMetaData.getSecurityMetaData(csec_request, parameters, traceObject, secUtils.getExecutionId(), EVENT_TYPE.SECURE_COOKIE, EVENT_CATEGORY.SECURE_COOKIE)
706
+ const secEvent = API.generateSecEvent(secMetadata);
707
+ API.sendEvent(secEvent);
708
+ } catch (error) {
709
+ logger.debug("Error while generating secure cookie event:", error);
710
+ }
711
+
712
+ }
713
+
714
+ function truncateStringToBytes(inputString, maxBytes) {
715
+ if (!inputString || maxBytes <= 0) {
716
+ return EMPTY_STRING; // Return an empty string if input is invalid
717
+ }
718
+ const buffer = Buffer.from(inputString, UTF8);
719
+ if (buffer.length <= maxBytes) {
720
+ return inputString;
721
+ }
722
+ const truncatedBuffer = buffer.slice(0, maxBytes);
723
+ const truncatedString = truncatedBuffer.toString(UTF8);
724
+ return truncatedString;
725
+ }
726
+
@@ -43,6 +43,7 @@ function AuthHeaders() {
43
43
  commonUtils.addLogEventtoBuffer(logMessage);
44
44
  }
45
45
 
46
+
46
47
  return authHeaders;
47
48
  }
48
49
  AuthHeaders.prototype.constructor = AuthHeaders;
@@ -98,9 +98,7 @@ function exceptionReporting(exception, request, responseCode) {
98
98
  this.category = exception.name;
99
99
  this.traceId = shaUtil.getSHA256ForData(this.exception.stackTrace.join('|') + this.category + this.httpRequest.route + this.httpRequest.method);
100
100
  }
101
- else {
102
- this.traceId = shaUtil.getSHA256ForData(this.category + this.httpRequest.route + this.httpRequest.method);
103
- }
101
+
104
102
  if (responseCode) {
105
103
  if (responseCode >= 500) {
106
104
  this.category = http.STATUS_CODES[responseCode];
@@ -124,6 +122,9 @@ function exceptionReporting(exception, request, responseCode) {
124
122
  function generateExceptionReportingEvent(error, request) {
125
123
  try {
126
124
  const exception = new exceptionReporting(error, request);
125
+ if(!exception.traceId){
126
+ return;
127
+ }
127
128
  let key = exception.exception.type + exception.exception.stackTrace[0];
128
129
  if (!exceptionsMap.has(key)) {
129
130
  exceptionsMap.set(key, exception);
@@ -141,6 +142,9 @@ function generateExceptionReportingEvent(error, request) {
141
142
  function generate5xxReportingEvent(error, request, responseCode) {
142
143
  try {
143
144
  const exception = new exceptionReporting(error, request, responseCode);
145
+ if(!exception.traceId){
146
+ return;
147
+ }
144
148
  let key = (request.uri ? request.uri : request.url) + responseCode;
145
149
  if (!exceptionsMap.has(key)) {
146
150
  exceptionsMap.set(key, exception);
@@ -17,7 +17,6 @@ const logger = logs.getLogger();
17
17
  const initLogger = logs.getInitLogger();
18
18
  const jsonValidator = require('./jsonValidator');
19
19
  const LOG_MESSAGES = require('./sec-agent-constants').LOG_MESSAGES;
20
- const statusUtils = require('./statusUtils');
21
20
 
22
21
  function Policy() {
23
22
  this.data = {};
@@ -72,7 +71,6 @@ function setDefaultPolicy() {
72
71
  logger.info(LOG_MESSAGES.DEFAULT_POL_SET, JSON.stringify(policy.data));
73
72
  } catch (e) {
74
73
  logger.error(LOG_MESSAGES.UNABLE_SET_DEFAULT_POL, e);
75
- statusUtils.addErrortoBuffer(e);
76
74
  };
77
75
  }
78
76
 
@@ -90,16 +88,6 @@ function appInfoVersionUpdate(policy, data) {
90
88
  }
91
89
  }
92
90
 
93
- /**
94
- * Function to send updated policy to validator
95
- * @param {*} data
96
- */
97
- function sendUpdatedPolicy(data) {
98
- const wsClient = Agent.getAgent().client;
99
- let updatedPolicy = Object.assign({}, data);
100
- updatedPolicy.jsonName = 'lc-policy';
101
- wsClient.dispatcher(updatedPolicy);
102
- }
103
91
  module.exports = {
104
92
  getInstance,
105
93
  setPolicyData,
@@ -130,7 +130,8 @@ Agent.setNRAgent = function setNRAgent(agent) {
130
130
  }
131
131
  const CSECAgent = Agent.getAgent();
132
132
  if (CSECAgent) {
133
- CSECAgent.applicationInfo.entityGuid = NRAgent.config.entity_guid;
133
+ CSECAgent.applicationInfo.appEntityGuid = NRAgent.config.entity_guid;
134
+ CSECAgent.applicationInfo.appAccountId = NRAgent.config.account_id;
134
135
  CSECAgent.applicationInfo.linkingMetadata = LinkingMetaData.getLinkingMetadata();
135
136
  CSECAgent.applicationInfo.linkingMetadata.agentRunId = NRAgent.config.run_id;
136
137
  CSECAgent.setApplicationInfo(CSECAgent.applicationInfo);
@@ -90,10 +90,10 @@ function ApplicationInfo () {
90
90
  const k8sHost = isK8S();
91
91
  const ECSEnv = isECSEnv();
92
92
  const isLambda = IS_LAMBDA_ENV;
93
-
94
93
  this.jsonName = APPLICATION_INFO_KEYWORD;
95
94
  if (NRAgent && NRAgent.config) {
96
- this.entityGuid = NRAgent.config.entity_guid;
95
+ this.appEntityGuid = NRAgent.config.entity_guid;
96
+ this.appAccountId = NRAgent.config.account_id;
97
97
  }
98
98
  this.eventType = 'sec_appinfo';
99
99
  this.startTime = Date.now();
@@ -133,7 +133,6 @@ function getOS() {
133
133
  function createDirectories() {
134
134
  createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home`);
135
135
  createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}logs`);
136
- createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}logs${SLASH}snapshots`);
137
136
  createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}tmp`);
138
137
  createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}tmp${SLASH}language-agent`);
139
138
  createPathIfNotExist(`${CSEC_HOME}${SLASH}nr-security-home${SLASH}tmp${SLASH}language-agent${SLASH}${getUUID()}`);
@@ -159,14 +158,11 @@ async function initialSetup() {
159
158
  */
160
159
  function isLogAccessible(logFileName) {
161
160
  return new Promise((resolve) => {
162
- if (logFileName === njsAgentConstants.STATUS_LOG_FILE && require('./statusUtils').getBufferedHC().length === 0) {
163
- resolve('OK');
164
- }
165
161
  fs.access(logFileName, fs.constants.F_OK | fs.constants.W_OK, (err) => {
166
162
  if (err) {
167
163
  resolve('Error');
168
164
  }
169
- else if ((!logs.isLoggerOk || logger.level.levelStr == 'OFF') && logFileName != njsAgentConstants.STATUS_LOG_FILE) {
165
+ else if ((!logs.isLoggerOk || logger.level.levelStr == 'OFF')) {
170
166
  resolve('Error');
171
167
  }
172
168
  else {
@@ -256,42 +252,6 @@ function isAgentActiveState() {
256
252
  function iastRestClientStatus() {
257
253
  return 'OK';
258
254
  }
259
- /**
260
- * Utiltity to remove older snapshots
261
- */
262
- function removeOlderSnapshots() {
263
- try {
264
- const basePath = njsAgentConstants.LOG_DIR + 'snapshots' + SLASH;
265
- const directoryContent = fs.readdirSync(basePath);
266
- const files = directoryContent.filter((filename) => {
267
- return fs.statSync(`${basePath}${SLASH}${filename}`).isFile();
268
- });
269
- const sorted = files.sort((a, b) => {
270
- const firstStat = fs.statSync(`${basePath}${SLASH}${a}`);
271
- const secondStat = fs.statSync(`${basePath}${SLASH}${b}`);
272
- return new Date(secondStat.mtime).getTime() - new Date(firstStat.mtime).getTime();
273
- });
274
- for (let i = 99; i < sorted.length; i++) {
275
- const fileToDelete = basePath + sorted[i];
276
- try {
277
- fs.unlink(fileToDelete, (err) => {
278
- if (err) {
279
- logger.error(err);
280
- } else {
281
- logger.debug('Snapshot deleted:', fileToDelete);
282
- }
283
- });
284
- } catch (error) {
285
- logger.error(error);
286
- }
287
- }
288
- } catch (error) {
289
- logger.debug(error);
290
- const LogMessage = require('./LogMessage');
291
- const logMessage = new LogMessage.logMessage("DEBUG", 'Error in processing snapshot files', __filename, error);
292
- addLogEventtoBuffer(logMessage);
293
- }
294
- }
295
255
  /**
296
256
  * Utilty to remove older log files.
297
257
  */
@@ -459,6 +419,14 @@ function getFramework(){
459
419
  return framework;
460
420
  }
461
421
 
422
+ /**
423
+ * Utility to send error to error inbox of APM
424
+ * @param {*} error
425
+ */
426
+ function sendErrorToErrorInbox(error){
427
+ API.newrelic.noticeError(error);
428
+ }
429
+
462
430
 
463
431
  module.exports = {
464
432
  getUUID,
@@ -475,7 +443,6 @@ module.exports = {
475
443
  getHCStats,
476
444
  isAgentActiveState,
477
445
  iastRestClientStatus,
478
- removeOlderSnapshots,
479
446
  logRollOver,
480
447
  getValidatorServiceEndpointURL,
481
448
  getCSECmode,
@@ -484,5 +451,6 @@ module.exports = {
484
451
  setWSHealthStatus,
485
452
  addLogEventtoBuffer,
486
453
  getLogEvents,
487
- getFramework
454
+ getFramework,
455
+ sendErrorToErrorInbox
488
456
  };
@@ -0,0 +1,61 @@
1
+ /*
2
+ * Copyright 2024 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: New Relic Software License v1.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const { HttpsProxyAgent } = require('https-proxy-agent')
9
+ const logs = require('../../logging');
10
+ let logger = logs.getLogger();
11
+ const commonUtils = require('../../commonUtils');
12
+ const fs = require('fs');
13
+
14
+ let agentProxyWithKeepAlive = null
15
+
16
+ exports.proxyAgent = function proxyAgent(config) {
17
+ if (null !== agentProxyWithKeepAlive) {
18
+ return agentProxyWithKeepAlive
19
+ }
20
+ const proxyUrl = proxyOptions(config)
21
+ let proxyOpts = {
22
+ secureEndpoint: config.ssl,
23
+ auth: proxyUrl.auth,
24
+ ca: config?.certificates?.length ? config.certificates : [],
25
+ keepAlive: true
26
+ }
27
+ let cert = '';
28
+ try {
29
+ const certPath = commonUtils.getPathOfCACert();
30
+ cert = fs.readFileSync(certPath, 'utf8');
31
+ } catch (error) {
32
+ logger.error("Error in reading certificate:", error);
33
+
34
+ }
35
+ proxyOpts.ca.push(cert);
36
+ logger.info(`Using proxy: ${proxyUrl}`)
37
+ agentProxyWithKeepAlive = new HttpsProxyAgent(proxyUrl, proxyOpts)
38
+ return agentProxyWithKeepAlive
39
+ }
40
+
41
+ /**
42
+ * Utility to create proxy URL
43
+ * @param config
44
+ * @returns
45
+ */
46
+ function proxyOptions(config) {
47
+ let proxyUrl
48
+ if (config.proxy) {
49
+ proxyUrl = config.proxy
50
+ } else {
51
+ proxyUrl = 'https://'
52
+ let proxyAuth = config.proxy_user
53
+ if (config.proxy_pass !== '') {
54
+ proxyAuth += ':' + config.proxy_pass
55
+ proxyUrl += `${proxyAuth}@`
56
+ }
57
+
58
+ proxyUrl += `${config.proxy_host || 'localhost'}:${config.proxy_port || 80}`
59
+ }
60
+ return proxyUrl
61
+ }
@@ -31,7 +31,7 @@ function completedRequestsMapInit(id) {
31
31
 
32
32
  function addCompletedRequests(id, eventId) {
33
33
  let data = completedRequestsMap.get(id);
34
- if(!data){
34
+ if (!data) {
35
35
  return;
36
36
  }
37
37
  data.push(eventId);
@@ -42,15 +42,15 @@ function getCompletedRequestsMap() {
42
42
  return completedRequestsMap;
43
43
  }
44
44
 
45
- function clearCompletedRequestMap(){
45
+ function clearCompletedRequestMap() {
46
46
  completedRequestsMap.clear();
47
47
  }
48
48
 
49
- function clearPendingRequestIdSet(){
49
+ function clearPendingRequestIdSet() {
50
50
  pendingRequestIds.clear();
51
51
  }
52
52
 
53
- function IASTCleanup(){
53
+ function IASTCleanup() {
54
54
  clearCompletedRequestMap();
55
55
  clearPendingRequestIdSet();
56
56
  }
@@ -69,6 +69,8 @@ function generateIASTDataRequest() {
69
69
  let object = {};
70
70
  object['jsonName'] = 'iast-data-request';
71
71
  object['applicationUUID'] = Agent.getAgent().applicationInfo.applicationUUID;
72
+ object['appEntityGuid'] = Agent.getAgent().applicationInfo.appEntityGuid;
73
+ object['appAccountId'] = Agent.getAgent().applicationInfo.appAccountId;
72
74
  object["batchSize"] = batchSize ? batchSize : 300;
73
75
  object['pendingRequestIds'] = getPendingRequestIds();
74
76
  object['completedRequests'] = Object.fromEntries(getCompletedRequestsMap());
@@ -21,7 +21,6 @@ const LogMessage = require('../../../LogMessage');
21
21
  require('dns').setDefaultResultOrder('ipv4first')
22
22
  const PolicyManager = require('../../../Policy');
23
23
 
24
- const statusUtils = require('../../../statusUtils');
25
24
 
26
25
  const find = `${SLASH}{{NR_CSEC_VALIDATOR_HOME_TMP}}`;
27
26
  const CSEC_HOME_TMP_CONST = new RegExp(find, 'g');
@@ -158,7 +157,6 @@ function handleFuzzRequest(fuzzDetails) {
158
157
  handleFuzzResponse(response, fuzzDetails);
159
158
  }
160
159
  } catch (err) {
161
- statusUtils.addErrortoBuffer(err);
162
160
  const fuzzFailEvent = new FuzzFailEvent(fuzzRequest.headers[NR_CSEC_FUZZ_REQUEST_ID]);
163
161
  logger.error(stringify(fuzzFailEvent));
164
162
  try {
@@ -17,11 +17,11 @@ const hc = require('../../health-check');
17
17
  const fs = require('fs');
18
18
  const { promisify } = require('../../sec-util');
19
19
  const ResponseHandler = require('./response');
20
- const { EMPTY_APPLICATION_UUID, LOG_MESSAGES, CSEC_SEP, NR_CSEC_FUZZ_REQUEST_ID, EXITEVENT, JSON_NAME, RASP, SEVERE } = require('../../sec-agent-constants');
21
- const statusUtils = require('../../statusUtils');
20
+ const { EMPTY_APPLICATION_UUID, LOG_MESSAGES, CSEC_SEP, NR_CSEC_FUZZ_REQUEST_ID, EXITEVENT, JSON_NAME, SEVERE } = require('../../sec-agent-constants');
22
21
  const commonUtils = require('../../commonUtils');
23
22
  const IASTUtil = require('../websocket/response/IASTUtils');
24
23
  const LogMessage = require('../../LogMessage');
24
+ const proxyUtil = require('./proxy-util');
25
25
 
26
26
  const BUSY = 'busy';
27
27
  const IDLE = 'idle';
@@ -105,18 +105,25 @@ SecWebSocket.prototype.init = function init() {
105
105
  const authHeaders = require('../../Auth-headers').getInstance();
106
106
  initLogger.info(`Connecting to Validator at ${validatorService}`);
107
107
  IASTUtil.IASTCleanup();
108
- let cert = ''
108
+ let cert = '';
109
109
  try {
110
110
  const certPath = commonUtils.getPathOfCACert();
111
111
  cert = fs.readFileSync(certPath, 'utf8');
112
112
  } catch (error) {
113
113
  logger.error("Error in reading certificate:", error);
114
+ commonUtils.sendErrorToErrorInbox(error)
114
115
  const logMessage = new LogMessage.logMessage(SEVERE, 'Error in reading certificate for websocket', __filename, error);
115
116
  commonUtils.addLogEventtoBuffer(logMessage);
116
117
 
117
118
  }
118
-
119
- webSocket = new WebSocket(validatorService, { headers: authHeaders, cert: cert, handshakeTimeout: 10000 });
119
+ const isProxy = !!(NRAgent.config.proxy || NRAgent.config.proxy_port || NRAgent.config.proxy_host)
120
+ if(isProxy){
121
+ const agentWithProxy = proxyUtil.proxyAgent(NRAgent.config);
122
+ webSocket = new WebSocket(validatorService, { headers: authHeaders, cert: cert, handshakeTimeout: 10000, agent:agentWithProxy });
123
+ }
124
+ else{
125
+ webSocket = new WebSocket(validatorService, { headers: authHeaders, cert: cert, handshakeTimeout: 10000 });
126
+ }
120
127
 
121
128
  webSocket.on(WS_ON_OPEN, this.openCB);
122
129
  webSocket.on(WS_ON_MSG, this.msgCB);
@@ -162,6 +169,7 @@ SecWebSocket.prototype.reconnect = function reconnect() {
162
169
  return;
163
170
  }
164
171
  commonUtils.setWSHealthStatus('Error');
172
+ commonUtils.sendErrorToErrorInbox(new Error("Unable to connect to Security Engine"))
165
173
  logger.debug(LOG_MESSAGES.DETECTED_BROKEN_CONN);
166
174
  this.obeyReconnect();
167
175
 
@@ -204,13 +212,11 @@ SecWebSocket.prototype.pingWS = async function pingWS() {
204
212
  if (err) {
205
213
  logger.debug("Error while pinging:", err);
206
214
  commonUtils.setWSHealthStatus('Error');
207
- statusUtils.addErrortoBuffer(err);
208
215
 
209
216
  }
210
217
  });
211
218
  } catch (err) {
212
219
  logger.debug("error in ping:", err);
213
- statusUtils.addErrortoBuffer(err);
214
220
  commonUtils.setWSHealthStatus('Error');
215
221
  return;
216
222
  }
@@ -266,7 +272,6 @@ SecWebSocket.prototype.dispatch = async function dispatch(event) {
266
272
  })
267
273
  } catch (error) {
268
274
  logger.debug(LOG_MESSAGES.ERROR_WHILE_SEND_EVENT, eventStr, error);
269
- statusUtils.addErrortoBuffer(error);
270
275
  handleDispatchFailure(this, event, eventStr);
271
276
  }
272
277
  };
@@ -286,7 +291,6 @@ SecWebSocket.prototype.dispatchApplicationInfo = async function dispatchApplicat
286
291
  try {
287
292
  await promisify(this.instance, this.instance.send)(eventStr, { mask: true });
288
293
  } catch (error) {
289
- statusUtils.addErrortoBuffer(error);
290
294
  logger.warn('Error while sending applicationInfo:', error);
291
295
  this.obeyReconnect();
292
296
  }
@@ -325,7 +329,6 @@ SecWebSocket.prototype.flushEventQueue = async function flushEventQueue() {
325
329
  const results = await Promise.all(sentEvents);
326
330
  hc.getInstance().registerEventsSent(results.length);
327
331
  } catch (err) {
328
- statusUtils.addErrortoBuffer(err);
329
332
  logger.debug('Flush failed, reception of all events on IC couldn\'t be ensured.');
330
333
  }
331
334
  };
@@ -398,7 +401,7 @@ const defaultOnOpenCB = () => async function onOpen() {
398
401
  initLogger.info(LOG_MESSAGES.SENDING_APPINFO_COMPLETE, JSON.stringify(applicationInfo));
399
402
  } catch (err) {
400
403
  logger.error(`Connection broken: ${err.message}`);
401
- statusUtils.addErrortoBuffer(err);
404
+ commonUtils.sendErrorToErrorInbox(err);
402
405
  commonUtils.setWSHealthStatus('Error')
403
406
  setWebSocketConn();
404
407
  self.setValidatorNotReadyForEvents();
@@ -431,10 +434,10 @@ const defaultOnMsgCB = () => function onMessage(data) {
431
434
  */
432
435
  const defaultOnErrCB = () => function onClose(error) {
433
436
  commonUtils.setWSHealthStatus('Error');
434
- statusUtils.addErrortoBuffer(error);
435
437
  const self = this;
436
438
  logger.error("Error while connecting to validator:", error.message)
437
439
  const logMessage = new LogMessage.logMessage(SEVERE, 'Error while connecting to validator', __filename, error);
440
+ commonUtils.sendErrorToErrorInbox(error);
438
441
  commonUtils.addLogEventtoBuffer(logMessage);
439
442
  logger.warn("Security Agent INACTIVE!!!")
440
443
  if (error && error.code && (error.code === 'ECONNREFUSED' || error.code === 'EPIPE' || error.code === 'ECONNRESET')) {
@@ -34,8 +34,8 @@ 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;
38
- this.entityGuid = NRAgent.config.entity_guid;
37
+ this.appAccountId = NRAgent.config.account_id;
38
+ this.appEntityGuid = NRAgent.config.entity_guid;
39
39
  this.linkingMetadata = LinkingMetaData.getLinkingMetadata();
40
40
  this.linkingMetadata.agentRunId = NRAgent.config.run_id;
41
41
  }
@@ -46,7 +46,8 @@ function HC(info) {
46
46
  this.applicationUUID = info.applicationUUID;
47
47
  this.eventType = 'sec_health_check_lc';
48
48
  if (NRAgent && NRAgent.config) {
49
- this.entityGuid = NRAgent.config.entity_guid;
49
+ this.appEntityGuid = NRAgent.config.entity_guid;
50
+ this.appAccountId = NRAgent.config.account_id;
50
51
  }
51
52
  this.protectedServer = NODE_SERVER;
52
53
  this.eventDropCount = 0;
@@ -24,15 +24,14 @@ module.exports = {
24
24
  CSEC_HOME_TMP: `${CSEC_HOME}${sep}nr-security-home${sep}tmp${sep}language-agent${sep}${process.env.applicationUUID}`,
25
25
  AGENT_START_TIME: agentStartTime,
26
26
  FRAMEWORK: '',
27
- STATUS_LOG_FILE: `${CSEC_HOME}${sep}nr-security-home${sep}logs${sep}snapshots${sep}node-security-collector-status-${process.env.applicationUUID}.log`,
28
-
27
+
29
28
  DB_LIST: {
30
29
  MYSQL: 'MYSQL',
31
30
  MYSQL2: 'MYSQL',
32
31
  POSTGRES: 'POSTGRES',
33
32
  MONGODB: 'MONGODB',
34
33
  ORACLEDB: 'ORACLEDB',
35
- MSSQL: 'MSSQL',
34
+ MSSQL: 'MSSQL',
36
35
  SQLITE3: 'SQLITE3'
37
36
  },
38
37
  SERVER_COMMAND: {
@@ -9,16 +9,17 @@ const { SecEvent } = require('./event');
9
9
  const logs = require('./logging');
10
10
  const { Agent } = require('./agent');
11
11
  const shaUtil = require('./sha-size-util');
12
- const { LOG_MESSAGES, NR_CSEC_FUZZ_REQUEST_ID, COLON, VULNERABLE, EXITEVENT, EMPTY_STR, RASP, SEVERE, HYPHEN } = require('./sec-agent-constants');
12
+ const { NR_CSEC_FUZZ_REQUEST_ID, COLON, VULNERABLE, EXITEVENT, EMPTY_STR, SEVERE, HYPHEN } = require('./sec-agent-constants');
13
13
  const API = require('../../../nr-security-api');
14
14
  const NRAgent = API.getNRAgent();
15
15
  const HC = require('./health-check');
16
16
  const LogMessage = require('./LogMessage');
17
17
  const commonUtils = require('./commonUtils');
18
18
 
19
- const statusUtils = require('./statusUtils');
19
+ const appInfo = require('./applicationinfo').getInstance();
20
20
 
21
21
  let firstEventSent = false;
22
+ let appDir = EMPTY_STR;
22
23
 
23
24
  const events = require('events');
24
25
  const eventEmitter = undefined;
@@ -27,6 +28,20 @@ let logger = logs.getLogger();
27
28
  const initLogger = logs.getInitLogger();
28
29
 
29
30
  const NODE_SER = 'node-serialize/lib/serialize.js';
31
+ const SECURE_COOKIE = 'SECURE_COOKIE';
32
+
33
+ const insecureSettingApiIdSet = new Set();
34
+
35
+ /**
36
+ * Utility to remove insecureSettingAPIIds after 30 mins from the set.
37
+ * @param {*} entry
38
+ */
39
+ function addInsecureSettingApiId(entry) {
40
+ insecureSettingApiIdSet.add(entry);
41
+ setTimeout(() => {
42
+ insecureSettingApiIdSet.delete(entry);
43
+ }, 30 * 60 * 1000);
44
+ }
30
45
 
31
46
  function getSecEventEmitter() {
32
47
  if (eventEmitter) {
@@ -68,6 +83,7 @@ const sleep = (timeout) => new Promise((resolve) => {
68
83
  function generateSecEvent(securityMetadata) {
69
84
  const traceObject = securityMetadata.traceObject;
70
85
  const request = securityMetadata.request;
86
+ let appServerInfo = {};
71
87
 
72
88
  if (request && request.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
73
89
  HC.getInstance().iastEventStats.processed++;
@@ -75,6 +91,16 @@ function generateSecEvent(securityMetadata) {
75
91
  else {
76
92
  HC.getInstance().raspEventStats.processed++;
77
93
  }
94
+
95
+ try {
96
+ appDir = appInfo.serverInfo.deployedApplications[0].deployedPath;
97
+ appServerInfo.applicationDirectory = appDir;
98
+ appServerInfo.serverBaseDirectory = appDir;
99
+ appServerInfo.connectionConfiguration = { [request.serverPort]: request.protocol };
100
+
101
+ } catch (error) {
102
+ }
103
+
78
104
  try {
79
105
  let stakTrace = traceObject.stacktrace;
80
106
  let uri = EMPTY_STR;
@@ -84,8 +110,15 @@ function generateSecEvent(securityMetadata) {
84
110
 
85
111
  let apiId = shaUtil.getSHA256ForData(traceObject.stacktrace.join('|') + uri);
86
112
  apiId = securityMetadata.eventType + HYPHEN + apiId;
113
+ if (securityMetadata.eventType == SECURE_COOKIE) {
114
+ if (insecureSettingApiIdSet.has(apiId)) {
115
+ return;
116
+ }
117
+ addInsecureSettingApiId(apiId);
118
+ }
87
119
  const agentModule = Agent.getAgent();
88
120
  const metaData = {};
121
+ metaData.appServerInfo = appServerInfo;
89
122
  if (traceObject.sourceDetails && traceObject.sourceDetails.evalObj) {
90
123
  metaData.triggerViaRCI = true;
91
124
  metaData.rciMethodsCalls = traceObject.sourceDetails.evalObj.invokedCalls;
@@ -104,7 +137,6 @@ function generateSecEvent(securityMetadata) {
104
137
  event.httpRequest.route = uri;
105
138
  return event;
106
139
  } catch (e) {
107
- statusUtils.addErrortoBuffer(e);
108
140
  logger.error("Error in generating event:", e);
109
141
 
110
142
  const logMessage = new LogMessage.logMessage(SEVERE, 'Error while generating event', __filename, e);
@@ -25,7 +25,6 @@ const {
25
25
  } = require('./connections/websocket');
26
26
  const { HC_INTERVAL_MS } = require('../../resources/config');
27
27
  const { LOG_DIR, EXITEVENT, JSON_NAME } = require('./sec-agent-constants');
28
- const statusUtils = require('./statusUtils');
29
28
  const njsAgentConstants = require('./sec-agent-constants');
30
29
 
31
30
  let wsInstance;
@@ -120,6 +119,10 @@ const sendHC = async function () {
120
119
  json = JSON.parse(json);
121
120
  json.policyVersion = applicationInfo.policyVersion;
122
121
  json.entityGuid = applicationInfo.entityGuid;
122
+ if (NRAgent && NRAgent.config) {
123
+ json.appEntityGuid = NRAgent.config.entity_guid;
124
+ json.appAccountId = NRAgent.config.account_id;
125
+ }
123
126
 
124
127
  try {
125
128
  json.stats = await commonUtils.getHCStats();
@@ -131,7 +134,6 @@ const sendHC = async function () {
131
134
  json.serviceStatus.websocket = commonUtils.getWSHealthStatus();
132
135
  json.serviceStatus.logWriter = await commonUtils.isLogAccessible(`${LOG_DIR}node-security-collector.log`);
133
136
  json.serviceStatus.initLogWriter = await commonUtils.isLogAccessible(`${LOG_DIR}node-security-collector-init.log`);
134
- json.serviceStatus.statusLogWriter = await commonUtils.isLogAccessible(njsAgentConstants.STATUS_LOG_FILE);
135
137
  json.serviceStatus.agentActiveStat = commonUtils.isAgentActiveState();
136
138
  json.serviceStatus.iastRestClient = commonUtils.iastRestClientStatus();
137
139
 
@@ -148,8 +150,6 @@ const sendHC = async function () {
148
150
  hc.getInstance().resetEventStats();
149
151
 
150
152
  logger.info('Health Check Status:', JSON.stringify(json));
151
- statusUtils.addHCtoBuffer(json);
152
- statusUtils.writeSnapshot();
153
153
  getDispatcherAndSendEvent(json);
154
154
  }
155
155
  };
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@newrelic/security-agent",
3
- "version": "1.4.0",
3
+ "version": "2.0.0",
4
4
  "description": "New Relic Security Agent for Node.js",
5
5
  "main": "index.js",
6
- "jsonVersion": "1.2.3",
6
+ "jsonVersion": "1.2.5",
7
7
  "contributors": [
8
8
  {
9
9
  "name": "Sumit Suthar",
10
10
  "email": "ssuthar@newrelic.com",
11
- "web": "http://newrelic.com"
11
+ "web": "https://newrelic.com"
12
12
  },
13
13
  {
14
14
  "name": "Pratik Gunjetiya",
15
15
  "email": "pgunjetiya@newrelic.com",
16
- "web": "http://newrelic.com"
16
+ "web": "https://newrelic.com"
17
17
  }
18
18
  ],
19
19
  "scripts": {
@@ -28,6 +28,10 @@
28
28
  "New Relic Security Agent",
29
29
  "Node.js Application Security"
30
30
  ],
31
+ "engines": {
32
+ "node": ">=18",
33
+ "npm": ">=6.0.0"
34
+ },
31
35
  "author": "newrelic",
32
36
  "license": "New Relic Software License v1.0",
33
37
  "repository": {
@@ -35,13 +39,14 @@
35
39
  "url": "https://github.com/newrelic/csec-node-agent.git"
36
40
  },
37
41
  "dependencies": {
38
- "axios": "^1.6.8",
42
+ "axios": "^1.7.4",
39
43
  "check-disk-space": "^3.4.0",
40
44
  "content-type": "^1.0.5",
41
45
  "fast-safe-stringify": "^2.1.1",
42
46
  "find-package-json": "^1.2.0",
43
47
  "hash.js": "^1.1.7",
44
48
  "html-entities": "^2.3.6",
49
+ "https-proxy-agent": "^7.0.4",
45
50
  "is-invalid-path": "^1.0.2",
46
51
  "js-yaml": "^4.1.0",
47
52
  "jsonschema": "^1.4.1",
@@ -1,165 +0,0 @@
1
- /*
2
- * Copyright 2023 New Relic Corporation. All rights reserved.
3
- * SPDX-License-Identifier: New Relic Software License v1.0
4
- */
5
-
6
- /* eslint-disable new-cap */
7
-
8
- const njsAgentConstants = require('./sec-agent-constants');
9
- const agentConfig = require('../../resources/config');
10
- const { AGENT_DIR } = agentConfig;
11
- const lodash = require('lodash');
12
-
13
- let errors = [];
14
-
15
- const ringBuffer = require('ringbufferjs');
16
- const bufferedHC = new ringBuffer(5);
17
- const bufferedErrors = new ringBuffer(5);
18
- const fs = require('fs');
19
- const util = require('util');
20
- let HC = [];
21
- let lastHC = {};
22
- const logs = require('./logging');
23
- const commonUtils = require('./commonUtils');
24
- const logger = logs.getLogger();
25
- const statusFile = njsAgentConstants.STATUS_LOG_FILE;
26
- const sep = require('path').sep;
27
-
28
- const statusTemplate = 'Snapshot timestamp: : %s\n' +
29
- `CSEC Agent start timestamp: ${njsAgentConstants.AGENT_START_TIME} with application uuid:${commonUtils.getUUID()}\n` +
30
- `CSEC_HOME: ${njsAgentConstants.CSEC_HOME}${sep}nr-security-home${sep}\n` +
31
- `Agent location: ${AGENT_DIR}\n` +
32
- `Using CSEC Agent for Node.js, Node version:${process.version}, PID:${process.pid}\n` +
33
- `Process title: ${process.title}\n` +
34
- `Process binary: ${process.execPath}\n` +
35
- 'Application location: %s\n' +
36
- `Current working directory: ${process.cwd()}\n` +
37
- `Agent mode: ${commonUtils.getCSECmode()}\n` +
38
- 'Application server: %s\n' +
39
- `Application Framework: %s\n` +
40
- `Websocket connection to Prevent Web: ${commonUtils.getValidatorServiceEndpointURL()}, Status: %s\n` +
41
- 'Instrumentation successful\n' +
42
- 'Tracking loaded modules in the application\n' +
43
- 'Policy applied successfully. Policy version is: %s\n' +
44
- 'Started Health Check for Agent\n' +
45
- 'Started Inbound and Outbound monitoring \n' +
46
- '\nProcess stats:\n%s\n' +
47
- '\nService stats:\n%s\n' +
48
- '\nLast 5 errors: \n%s\n' +
49
- '\nLast 5 Health Checks are:\n %s \n';
50
-
51
- /**
52
- * Utility to add hc in hc buffer
53
- * @param {*} healthCheck
54
- */
55
- function addHCtoBuffer(healthCheck) {
56
- try {
57
- bufferedHC.enq(healthCheck);
58
- lastHC = healthCheck;
59
- } catch (error) {
60
- logger.debug(error);
61
- }
62
- }
63
-
64
- /**
65
- * Utility to add error object in error buffer
66
- * @param {*} error
67
- */
68
- function addErrortoBuffer(error) {
69
- try {
70
- let errorObj = {
71
- 'error': error.stack
72
- }
73
- bufferedErrors.enq(errorObj);
74
- } catch (error) {
75
- logger.debug(error);
76
- }
77
- }
78
-
79
- /**
80
- * utility to format status template with dynamic values
81
- */
82
- function getFormattedData() {
83
- const Agent = require('./agent').Agent.getAgent();
84
- const appInfo = Agent.applicationInfo;
85
- const deployedApplications = appInfo && appInfo.serverInfo && appInfo.serverInfo.deployedApplications;
86
- const appLoc = deployedApplications[0].deployedPath;
87
- const formattedSnapshot = util.format(statusTemplate, new Date().toString(), appLoc, appInfo.serverInfo.name, commonUtils.getFramework(), commonUtils.getWSHealthStatus(), appInfo.policyVersion, getKeyValPairs(lastHC.stats), getKeyValPairs(lastHC.serviceStatus), JSON.stringify(getBufferedErrors()), JSON.stringify(getBufferedHC()));
88
- return formattedSnapshot;
89
- }
90
-
91
- /**
92
- * utility to write snapshot in snapshot file
93
- */
94
- function writeSnapshot() {
95
- const snapshot = getFormattedData();
96
- commonUtils.removeOlderSnapshots();
97
- fs.writeFile(statusFile, snapshot, { mode: 0o660 }, function (err) {
98
- if (err) {
99
- logger.debug(err.message);
100
- const LogMessage = require('./LogMessage');
101
- const logMessage = new LogMessage.logMessage("DEBUG", 'Error in creating snapshot file', __filename, err);
102
- commonUtils.addLogEventtoBuffer(logMessage);
103
- } else {
104
- logger.info('Snapshot updated to file: %s', statusFile);
105
- fs.chmod(statusFile, 0o660, (err) => {
106
- if (err) {
107
- addErrortoBuffer(err);
108
- }
109
- });
110
- }
111
- });
112
- }
113
- /**
114
- * return buffered error instance.
115
- */
116
- function getRingBufferedErrors() {
117
- return bufferedErrors;
118
- }
119
-
120
- /**
121
- * return buffered HC list without null, undefined
122
- */
123
- function getBufferedHC() {
124
- try {
125
- HC = lodash.compact(bufferedHC._elements);
126
- } catch (error) {
127
- logger.debug(error);
128
- }
129
-
130
- return HC;
131
- }
132
- /**
133
- * returns buffered error list without null, undefined
134
- */
135
- function getBufferedErrors() {
136
- try {
137
- errors = lodash.compact(bufferedErrors._elements);
138
- } catch (error) {
139
- logger.debug(error);
140
- }
141
- return errors;
142
- }
143
-
144
- /**
145
- * Utility to get key val pairs from json object
146
- * @param {*} jsonObject
147
- */
148
- function getKeyValPairs(jsonObject) {
149
- let statStr = njsAgentConstants.EMPTY_STR;
150
- for (const key in jsonObject) {
151
- statStr = statStr + key + ': ' + jsonObject[key] + '\n';
152
- }
153
- return statStr;
154
- }
155
-
156
- module.exports = {
157
- statusTemplate,
158
- addHCtoBuffer,
159
- getFormattedData,
160
- writeSnapshot,
161
- addErrortoBuffer,
162
- getRingBufferedErrors,
163
- getBufferedHC,
164
- getBufferedErrors
165
- };