@newrelic/security-agent 2.3.2 → 2.4.1
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 +15 -0
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/lib/instrumentation-security/hooks/grpc-js/nr-grpc.js +35 -6
- package/lib/instrumentation-security/hooks/http/nr-http.js +7 -2
- package/lib/instrumentation-security/hooks/nextjs/nr-next.js +52 -0
- package/lib/instrumentation-security/index.js +9 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
### v2.4.1 (2025-05-13)
|
|
2
|
+
#### Bug fixes
|
|
3
|
+
* Only listen once for uncaughtException
|
|
4
|
+
#### Miscellaneous chores
|
|
5
|
+
* (deps-dev): bump koa from v2.14.2 to v2.16.1
|
|
6
|
+
* (deps-dev): bump tar-fs from v2.1.1 to v2.1.2
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### v2.4.0 (2025-03-21)
|
|
10
|
+
#### Features
|
|
11
|
+
* Support for gRPC error reporting
|
|
12
|
+
* Support to detect API endpoints in next.js framework
|
|
13
|
+
#### Miscellaneous chores
|
|
14
|
+
* Updated axios to v1.8.4
|
|
15
|
+
|
|
1
16
|
### v2.3.2 (2025-03-11)
|
|
2
17
|
#### Bug fixes
|
|
3
18
|
* Updated axios to v1.8.2
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -78,7 +78,7 @@ code, the source code can be found at [https://github.com/newrelic/csec-node-age
|
|
|
78
78
|
|
|
79
79
|
### axios
|
|
80
80
|
|
|
81
|
-
This product includes source derived from [axios](https://github.com/axios/axios) ([v1.8.
|
|
81
|
+
This product includes source derived from [axios](https://github.com/axios/axios) ([v1.8.4](https://github.com/axios/axios/tree/v1.8.4)), distributed under the [MIT License](https://github.com/axios/axios/blob/v1.8.4/LICENSE):
|
|
82
82
|
|
|
83
83
|
```
|
|
84
84
|
# Copyright (c) 2014-present Matt Zabriskie & Collaborators
|
|
@@ -16,6 +16,7 @@ const grpcutils = require('../../core/grpc-utils');
|
|
|
16
16
|
const routeManager = require('../../core/route-manager');
|
|
17
17
|
let serverAddress;
|
|
18
18
|
const ExceptionReporting = require('../../../nr-security-agent/lib/core/ExceptionReporting');
|
|
19
|
+
const responseErrorSet = new Set([2, 12, 13, 14, 15])
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
|
|
@@ -58,6 +59,33 @@ module.exports.wrapStartCall = function wrappedClient(shim, callStream) {
|
|
|
58
59
|
shim.wrap(callStream.Http2CallStream.prototype, 'start', wrapStart)
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Wrapper for error collection
|
|
64
|
+
* @param {*} shim
|
|
65
|
+
* @param {*} call
|
|
66
|
+
*/
|
|
67
|
+
module.exports.wrapCall = function wrappedCall(shim, call) {
|
|
68
|
+
shim.wrap(call, 'callErrorFromStatus', function wrapCallErrorFromStatus(shim, fn) {
|
|
69
|
+
if (!shim.isFunction(fn)) {
|
|
70
|
+
return fn
|
|
71
|
+
}
|
|
72
|
+
return function wrappedCallErrorFromStatus() {
|
|
73
|
+
const errRes = fn.apply(this, arguments);
|
|
74
|
+
try {
|
|
75
|
+
let error = new Error(errRes.message);
|
|
76
|
+
error.stack = errRes.stack;
|
|
77
|
+
const request = requestManager.getRequest(shim);
|
|
78
|
+
if (request) {
|
|
79
|
+
ExceptionReporting.generate5xxReportingEvent(error, request);
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return errRes;
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
61
89
|
module.exports.wrapServer = function wrappedServer(shim, server) {
|
|
62
90
|
const grpcVersion = shim.require('./package.json').version
|
|
63
91
|
logger.info("Instrumenting @grpc/grpc-js", grpcVersion)
|
|
@@ -109,6 +137,7 @@ function wrapStart(shim, original) {
|
|
|
109
137
|
"path": method,
|
|
110
138
|
"protocol": protocol,
|
|
111
139
|
}
|
|
140
|
+
|
|
112
141
|
if (request) {
|
|
113
142
|
const traceObject = secUtils.getTraceObject(shim);
|
|
114
143
|
const secMetadata = securityMetaData.getSecurityMetaData(request, urlObj, traceObject, secUtils.getExecutionId(), EVENT_TYPE.HTTP_REQUEST, EVENT_CATEGORY.HTTP)
|
|
@@ -256,11 +285,11 @@ function wrapCallback(shim, mod, method) {
|
|
|
256
285
|
if (arguments[0].details && request) {
|
|
257
286
|
ExceptionReporting.generateExceptionReportingEvent(arguments[0].details, request);
|
|
258
287
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
288
|
+
|
|
289
|
+
if (arguments[0].code && responseErrorSet.has(arguments[0].code) && request) {
|
|
290
|
+
let responseCode = arguments[0].code
|
|
291
|
+
ExceptionReporting.generate5xxReportingEvent(null, request, responseCode);
|
|
292
|
+
}
|
|
264
293
|
}
|
|
265
294
|
} catch (error) {
|
|
266
295
|
|
|
@@ -282,7 +311,7 @@ function wrapCallback(shim, mod, method) {
|
|
|
282
311
|
function addRequestData(shim, type, path, args) {
|
|
283
312
|
const data = Object.assign({});
|
|
284
313
|
try {
|
|
285
|
-
const transaction = shim.tracer.getTransaction();
|
|
314
|
+
const transaction = shim.tracer.getTransaction();
|
|
286
315
|
if (transaction) {
|
|
287
316
|
data.method = path;
|
|
288
317
|
data.url = path;
|
|
@@ -629,7 +629,12 @@ function deleteTempFileForIAST(file) {
|
|
|
629
629
|
/**
|
|
630
630
|
* Collect and report the uncaught Exception
|
|
631
631
|
*/
|
|
632
|
-
process.
|
|
632
|
+
const listeners = process.listeners('uncaughtException')
|
|
633
|
+
if (listeners.findIndex(uncaughtListener) === -1) {
|
|
634
|
+
process.on('uncaughtException', uncaughtListener);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function uncaughtListener(err, origin) {
|
|
633
638
|
console.error(err);
|
|
634
639
|
if (uncaughtExceptionReportFlag) {
|
|
635
640
|
process.exit(1);
|
|
@@ -641,7 +646,7 @@ process.on('uncaughtException', (err, origin) => {
|
|
|
641
646
|
}
|
|
642
647
|
logger.error("An uncaughtException is detected:", err);
|
|
643
648
|
uncaughtExceptionReportFlag = true;
|
|
644
|
-
}
|
|
649
|
+
}
|
|
645
650
|
|
|
646
651
|
/**
|
|
647
652
|
* utility to parse response headers for cookie check
|
|
@@ -6,9 +6,14 @@
|
|
|
6
6
|
'use strict'
|
|
7
7
|
const requestManager = require("../../core/request-manager");
|
|
8
8
|
const API = require("../../../nr-security-api");
|
|
9
|
+
const routeManager = require('../../core/route-manager');
|
|
9
10
|
const logger = API.getLogger();
|
|
11
|
+
const lodash = require('lodash');
|
|
12
|
+
const { ATTHERATE, DOUBLE_DOLLAR, EMPTY_STR } = require('../../core/constants');
|
|
10
13
|
|
|
11
14
|
const semver = require('semver')
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
12
17
|
|
|
13
18
|
module.exports = function initialize(shim, nextjs) {
|
|
14
19
|
const nextVersion = shim.require('./package.json').version
|
|
@@ -16,6 +21,23 @@ module.exports = function initialize(shim, nextjs) {
|
|
|
16
21
|
|
|
17
22
|
const nextServer = nextjs.default;
|
|
18
23
|
hookRunAPI(shim, nextServer.prototype, nextVersion);
|
|
24
|
+
|
|
25
|
+
//TODO need to update for API endpoints
|
|
26
|
+
try {
|
|
27
|
+
const appRoot = process.env.PWD;
|
|
28
|
+
const searchPath = appRoot + '/.next/server/pages/api';
|
|
29
|
+
const allAPIEndpoints = getAllAPIEndpoints(searchPath);
|
|
30
|
+
logger.debug("allAPIEndpoints:", allAPIEndpoints);
|
|
31
|
+
for (let index = 0; index < allAPIEndpoints.length; index++) {
|
|
32
|
+
const element = allAPIEndpoints[index];
|
|
33
|
+
let key = "*" + ATTHERATE + element;
|
|
34
|
+
routeManager.setRoute(key, EMPTY_STR);
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
logger.debug("Error while getting all API end points for next.js", error);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
19
41
|
}
|
|
20
42
|
|
|
21
43
|
/**
|
|
@@ -91,3 +113,33 @@ function extractParams(shim, page, params) {
|
|
|
91
113
|
}
|
|
92
114
|
}
|
|
93
115
|
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Utility to scan pages directory to get all avaible routes
|
|
119
|
+
* @param {*} dir
|
|
120
|
+
* @returns
|
|
121
|
+
*/
|
|
122
|
+
const getAllAPIEndpoints = (dir) => {
|
|
123
|
+
logger.debug("dir is:", dir)
|
|
124
|
+
const apiEndpoints = [];
|
|
125
|
+
|
|
126
|
+
const scanDirectory = (currentDir) => {
|
|
127
|
+
logger.debug("currentDir is:", currentDir)
|
|
128
|
+
const files = fs.readdirSync(currentDir);
|
|
129
|
+
|
|
130
|
+
files.forEach((file) => {
|
|
131
|
+
const filePath = path.join(currentDir, file);
|
|
132
|
+
const isDirectory = fs.statSync(filePath).isDirectory();
|
|
133
|
+
|
|
134
|
+
if (isDirectory) {
|
|
135
|
+
scanDirectory(filePath);
|
|
136
|
+
} else {
|
|
137
|
+
const apiEndpoint = filePath.replace(`${dir}/`, '/api/').replace(/\.js*.*/, '');
|
|
138
|
+
apiEndpoints.push(apiEndpoint);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
scanDirectory(dir);
|
|
144
|
+
return apiEndpoints;
|
|
145
|
+
};
|
|
@@ -235,6 +235,15 @@ newrelic.instrument({
|
|
|
235
235
|
}
|
|
236
236
|
})
|
|
237
237
|
|
|
238
|
+
newrelic.instrument({
|
|
239
|
+
moduleName: '@grpc/grpc-js/build/src/call',
|
|
240
|
+
isEsm: true,
|
|
241
|
+
onRequire: require('./hooks/grpc-js/nr-grpc').wrapCall,
|
|
242
|
+
onError: function intrumentErrorHandler(err) {
|
|
243
|
+
logger.error(err.message, err.stack)
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
|
|
238
247
|
newrelic.instrument({
|
|
239
248
|
moduleName: 'graphql/execution/execute',
|
|
240
249
|
isEsm: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/security-agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "New Relic Security Agent for Node.js",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"jsonVersion": "1.2.9",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"url": "https://github.com/newrelic/csec-node-agent.git"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"axios": "^1.8.
|
|
42
|
+
"axios": "^1.8.4",
|
|
43
43
|
"check-disk-space": "^3.4.0",
|
|
44
44
|
"content-type": "^1.0.5",
|
|
45
45
|
"cron": "^3.1.7",
|