Skip to content

Commit 44a511c

Browse files
committed
merge main
2 parents a6d2c33 + d61be7e commit 44a511c

File tree

21 files changed

+1211
-559
lines changed

21 files changed

+1211
-559
lines changed

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,39 @@
1+
### v2.3.2 (2025-03-11)
2+
#### Bug fixes
3+
* Updated axios to v1.8.2
4+
#### Miscellaneous chores
5+
* Updated readme
6+
7+
8+
### v2.3.1 (2025-02-04)
9+
#### Bug fixes
10+
* Removed docker-cli-js dependency and updated mongodb unit test case (#283)
11+
* Added safety check for agentModule before accessing its properties (#284)
12+
13+
### v2.3.0 (2025-02-03)
14+
#### Features
15+
* Added Support for VM module
16+
* IAST support for Next.js
17+
* Support for Insecure settings i.e crypto, hash and random modules
18+
19+
#### Bug fixes
20+
* Fix for special characters in ws header
21+
* Fix for getting transaction in graphql instrumentation
22+
* Fix for mongodb unit tests
23+
24+
#### Miscellaneous chores
25+
* deps-dev: bump undici from v5.28.4 to v5.28.5
26+
* Updated axios to v1.7.9
27+
28+
### v2.2.0 (2024-12-18)
29+
#### Features
30+
* Support for express 5.x
31+
* IAST support for GraphQL
32+
* Added support for trustboundary security events
33+
34+
#### Bug fixes
35+
* Fix for empty route in fastify
36+
137
### v2.1.1 (2024-11-07)
238
#### Bug fixes
339
* Fix for assignment to logger constant

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ The [Developer docs](http://newrelic.github.io/node-newrelic/) for writing instr
5151
- `https`
5252
- `fs`
5353
- `child_process`
54+
- `vm`
5455
- [mysql](https://www.npmjs.com/package/mysql)(2.16.x and above)
5556
- [mysql2](https://www.npmjs.com/package/mysql2) (2.x and above)
5657
- [pg](https://www.npmjs.com/package/pg)(7.x and above)
@@ -68,6 +69,7 @@ The [Developer docs](http://newrelic.github.io/node-newrelic/) for writing instr
6869
- [xpath](https://www.npmjs.com/package/xpath)(0.0.20 and above)
6970
- [xpath.js](https://www.npmjs.com/package/xpath.js)(0.0.1 and above)
7071
- [undici](https://www.npmjs.com/package/undici)(4.7.0 and above)
72+
- [next](https://www.npmjs.com/package/next)(13.4.19 and above)
7173

7274
For more information, please see New Relic Node.js agent [compatibility and requirements](https://docs.newrelic.com/docs/apm/agents/nodejs-agent/getting-started/compatibility-requirements-nodejs-agent/).
7375

THIRD_PARTY_NOTICES.md

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ code, the source code can be found at [https://github.com/newrelic/csec-node-age
5959
* [eslint-plugin-promise](#eslint-plugin-promise)
6060
* [eslint-plugin-sonarjs](#eslint-plugin-sonarjs)
6161
* [koa](#koa)
62-
* [mongodb-memory-server](#mongodb-memory-server)
6362
* [mongodb](#mongodb)
6463
* [mongodb](#mongodb)
6564
* [mongodb](#mongodb)
@@ -79,7 +78,7 @@ code, the source code can be found at [https://github.com/newrelic/csec-node-age
7978

8079
### axios
8180

82-
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+
This product includes source derived from [axios](https://github.com/axios/axios) ([v1.8.2](https://github.com/axios/axios/tree/v1.8.2)), distributed under the [MIT License](https://github.com/axios/axios/blob/v1.8.2/LICENSE):
8382

8483
```
8584
# Copyright (c) 2014-present Matt Zabriskie & Collaborators
@@ -259,7 +258,7 @@ THE SOFTWARE.
259258

260259
### https-proxy-agent
261260

262-
This product includes source derived from [https-proxy-agent](https://github.com/TooTallNate/proxy-agents) ([v7.0.4](https://github.com/TooTallNate/proxy-agents/tree/v7.0.4)), distributed under the [MIT License](https://github.com/TooTallNate/proxy-agents/blob/v7.0.4/LICENSE):
261+
This product includes source derived from [https-proxy-agent](https://github.com/TooTallNate/proxy-agents) ([v7.0.6](https://github.com/TooTallNate/proxy-agents/tree/v7.0.6)), distributed under the [MIT License](https://github.com/TooTallNate/proxy-agents/blob/v7.0.6/LICENSE):
263262

264263
```
265264
(The MIT License)
@@ -512,7 +511,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
512511

513512
### semver
514513

515-
This product includes source derived from [semver](https://github.com/npm/node-semver) ([v7.5.4](https://github.com/npm/node-semver/tree/v7.5.4)), distributed under the [ISC License](https://github.com/npm/node-semver/blob/v7.5.4/LICENSE):
514+
This product includes source derived from [semver](https://github.com/npm/node-semver) ([v7.6.3](https://github.com/npm/node-semver/tree/v7.6.3)), distributed under the [ISC License](https://github.com/npm/node-semver/blob/v7.6.3/LICENSE):
516515

517516
```
518517
The ISC License
@@ -1874,23 +1873,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18741873
18751874
```
18761875

1877-
### mongodb-memory-server
1878-
1879-
This product includes source derived from [mongodb-memory-server](https://github.com/nodkz/mongodb-memory-server) ([v8.13.0](https://github.com/nodkz/mongodb-memory-server/tree/v8.13.0)), distributed under the [MIT License](https://github.com/nodkz/mongodb-memory-server/blob/v8.13.0/LICENSE.md):
1880-
1881-
```
1882-
The MIT License (MIT)
1883-
1884-
Copyright (c) 2017-present Pavel Chertorogov
1885-
1886-
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:
1887-
1888-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
1889-
1890-
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.
1891-
1892-
```
1893-
18941876
### mongodb
18951877

18961878
This product includes source derived from [mongodb](https://github.com/mongodb/node-mongodb-native) ([v2.2.36](https://github.com/mongodb/node-mongodb-native/tree/v2.2.36)), distributed under the [Apache-2.0 License](undefined):

lib/instrumentation-security/core/event-constants.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ const EVENT_TYPE = {
1414
HTTP_REQUEST: 'HTTP_REQUEST',
1515
CODE_INJECTION: 'CODE_INJECTION',
1616
XXE: 'XXE',
17-
CIPHER: 'CIPHER',
1817
HASH: 'HASH',
1918
RANDOM: 'RANDOM',
2019
UNVALIDATED_REDIRECT: 'UNVALIDATED_REDIRECT',
2120
REFLECTED_XSS: 'REFLECTED_XSS',
2221
XPATH: 'XPATH',
2322
LDAP: 'LDAP',
24-
SECURE_COOKIE: 'SECURE_COOKIE'
23+
SECURE_COOKIE: 'SECURE_COOKIE',
24+
TRUSTBOUNDARY: 'TRUSTBOUNDARY',
25+
CRYPTO: 'CRYPTO',
2526
}
2627
const EVENT_CATEGORY = {
2728
MYSQL: 'MYSQL',
@@ -42,7 +43,9 @@ const EVENT_CATEGORY = {
4243
REFLECTED_XSS: 'REFLECTED_XSS',
4344
XPATH: 'XPATH',
4445
LDAP: 'LDAP',
45-
SECURE_COOKIE: 'SECURE_COOKIE'
46+
SECURE_COOKIE: 'SECURE_COOKIE',
47+
TRUSTBOUNDARY: 'TRUSTBOUNDARY',
48+
CIPHER: 'CIPHER',
4649
}
4750

4851
module.exports = {
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright 2023 New Relic Corporation. All rights reserved.
3+
* SPDX-License-Identifier: New Relic Software License v1.0
4+
*/
5+
6+
module.exports = initialize
7+
const requestManager = require('../../core/request-manager');
8+
const secUtils = require("../../core/sec-utils");
9+
const API = require("../../../nr-security-api");
10+
const securityMetaData = require('../../core/security-metadata');
11+
const { EVENT_TYPE, EVENT_CATEGORY } = require('../../core/event-constants');
12+
const { NR_CSEC_FUZZ_REQUEST_ID } = require('../../core/constants');
13+
const logger = API.getLogger();
14+
const NRLIB = 'newrelic/lib';
15+
const SALIB = 'security-agent/lib'
16+
17+
/**
18+
* Entry point of crypto module hook
19+
* @param {*} shim
20+
* @param {*} mod
21+
* @param {*} moduleName
22+
*/
23+
function initialize(shim, mod, moduleName) {
24+
if (API && API.getNRAgent() && API.getNRAgent().config.security.exclude_from_iast_scan.iast_detection_category.insecure_settings) {
25+
logger.warn('insecure_settings detection is disabled');
26+
return;
27+
}
28+
logger.info('Instrumenting ' + moduleName)
29+
cryptoCipherHooks(shim, mod, 'createCipheriv', moduleName);
30+
cryptoHashHmacHooks(shim, mod, 'createHash', moduleName);
31+
cryptoHashHmacHooks(shim, mod, 'createHmac', moduleName);
32+
cryptoRandomHooks(shim, Math, 'random', "Math");
33+
}
34+
/**
35+
* wrapper to hook crypto cipher methods
36+
* @param {*} shim
37+
* @param {*} mod
38+
* @param {*} methodName
39+
* @param {*} moduleName
40+
*/
41+
function cryptoCipherHooks(shim, mod, methodName, moduleName) {
42+
shim.wrap(mod, methodName, function makeWrapper(shim, fn) {
43+
logger.debug(`Instrumenting ${moduleName}.${methodName}`);
44+
return function wrapper() {
45+
const interceptedArgs = [arguments[0]];
46+
shim.interceptedArgs = interceptedArgs;
47+
const request = requestManager.getRequest(shim);
48+
if (request && API.getSecAgent() && API.getSecAgent().status && API.getSecAgent().status.getStatus() === 'active') {
49+
const traceObject = secUtils.getTraceObject(shim);
50+
const secMetadata = securityMetaData.getSecurityMetaData(request, interceptedArgs, traceObject, secUtils.getExecutionId(), EVENT_TYPE.CRYPTO, EVENT_CATEGORY.CIPHER)
51+
if (secMetadata.traceObject && secMetadata.traceObject.stacktrace && (secMetadata.traceObject.stacktrace[0].includes(NRLIB) || secMetadata.traceObject.stacktrace[0].includes(SALIB))) {
52+
//do nothing
53+
}
54+
else {
55+
const secEvent = API.generateSecEvent(secMetadata);
56+
this.secEvent = secEvent;
57+
API.sendEvent(secEvent);
58+
}
59+
}
60+
const result = fn.apply(this, arguments);
61+
62+
if (result && request && request.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
63+
API.generateExitEvent(this.secEvent);
64+
delete this.secEvent
65+
}
66+
return result;
67+
}
68+
})
69+
}
70+
71+
72+
/**
73+
* wrapper to hook crypto hash and mac methods
74+
* @param {*} shim
75+
* @param {*} mod
76+
* @param {*} methodName
77+
* @param {*} moduleName
78+
*/
79+
function cryptoHashHmacHooks(shim, mod, methodName, moduleName) {
80+
shim.wrap(mod, methodName, function makeWrapper(shim, fn) {
81+
logger.debug(`Instrumenting ${moduleName}.${methodName}`);
82+
return function wrapper() {
83+
const interceptedArgs = [arguments[0]];
84+
shim.interceptedArgs = interceptedArgs;
85+
const request = requestManager.getRequest(shim);
86+
87+
if (request && arguments[0] !== 'sha256' && API.getSecAgent() && API.getSecAgent().status && API.getSecAgent().status.getStatus() === 'active') {
88+
const traceObject = secUtils.getTraceObject(shim);
89+
const secMetadata = securityMetaData.getSecurityMetaData(request, interceptedArgs, traceObject, secUtils.getExecutionId(), EVENT_TYPE.HASH, EVENT_CATEGORY.HASH)
90+
if (secMetadata.traceObject && secMetadata.traceObject.stacktrace && (secMetadata.traceObject.stacktrace[0].includes(NRLIB) || secMetadata.traceObject.stacktrace[0].includes(SALIB))) {
91+
//do nothing
92+
}
93+
else {
94+
const secEvent = API.generateSecEvent(secMetadata);
95+
this.secEvent = secEvent;
96+
API.sendEvent(secEvent);
97+
}
98+
}
99+
const result = fn.apply(this, arguments);
100+
if (result && request && request.headers[NR_CSEC_FUZZ_REQUEST_ID] && arguments[0] !== 'sha256') {
101+
API.generateExitEvent(this.secEvent);
102+
delete this.secEvent
103+
}
104+
return result;
105+
}
106+
})
107+
}
108+
/**
109+
* Wrapper for random hooks
110+
* @param {*} shim
111+
* @param {*} mod
112+
* @param {*} methodName
113+
* @param {*} moduleName
114+
*/
115+
function cryptoRandomHooks(shim, mod, methodName, moduleName) {
116+
shim.wrap(mod, methodName, function makeWrapper(shim, fn) {
117+
logger.debug(`Instrumenting ${moduleName}.${methodName}`);
118+
return function wrapper() {
119+
const interceptedArgs = ["Math.random"];
120+
shim.interceptedArgs = interceptedArgs;
121+
const request = requestManager.getRequest(shim);
122+
if (request && API.getSecAgent() && API.getSecAgent().status && API.getSecAgent().status.getStatus() === 'active') {
123+
const traceObject = secUtils.getTraceObject(shim);
124+
const secMetadata = securityMetaData.getSecurityMetaData(request, interceptedArgs, traceObject, secUtils.getExecutionId(), EVENT_TYPE.RANDOM, EVENT_CATEGORY.WEAKRANDOM)
125+
if (secMetadata.traceObject && secMetadata.traceObject.stacktrace && secMetadata.traceObject.stacktrace[0] && (secMetadata.traceObject.stacktrace[0].includes(NRLIB) || secMetadata.traceObject.stacktrace[0].includes(SALIB))) {
126+
//do nothing
127+
}
128+
else {
129+
const secEvent = API.generateSecEvent(secMetadata);
130+
this.secEvent = secEvent;
131+
API.sendEvent(secEvent);
132+
}
133+
134+
}
135+
const result = fn.apply(this, arguments);
136+
if (result && request && request.headers[NR_CSEC_FUZZ_REQUEST_ID]) {
137+
API.generateExitEvent(this.secEvent);
138+
delete this.secEvent
139+
}
140+
return result;
141+
}
142+
})
143+
}
144+
145+

lib/instrumentation-security/hooks/express/nr-express.js

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ module.exports = function initialize(shim, express) {
3030

3131
if (express.Router.use) {
3232
wrapExpress4(shim, express)
33-
}
33+
}
34+
else if (express.Router.prototype) {
35+
wrapExpress5(shim, express)
36+
}
3437
if(API && API.getNRAgent() && !API.getNRAgent().config.security.exclude_from_iast_scan.iast_detection_category.invalid_file_access){
3538
expressFileHook(shim, express && express.response, 'download')
3639
expressFileHook(shim, express && express.response, 'sendFile')
3740
}
38-
3941
}
4042

4143
/**
@@ -121,7 +123,7 @@ function extractParams(shim, req) {
121123
* @param {*} mod
122124
* @param {*} fun
123125
*/
124-
function expressFileHook(shim, mod, fun){
126+
function expressFileHook(shim, mod, fun) {
125127
shim.wrap(mod, fun, function makeFAWrapper(shim, fn) {
126128
if (!shim.isFunction(fn)) {
127129
return fn
@@ -149,4 +151,70 @@ function expressFileHook(shim, mod, fun){
149151
});
150152
}
151153

154+
/**
155+
* Wrapper to hook express version 5.x route method
156+
* @param {*} shim
157+
* @param {*} express
158+
*/
159+
function wrapExpress5(shim, express) {
160+
shim.wrap(express.Router.prototype, 'route', function wrapRoute(shim, fn) {
161+
if (!shim.isFunction(fn)) {
162+
return fn
163+
}
164+
logger.debug('Instrumenting express.Router.prototype.route');
165+
return function wrappedRoute() {
166+
try {
167+
const stakTrace = secUtils.traceElementForRoute();
168+
const splittedStack = stakTrace[0].split(DOUBLE_DOLLAR);
169+
const key = lodash.upperCase(splittedStack[1]) + ATTHERATE + arguments[0];
170+
routeManager.setRoute(key, splittedStack[0]);
171+
} catch (error) {
172+
173+
}
174+
const route = fn.apply(this, arguments)
175+
return route;
176+
}
177+
})
178+
179+
}
180+
181+
module.exports.wrapRouter = function wrapExpress5Router(shim, mod) {
182+
let layer = shim.require('./lib/layer');
183+
shim.wrap(layer.prototype, 'match', function wrapParam(shim, fn) {
184+
if (!shim.isFunction(fn)) {
185+
return fn
186+
}
187+
logger.debug('Instrumenting router.layer.match');
188+
return function wrappedParam() {
189+
const route = fn.apply(this, arguments);
190+
191+
try {
192+
const uri = this.route.path;
193+
const params = this.params;
194+
const transaction = shim.tracer.getTransaction();
195+
if (transaction) {
196+
let request = requestManager.getRequestFromId(transaction.id);
197+
if (params && request) {
198+
Object.keys(params).forEach(function (key) {
199+
if (params[key]) {
200+
if (!request.parameterMap[key]) {
201+
request.parameterMap[key] = new Array(params[key].toString());
202+
requestManager.setRequest(transaction.id, request);
203+
}
204+
}
205+
});
206+
}
207+
if (uri && request) {
208+
request.uri = uri;
209+
requestManager.setRequest(transaction.id, request);
210+
}
211+
}
212+
} catch (error) {
213+
logger.debug("Error while getting path params via router module", error);
214+
}
215+
216+
return route
217+
}
218+
})
219+
}
152220

0 commit comments

Comments
 (0)