@newrelic/security-agent 1.1.1 → 1.3.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 (26) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +1 -1
  3. package/THIRD_PARTY_NOTICES.md +211 -1
  4. package/lib/instrumentation-security/core/constants.js +1 -0
  5. package/lib/instrumentation-security/core/grpc-utils.js +53 -0
  6. package/lib/instrumentation-security/core/sec-utils.js +99 -57
  7. package/lib/instrumentation-security/core/security-metadata.js +12 -2
  8. package/lib/instrumentation-security/hooks/express/nr-express.js +43 -3
  9. package/lib/instrumentation-security/hooks/fs/nr-fs.js +4 -4
  10. package/lib/instrumentation-security/hooks/grpc-js/nr-grpc.js +301 -0
  11. package/lib/instrumentation-security/hooks/http/nr-http.js +34 -23
  12. package/lib/instrumentation-security/index.js +32 -3
  13. package/lib/nr-security-agent/index.js +7 -1
  14. package/lib/nr-security-agent/lib/core/Policy.js +0 -1
  15. package/lib/nr-security-agent/lib/core/commonUtils.js +19 -4
  16. package/lib/nr-security-agent/lib/core/connections/grpcClient.js +112 -0
  17. package/lib/nr-security-agent/lib/core/connections/websocket/response/IASTUtils.js +3 -0
  18. package/lib/nr-security-agent/lib/core/connections/websocket/response/fuzz-request-handler.js +32 -12
  19. package/lib/nr-security-agent/lib/core/connections/websocket/response/startup-handshake-handler.js +1 -1
  20. package/lib/nr-security-agent/lib/core/connections/websocket/websocket.js +9 -4
  21. package/lib/nr-security-agent/lib/core/sec-agent-constants.js +2 -1
  22. package/lib/nr-security-agent/lib/core/sec-util.js +3 -1
  23. package/lib/nr-security-agent/lib/core/statusUtils.js +7 -6
  24. package/lib/nr-security-agent/lib/core/websocket-client.js +1 -1
  25. package/lib/nr-security-agent/lib/core/xss-utils.js +11 -5
  26. package/package.json +3 -2
@@ -7,6 +7,7 @@
7
7
  const stringify = require('fast-safe-stringify');
8
8
 
9
9
  const restClient = require('../../restclient');
10
+ const grpcClient = require('../../grpcClient');
10
11
  const { Agent } = require('../../../agent');
11
12
  const { FuzzFailEvent } = require('../../../FuzzFailEvent');
12
13
  const { CSEC_HOME_TMP, COLON, NR_CSEC_FUZZ_REQUEST_ID, SLASH, CSEC_SEP, SEVERE } = require('../../../sec-agent-constants');
@@ -20,9 +21,6 @@ const LogMessage = require('../../../LogMessage');
20
21
  require('dns').setDefaultResultOrder('ipv4first')
21
22
  const PolicyManager = require('../../../Policy');
22
23
 
23
- const {
24
- IS_LAMBDA_ENV,
25
- AWS_LAMBDA_FUNCTION_VERSION_ENV_IDENTIFIER } = require('../../../sec-agent-constants');
26
24
  const statusUtils = require('../../../statusUtils');
27
25
 
28
26
  const find = `${SLASH}{{NR_CSEC_VALIDATOR_HOME_TMP}}`;
@@ -53,7 +51,7 @@ function startIASTSchedular() {
53
51
  if (isNaN(probingInterval)) {
54
52
  probingInterval = 5;
55
53
  }
56
-
54
+
57
55
  iastIntervalConst = setInterval(() => {
58
56
  let data = IASTUtil.generateIASTDataRequest();
59
57
  let currentTime = Date.now();
@@ -125,13 +123,16 @@ function handler(json) {
125
123
  function handleFuzzRequest(fuzzDetails) {
126
124
  const { fuzzRequest } = fuzzDetails;
127
125
  try {
128
- if (IS_LAMBDA_ENV) {
129
- const qualifiedARN = Agent.getAgent().applicationInfo.applicationUUID;
130
- const splitArn = String.prototype.split.call(qualifiedARN, ':');
131
- const arn = splitArn.slice(0, splitArn.length - 1).join(':');
132
- const ver = process.env[AWS_LAMBDA_FUNCTION_VERSION_ENV_IDENTIFIER];
133
- logger.info('Invoking Lambda:: ARN: ' + arn);
134
- logger.info('Invoking Lambda:: Version: ' + ver);
126
+ if (fuzzRequest.protocol == 'grpc') {
127
+ const config = parseGRPCRequestToFuzz(fuzzRequest);
128
+ config.headers['nr-csec-parent-id'] = fuzzRequest.id;
129
+ IASTUtil.completedRequestsMapInit(fuzzRequest.id);
130
+ grpcClient.fireRequest(config);
131
+ if (fuzzRequest.headers && fuzzRequest.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
132
+ logScannedApiId(fuzzRequest.headers[NR_CSEC_FUZZ_REQUEST_ID], fuzzRequest.requestURI)
133
+ }
134
+
135
+ IASTUtil.removePendingRequestId(fuzzRequest.id);
135
136
 
136
137
  } else {
137
138
  const config = parseAxiosHttpRequestToFuzz(fuzzRequest);
@@ -153,7 +154,6 @@ function handleFuzzRequest(fuzzDetails) {
153
154
 
154
155
 
155
156
  logger.info('Firing http request:: URL: ' + config.url);
156
-
157
157
  const response = restClient.fireRequest(config);
158
158
  handleFuzzResponse(response, fuzzDetails);
159
159
  }
@@ -210,6 +210,26 @@ function parseAxiosHttpRequestToFuzz(requestObject) {
210
210
  };
211
211
  }
212
212
 
213
+ /**
214
+ * Parses the passed fuzz request object to
215
+ * Axios config.
216
+ *
217
+ * @param {JSON} requestObject
218
+ */
219
+ function parseGRPCRequestToFuzz(requestObject) {
220
+ let serverName = requestObject.serverName ? requestObject.serverName : LOCALHOST;
221
+ let host = serverName + COLON + requestObject.serverPort
222
+ return {
223
+ url: requestObject.protocol + COLON_SLASH_SLASH + host + requestObject.url,
224
+ requestURI: requestObject.requestURI,
225
+ protocol: requestObject.protocol,
226
+ serverPort: requestObject.serverPort,
227
+ method: requestObject.method,
228
+ data: requestObject.body,
229
+ headers: requestObject.headers,
230
+ timeout: 5000,
231
+ };
232
+ }
213
233
 
214
234
 
215
235
  /**
@@ -18,7 +18,7 @@ module.exports.handler = function handler(json) {
18
18
 
19
19
  initLogger.info(LOG_MESSAGES.AGENT_INIT_WITH_PROP, JSON.stringify(startupConfig.config));
20
20
  initLogger.info('Security Agent is now ACTIVE for', applicationInfoModule.getInstance().applicationUUID);
21
- const logMessage = new LogMessage.logMessage("SEVERE", `Security Agent is ACTIVE for ${ applicationInfoModule.getInstance().applicationUUID}`, __filename, null);
21
+ const logMessage = new LogMessage.logMessage("INFO", `Security Agent is ACTIVE for ${ applicationInfoModule.getInstance().applicationUUID}`, __filename, null);
22
22
  require('../../../commonUtils').addLogEventtoBuffer(logMessage);
23
23
 
24
24
  } catch (error) {
@@ -104,6 +104,7 @@ SecWebSocket.prototype.init = function init() {
104
104
  const validatorService = commonUtils.getValidatorServiceEndpointURL();
105
105
  const authHeaders = require('../../Auth-headers').getInstance();
106
106
  initLogger.info(`Connecting to Validator at ${validatorService}`);
107
+ IASTUtil.IASTCleanup();
107
108
  let cert = ''
108
109
  try {
109
110
  const certPath = commonUtils.getPathOfCACert();
@@ -138,6 +139,7 @@ SecWebSocket.prototype.init = function init() {
138
139
  })
139
140
  webSocket.on('close', () => {
140
141
  logger.warn("WS connection closed");
142
+ this.obeyReconnect();
141
143
  })
142
144
  this.instance = webSocket;
143
145
  };
@@ -172,7 +174,7 @@ SecWebSocket.prototype.obeyReconnect = function obeyReconnect() {
172
174
  }
173
175
 
174
176
  initTimeOut = setTimeout(() => {
175
- IASTUtil.IASTCleanup();
177
+
176
178
  logger.debug("Terminating ws instance and reconnecting");
177
179
  try {
178
180
  instance.terminate();
@@ -189,18 +191,21 @@ SecWebSocket.prototype.pingWS = async function pingWS() {
189
191
  let currentTime = Date.now();
190
192
 
191
193
  let timeDiffInPing = (currentTime - lastPongtime) / 1000;
192
- if (timeDiffInPing > 40 && lastPongtime != 0) {
194
+ logger.debug("Websocket ready state is:",self.instance.readyState)
195
+
196
+ if (timeDiffInPing > 40 && lastPongtime != 0 && self.instance.readyState > 1) {
193
197
  self.obeyReconnect();
194
198
  lastPongtime = 0;
195
199
  return;
196
200
  }
201
+
197
202
  try {
198
203
  await promisify(self.instance, self.instance.ping)(ACK, true, function (err) {
199
204
  if (err) {
200
205
  logger.debug("Error while pinging:", err);
201
206
  commonUtils.setWSHealthStatus('Error');
202
207
  statusUtils.addErrortoBuffer(err);
203
- self.obeyReconnect();
208
+
204
209
  }
205
210
  });
206
211
  } catch (err) {
@@ -258,7 +263,7 @@ SecWebSocket.prototype.dispatch = async function dispatch(event) {
258
263
  hc.getInstance().exitEventStats.sent++;
259
264
  }
260
265
 
261
- });
266
+ })
262
267
  } catch (error) {
263
268
  logger.debug(LOG_MESSAGES.ERROR_WHILE_SEND_EVENT, eventStr, error);
264
269
  statusUtils.addErrortoBuffer(error);
@@ -111,6 +111,7 @@ module.exports = {
111
111
  SEVERE: 'SEVERE',
112
112
  HIGH: 'HIGH',
113
113
  CRITICAL: 'CRITICAL',
114
+ HYPHEN: '-',
114
115
 
115
116
  LOG_MESSAGES: {
116
117
  LOADED_CSEC_ENVS_MSG: '[STEP-1][COMPLETE][env] Environment Information Gathering Done.',
@@ -165,7 +166,7 @@ module.exports = {
165
166
  ERROR_WHILE_SEND_EVENT: 'ERROR While Sending Event: {}',
166
167
  WS_CONNECTED: '[STEP-4][COMPLETE][WS] Connected To Prevent-web Service',
167
168
  SENDING_APPINFO: '[APP_INFO] Sending Application Info To Prevent-web Service: ',
168
- SENDING_APPINFO_COMPLETE: '[COMPLETE][APP_INFO] Application info sent to Prevent-Web service :',
169
+ SENDING_APPINFO_COMPLETE: '[STEP-3][COMPLETE][APP_INFO] Application info sent to Prevent-Web service :',
169
170
  APPLY_INSTRUMENTATION: '[STEP-6][BEGIN][instrumentation] Applying Instrumentation',
170
171
  AVAIL_DISK: 'Available Disk Space Is: ',
171
172
  AVAIL_DISK_ERROR: 'Insufficient Disk Space Available To The Location %s Is : %s',
@@ -9,7 +9,7 @@ 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 } = require('./sec-agent-constants');
12
+ const { LOG_MESSAGES, NR_CSEC_FUZZ_REQUEST_ID, COLON, VULNERABLE, EXITEVENT, EMPTY_STR, RASP, 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');
@@ -83,6 +83,7 @@ function generateSecEvent(securityMetadata) {
83
83
  }
84
84
 
85
85
  let apiId = shaUtil.getSHA256ForData(traceObject.stacktrace.join('|') + uri);
86
+ apiId = securityMetadata.eventType + HYPHEN + apiId;
86
87
  const agentModule = Agent.getAgent();
87
88
  const metaData = {};
88
89
  if (traceObject.sourceDetails && traceObject.sourceDetails.evalObj) {
@@ -100,6 +101,7 @@ function generateSecEvent(securityMetadata) {
100
101
  const isAPIBlocked = false;
101
102
  const event = new SecEvent(traceObject.sourceDetails.fileName, traceObject.sourceDetails.funcName, traceObject.sourceDetails.lineNumber, securityMetadata.interceptedArgs, securityMetadata.executionId, agentModule.applicationInfo, securityMetadata.request, securityMetadata.eventType, traceObject.sourceDetails.source, securityMetadata.eventCategory, metaData, stakTrace, apiId, isAPIBlocked);
102
103
  event.metaData.isClientDetectedFromXFF = isClientDetectedFromXFF;
104
+ event.httpRequest.route = uri;
103
105
  return event;
104
106
  } catch (e) {
105
107
  statusUtils.addErrortoBuffer(e);
@@ -23,10 +23,11 @@ const logs = require('./logging');
23
23
  const commonUtils = require('./commonUtils');
24
24
  const logger = logs.getLogger();
25
25
  const statusFile = njsAgentConstants.STATUS_LOG_FILE;
26
+ const sep = require('path').sep;
26
27
 
27
28
  const statusTemplate = 'Snapshot timestamp: : %s\n' +
28
29
  `CSEC Agent start timestamp: ${njsAgentConstants.AGENT_START_TIME} with application uuid:${commonUtils.getUUID()}\n` +
29
- `SEC_HOME: ${njsAgentConstants.CSEC_HOME}\n` +
30
+ `CSEC_HOME: ${njsAgentConstants.CSEC_HOME}${sep}nr-security-home${sep}\n` +
30
31
  `Agent location: ${AGENT_DIR}\n` +
31
32
  `Using CSEC Agent for Node.js, Node version:${process.version}, PID:${process.pid}\n` +
32
33
  `Process title: ${process.title}\n` +
@@ -35,10 +36,10 @@ const statusTemplate = 'Snapshot timestamp: : %s\n' +
35
36
  `Current working directory: ${process.cwd()}\n` +
36
37
  `Agent mode: ${commonUtils.getCSECmode()}\n` +
37
38
  'Application server: %s\n' +
38
- 'Application Framework: %s\n' +
39
+ `Application Framework: %s\n` +
39
40
  `Websocket connection to Prevent Web: ${commonUtils.getValidatorServiceEndpointURL()}, Status: %s\n` +
40
- 'Instrumentation successful:\n' +
41
- 'Tracking loaded modules in the application:\n' +
41
+ 'Instrumentation successful\n' +
42
+ 'Tracking loaded modules in the application\n' +
42
43
  'Policy applied successfully. Policy version is: %s\n' +
43
44
  'Started Health Check for Agent\n' +
44
45
  'Started Inbound and Outbound monitoring \n' +
@@ -83,7 +84,7 @@ function getFormattedData() {
83
84
  const appInfo = Agent.applicationInfo;
84
85
  const deployedApplications = appInfo && appInfo.serverInfo && appInfo.serverInfo.deployedApplications;
85
86
  const appLoc = deployedApplications[0].deployedPath;
86
- const formattedSnapshot = util.format(statusTemplate, new Date().toString(), appLoc, appInfo.serverInfo.name, njsAgentConstants.FRAMEWORK, commonUtils.getWSHealthStatus(), appInfo.policyVersion, getKeyValPairs(lastHC.stats), getKeyValPairs(lastHC.serviceStatus), JSON.stringify(getBufferedErrors()), JSON.stringify(getBufferedHC()));
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()));
87
88
  return formattedSnapshot;
88
89
  }
89
90
 
@@ -97,7 +98,7 @@ function writeSnapshot() {
97
98
  if (err) {
98
99
  logger.debug(err.message);
99
100
  const LogMessage = require('./LogMessage');
100
- const logMessage = new LogMessage.logMessage("SEVERE", 'Error in creating snapshot file', __filename, err);
101
+ const logMessage = new LogMessage.logMessage("DEBUG", 'Error in creating snapshot file', __filename, err);
101
102
  commonUtils.addLogEventtoBuffer(logMessage);
102
103
  } else {
103
104
  logger.info('Snapshot updated to file: %s', statusFile);
@@ -82,7 +82,7 @@ const initialize = function () {
82
82
  try {
83
83
  const framework = NRAgent.environment.get('Framework')[0];
84
84
  const dispatcher = NRAgent.environment.get('Dispatcher')[0];
85
- const logMessage = new LogMessage.logMessage("SEVERE", `Detected framework: ${framework} and dispatcher: ${dispatcher}`, __filename, null);
85
+ const logMessage = new LogMessage.logMessage("INFO", `Detected framework: ${framework} and dispatcher: ${dispatcher}`, __filename, null);
86
86
  commonUtils.addLogEventtoBuffer(logMessage);
87
87
  } catch (error) {
88
88
  logger.debug("Error while extracting data from APM environment",error);
@@ -10,6 +10,7 @@ const querystring = require('querystring');
10
10
  const { Agent } = require('./agent');
11
11
 
12
12
  const htmlEntities = require('html-entities');
13
+ const { STRING } = require('./sec-agent-constants');
13
14
 
14
15
  const tagNameRegex = /<([a-zA-Z_-]+[0-9]*|!--)/gmi;
15
16
  const attribRegex = /([^(/\s<'">)]+?)(?:\s*)=\s*(('|")([\s\S]*?)(?:(?=(\\?))\5.)*?\3|.+?(?=\/>|>|\?>|\s|<\/|$))/gmi;
@@ -136,10 +137,15 @@ function decodeResponseData (responseBody, responseHeaders) {
136
137
 
137
138
  function isXSS (combinedData) {
138
139
  const attackConstructs = new Set();
139
- for (const data of combinedData) {
140
- const constructs = getXSSConstructs(data);
141
- constructs.forEach(attackConstructs.add, attackConstructs);
140
+ try {
141
+ for (const data of combinedData) {
142
+ const constructs = getXSSConstructs(data);
143
+ constructs.forEach(attackConstructs.add, attackConstructs);
144
+ }
145
+ } catch (error) {
146
+ logger.debug("Error while getting rxss constucts:", error);
142
147
  }
148
+
143
149
  return attackConstructs;
144
150
  }
145
151
 
@@ -152,7 +158,7 @@ function getXSSConstructs (data) {
152
158
  let tmpStartPos = 0;
153
159
  let matcher;
154
160
  let attribMatcher;
155
- while (currPos < data.length) {
161
+ while (currPos < data.length && typeof data == STRING ) {
156
162
  let tagName;
157
163
 
158
164
  matcher = tagNameRegex.exec(data);
@@ -233,7 +239,7 @@ function getXSSConstructs (data) {
233
239
  currPos = tmpCurrPos;
234
240
  }
235
241
 
236
- if (data.charAt(currPos) !== ANGLE_END_CHAR) {
242
+ if (typeof data == STRING && data.charAt(currPos) !== ANGLE_END_CHAR) {
237
243
  const tmp = data.indexOf(ANGLE_END, currPos);
238
244
  if (tmp !== -1) {
239
245
  currPos = tmp;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@newrelic/security-agent",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "New Relic Security Agent for Node.js",
5
5
  "main": "index.js",
6
- "jsonVersion": "1.1.1",
6
+ "jsonVersion": "1.2.0",
7
7
  "contributors": [
8
8
  {
9
9
  "name": "Sumit Suthar",
@@ -58,6 +58,7 @@
58
58
  "ws": "^8.14.2"
59
59
  },
60
60
  "devDependencies": {
61
+ "@grpc/proto-loader": "^0.7.10",
61
62
  "@hapi/hapi": "^21.3.0",
62
63
  "@koa/router": "^12.0.0",
63
64
  "@newrelic/newrelic-oss-cli": "^0.1.2",