@contrast/agent 4.8.0 → 4.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bootstrap.js +12 -2
- package/esm.mjs +33 -0
- package/lib/assess/index.js +2 -0
- package/lib/assess/models/source-event.js +6 -0
- package/lib/assess/policy/rules.json +29 -0
- package/lib/assess/policy/signatures.json +6 -0
- package/lib/assess/propagators/JSON/stringify.js +77 -7
- package/lib/assess/sinks/rethinkdb-nosql-injection.js +142 -0
- package/lib/assess/sources/event-handler.js +307 -0
- package/lib/assess/sources/index.js +93 -5
- package/lib/assess/spdy/index.js +23 -0
- package/lib/assess/spdy/sinks/index.js +23 -0
- package/lib/assess/spdy/sinks/xss.js +84 -0
- package/lib/assess/technologies/index.js +2 -1
- package/lib/constants.js +2 -1
- package/lib/contrast.js +6 -6
- package/lib/core/arch-components/index.js +1 -0
- package/lib/core/arch-components/mongodb.js +22 -18
- package/lib/core/arch-components/postgres.js +21 -3
- package/lib/core/arch-components/sqlite3.js +3 -5
- package/lib/core/config/options.js +35 -1
- package/lib/core/exclusions/exclusion.js +2 -5
- package/lib/core/express/index.js +25 -2
- package/lib/core/express/utils.js +8 -3
- package/lib/hooks/frameworks/index.js +2 -0
- package/lib/hooks/frameworks/spdy.js +87 -0
- package/lib/hooks/http.js +11 -0
- package/lib/reporter/translations/to-protobuf/dtm/trace-event/index.js +4 -4
- package/package.json +11 -6
|
@@ -26,15 +26,33 @@ ModuleHook.resolve({ name: 'pg', file: 'lib/client.js' }, (pgClient) =>
|
|
|
26
26
|
alwaysRun: true,
|
|
27
27
|
post(wrapCtx) {
|
|
28
28
|
try {
|
|
29
|
-
const {
|
|
29
|
+
const {
|
|
30
|
+
host = process.env.PGHOST,
|
|
31
|
+
port = process.env.PGPORT
|
|
32
|
+
} = wrapCtx.result;
|
|
33
|
+
|
|
34
|
+
if (!host) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let url = host;
|
|
39
|
+
|
|
40
|
+
// build protocol and port into url prior to parsing
|
|
41
|
+
if (url.indexOf('://') === -1) {
|
|
42
|
+
url = `postgresql://${url}`;
|
|
43
|
+
}
|
|
44
|
+
if (port !== undefined) {
|
|
45
|
+
url = `${url}:${port}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
30
48
|
agentEmitter.emit('architectureComponent', {
|
|
31
49
|
vendor: 'PostgreSQL',
|
|
32
50
|
remotePort: port || 0,
|
|
33
|
-
url: new URL(
|
|
51
|
+
url: new URL(url).toString()
|
|
34
52
|
});
|
|
35
53
|
} catch (err) {
|
|
36
54
|
logger.warn(
|
|
37
|
-
'unable to report PostgreSQL architecture component\n',
|
|
55
|
+
'unable to report PostgreSQL architecture component\n%o',
|
|
38
56
|
err
|
|
39
57
|
);
|
|
40
58
|
}
|
|
@@ -13,6 +13,7 @@ Copyright: 2022 Contrast Security, Inc
|
|
|
13
13
|
way not consistent with the End User License Agreement.
|
|
14
14
|
*/
|
|
15
15
|
'use strict';
|
|
16
|
+
|
|
16
17
|
const patcher = require('../../hooks/patcher');
|
|
17
18
|
const ModuleHook = require('../../hooks/require');
|
|
18
19
|
const agentEmitter = require('../../agent-emitter');
|
|
@@ -26,17 +27,14 @@ ModuleHook.resolve({ name: 'sqlite3' }, (sqlite3) => {
|
|
|
26
27
|
alwaysRun: true,
|
|
27
28
|
post(wrapCtx) {
|
|
28
29
|
try {
|
|
29
|
-
// can either be a path to a file or `:memory:'.
|
|
30
|
-
const url = new URL(wrapCtx.args[0]).toString();
|
|
31
|
-
|
|
32
30
|
agentEmitter.emit('architectureComponent', {
|
|
33
31
|
vendor: 'SQLite3',
|
|
34
|
-
url,
|
|
32
|
+
url: wrapCtx.args[0],
|
|
35
33
|
remoteHost: '',
|
|
36
34
|
remotePort: 0
|
|
37
35
|
});
|
|
38
36
|
} catch (err) {
|
|
39
|
-
logger.warn('unable to report SQLite3 architecture component\n', err);
|
|
37
|
+
logger.warn('unable to report SQLite3 architecture component\n%o', err);
|
|
40
38
|
}
|
|
41
39
|
}
|
|
42
40
|
});
|
|
@@ -33,7 +33,8 @@ Copyright: 2022 Contrast Security, Inc
|
|
|
33
33
|
* @module lib/core/config/options
|
|
34
34
|
*/
|
|
35
35
|
'use strict';
|
|
36
|
-
const
|
|
36
|
+
const { Command, Option } = require('commander');
|
|
37
|
+
const program = new Command();
|
|
37
38
|
const os = require('os');
|
|
38
39
|
const url = require('url');
|
|
39
40
|
const path = require('path');
|
|
@@ -486,6 +487,12 @@ const agent = [
|
|
|
486
487
|
desc:
|
|
487
488
|
'set limit for stack trace size (larger limits will improve accuracy but increase memory usage)'
|
|
488
489
|
},
|
|
490
|
+
{
|
|
491
|
+
name: 'agent.traverse_and_track',
|
|
492
|
+
arg: '<traverse-and-track>',
|
|
493
|
+
default: false,
|
|
494
|
+
desc: 'source membrane alternative'
|
|
495
|
+
},
|
|
489
496
|
{
|
|
490
497
|
name: 'agent.polling.app_activity_ms',
|
|
491
498
|
arg: '<ms>',
|
|
@@ -967,6 +974,16 @@ if (process.env.CONTRAST_DEV) {
|
|
|
967
974
|
}
|
|
968
975
|
];
|
|
969
976
|
}
|
|
977
|
+
const sails = [
|
|
978
|
+
{
|
|
979
|
+
name: 'pathToSails',
|
|
980
|
+
arg: '<path>',
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
name: 'gdsrc',
|
|
984
|
+
arg: '<path>',
|
|
985
|
+
}
|
|
986
|
+
];
|
|
970
987
|
|
|
971
988
|
const options = [].concat(
|
|
972
989
|
misc,
|
|
@@ -1008,6 +1025,23 @@ options.forEach((option) => {
|
|
|
1008
1025
|
program.option(name, option.desc);
|
|
1009
1026
|
});
|
|
1010
1027
|
|
|
1028
|
+
// In NODE-2059 it was discovered that a module was appending config options that the
|
|
1029
|
+
// agent didn't recognize and was causing the application to not load properly.
|
|
1030
|
+
// The agent doesn't need to do anything with these options. It just needs to not
|
|
1031
|
+
// throw an error when it encounters them but we also don't need them displayed on
|
|
1032
|
+
// the agent's config option list. The newest version of Commander lets us do exactly this.
|
|
1033
|
+
// This is structured so that if anything like this is discovered again, they can be
|
|
1034
|
+
// added in easily.
|
|
1035
|
+
const hiddenOptions = [].concat(
|
|
1036
|
+
sails
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
hiddenOptions.forEach((option) => {
|
|
1040
|
+
program.addOption(
|
|
1041
|
+
new Option(`--${option.name} ${option.arg}`).hideHelp()
|
|
1042
|
+
)
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1011
1045
|
function getDefault(optionName) {
|
|
1012
1046
|
let option;
|
|
1013
1047
|
options.forEach((entry) => {
|
|
@@ -31,12 +31,9 @@ class Exclusion {
|
|
|
31
31
|
return this.assess && this.appliesToRule(id, this.assessmentRulesList);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// When an exclusion applies to all rules, its rules list is empty
|
|
34
35
|
appliesToRule(id, list) {
|
|
35
|
-
|
|
36
|
-
const appliesToAllRules = list.length === 0;
|
|
37
|
-
const appliesToRuleId = list.includes(id);
|
|
38
|
-
|
|
39
|
-
return appliesToAllRules || appliesToRuleId;
|
|
36
|
+
return list.length === 0 || list.includes(id);
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
appliesToAllAssessRules() {
|
|
@@ -120,6 +120,24 @@ class ExpressFramework {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
});
|
|
123
|
+
|
|
124
|
+
patcher.patch(express.response, 'push', {
|
|
125
|
+
name: 'express.response.push',
|
|
126
|
+
patchType: PATCH_TYPES.PROTECT_SINK,
|
|
127
|
+
pre(data) {
|
|
128
|
+
agentEmitter.emit(
|
|
129
|
+
EVENTS.REQUEST_SEND,
|
|
130
|
+
data.args[0],
|
|
131
|
+
SINK_TYPES.RESPONSE_BODY
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const body = data.args[0];
|
|
135
|
+
if (isString(body)) {
|
|
136
|
+
emitSendEvent(body.valueOf());
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
123
141
|
patcher.patch(express.response, 'end', {
|
|
124
142
|
name: 'express.response.end',
|
|
125
143
|
patchType: PATCH_TYPES.PROTECT_SINK,
|
|
@@ -310,7 +328,9 @@ class ExpressFramework {
|
|
|
310
328
|
}, 'textParser');
|
|
311
329
|
|
|
312
330
|
this.useAfter(function ContrastBodyParsed(req, res, next) {
|
|
313
|
-
agentEmitter.emit(EVENTS.BODY_PARSED, req, res,
|
|
331
|
+
agentEmitter.emit(EVENTS.BODY_PARSED, req, res, {
|
|
332
|
+
type: INPUT_TYPES.BODY
|
|
333
|
+
});
|
|
314
334
|
next();
|
|
315
335
|
}, 'urlencodedParser');
|
|
316
336
|
|
|
@@ -356,7 +376,7 @@ class ExpressFramework {
|
|
|
356
376
|
const self = this;
|
|
357
377
|
|
|
358
378
|
// Hook the request handler so that we can access the top of each route.
|
|
359
|
-
// This is the only place the "params" hash is
|
|
379
|
+
// This is the only place the "params" hash is available
|
|
360
380
|
const Layer = ExpressFramework.getStack(app)[0].constructor;
|
|
361
381
|
const _handle = Layer.prototype.handle_request;
|
|
362
382
|
if (_handle) {
|
|
@@ -399,9 +419,12 @@ class ExpressFramework {
|
|
|
399
419
|
|
|
400
420
|
Whatever the core issue is, it doesn't appear to have any effects
|
|
401
421
|
elsewhere in any of our Express/Kraken framework support.
|
|
422
|
+
|
|
423
|
+
BODY_PARSED event is emitted to support Sails framework
|
|
402
424
|
*/
|
|
403
425
|
if (req.body) {
|
|
404
426
|
decorateRequest({ body: req.body });
|
|
427
|
+
agentEmitter.emit(EVENTS.BODY_PARSED, req, res, req.body);
|
|
405
428
|
}
|
|
406
429
|
}
|
|
407
430
|
});
|
|
@@ -33,7 +33,7 @@ const {
|
|
|
33
33
|
|
|
34
34
|
const EVENTS = {
|
|
35
35
|
REQUEST_READY: 'Express.RequestReady',
|
|
36
|
-
BODY_PARSED: 'Express.
|
|
36
|
+
BODY_PARSED: 'Express.bodyParsed',
|
|
37
37
|
COOKIES_PARSED: 'Express.cookiesParsed',
|
|
38
38
|
PARAMS_PARSED: 'Express.paramsParsed',
|
|
39
39
|
REQUEST_SEND: 'Express.requestSend',
|
|
@@ -246,7 +246,9 @@ const captureSignature = (layer, signature) => {
|
|
|
246
246
|
const normalizeSignatureArg = (arg) => {
|
|
247
247
|
// we weave in middleware for express that is prefixed with Contrast
|
|
248
248
|
// remove this from signature
|
|
249
|
-
if (
|
|
249
|
+
if (arg === '') {
|
|
250
|
+
return null;
|
|
251
|
+
} else if (typeof arg === 'function' && arg.name.startsWith('Contrast')) {
|
|
250
252
|
return null;
|
|
251
253
|
} else if (typeof arg === 'function') {
|
|
252
254
|
return getHandlerName(arg);
|
|
@@ -313,8 +315,11 @@ const updateRouterSignatures = (self, router, path) => {
|
|
|
313
315
|
return;
|
|
314
316
|
}
|
|
315
317
|
const routePath = _.get(route, 'path', '');
|
|
316
|
-
const routeHandle = _.get(route, 'stack[0].handle');
|
|
318
|
+
const routeHandle = _.get(route, 'stack[0].handle', '');
|
|
317
319
|
const routeMethod = _.get(route, 'stack[0].method');
|
|
320
|
+
if (!routeMethod) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
318
323
|
const newSignature = createSignature(self, 'Router', routeMethod, [
|
|
319
324
|
path,
|
|
320
325
|
routePath,
|
|
@@ -16,6 +16,7 @@ Copyright: 2022 Contrast Security, Inc
|
|
|
16
16
|
|
|
17
17
|
const Http = require('./http');
|
|
18
18
|
const Http2 = require('./http2');
|
|
19
|
+
const Spdy = require('./spdy');
|
|
19
20
|
const Hapi16 = require('./hapi16');
|
|
20
21
|
|
|
21
22
|
module.exports = function(agent) {
|
|
@@ -23,5 +24,6 @@ module.exports = function(agent) {
|
|
|
23
24
|
new Http(agent);
|
|
24
25
|
new Http(agent, 'https');
|
|
25
26
|
new Http2(agent);
|
|
27
|
+
new Spdy(agent);
|
|
26
28
|
new Hapi16(agent);
|
|
27
29
|
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Copyright: 2022 Contrast Security, Inc
|
|
3
|
+
Contact: support@contrastsecurity.com
|
|
4
|
+
License: Commercial
|
|
5
|
+
|
|
6
|
+
NOTICE: This Software and the patented inventions embodied within may only be
|
|
7
|
+
used as part of Contrast Security’s commercial offerings. Even though it is
|
|
8
|
+
made available through public repositories, use of this Software is subject to
|
|
9
|
+
the applicable End User Licensing Agreement found at
|
|
10
|
+
https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
11
|
+
between Contrast Security and the End User. The Software may not be reverse
|
|
12
|
+
engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
|
+
way not consistent with the End User License Agreement.
|
|
14
|
+
*/
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
PATCH_TYPES,
|
|
19
|
+
HTTP_RESPONSE_HOOKED_METHOD_KEYS,
|
|
20
|
+
SINK_TYPES
|
|
21
|
+
} = require('../../constants');
|
|
22
|
+
const patcher = require('../patcher');
|
|
23
|
+
const HttpFramework = require('./http');
|
|
24
|
+
const { isString } = require('../../util/is-string');
|
|
25
|
+
const { emitSendEvent } = require('../../hooks/frameworks/common');
|
|
26
|
+
const agentEmitter = require('../../agent-emitter');
|
|
27
|
+
|
|
28
|
+
class SpdyFramework extends HttpFramework {
|
|
29
|
+
/**
|
|
30
|
+
* @param {Agent} agent
|
|
31
|
+
*/
|
|
32
|
+
constructor(agent) {
|
|
33
|
+
super(agent, 'spdy');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {import('spdy')} spdy
|
|
38
|
+
* @returns {import('spdy')}
|
|
39
|
+
*/
|
|
40
|
+
onRequire(spdy) {
|
|
41
|
+
patcher.patch(spdy, 'createServer', {
|
|
42
|
+
name: `${this.id}.createServer`,
|
|
43
|
+
patchType: PATCH_TYPES.FRAMEWORK,
|
|
44
|
+
alwaysRun: true,
|
|
45
|
+
post: ({ args, result }) => {
|
|
46
|
+
this.handleServerCreate(args, result);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
patcher.patch(spdy.response, 'push', {
|
|
51
|
+
name: 'spdy.response.push',
|
|
52
|
+
patchType: PATCH_TYPES.FRAMEWORK,
|
|
53
|
+
pre(data) {
|
|
54
|
+
agentEmitter.emit(
|
|
55
|
+
HTTP_RESPONSE_HOOKED_METHOD_KEYS.PUSH,
|
|
56
|
+
data.args[0],
|
|
57
|
+
SINK_TYPES.RESPONSE_BODY
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const body = data.args[0];
|
|
61
|
+
if (isString(body)) {
|
|
62
|
+
emitSendEvent(body.valueOf());
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return spdy;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Emits a create event for the new Server instance.
|
|
72
|
+
* @param {any[]} args The arguments passed to the Server constructor
|
|
73
|
+
* @param {import('net').Server} server The http Server instance
|
|
74
|
+
*/
|
|
75
|
+
handleServerCreate(args, server) {
|
|
76
|
+
super.handleServerCreate(args, server);
|
|
77
|
+
|
|
78
|
+
patcher.patch(server, 'listen', {
|
|
79
|
+
name: `${this.id}.SpdyFramework.listen`,
|
|
80
|
+
patchType: PATCH_TYPES.FRAMEWORK,
|
|
81
|
+
alwaysRun: true,
|
|
82
|
+
pre: ({ args, obj }) => this.handleServerListen(args, obj)
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = SpdyFramework;
|
package/lib/hooks/http.js
CHANGED
|
@@ -195,5 +195,16 @@ module.exports = (agent) => {
|
|
|
195
195
|
}
|
|
196
196
|
});
|
|
197
197
|
});
|
|
198
|
+
|
|
199
|
+
moduleHook.resolve({ name: 'spdy' }, (spdy) => {
|
|
200
|
+
patcher.patch(spdy, 'createServer', {
|
|
201
|
+
name: `create-server-spdy-hooks`,
|
|
202
|
+
patchType: PATCH_TYPES.MISC,
|
|
203
|
+
alwaysRun: true,
|
|
204
|
+
post({ result }) {
|
|
205
|
+
hookServer(result, agent);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
});
|
|
198
209
|
};
|
|
199
210
|
module.exports.hookServer = hookServer;
|
|
@@ -48,9 +48,9 @@ module.exports = function TraceEvent(event = {}) {
|
|
|
48
48
|
16: ret, // 17 ret
|
|
49
49
|
17: args, // 18 args
|
|
50
50
|
18: stack, // 19 stack
|
|
51
|
-
19: eventSources, //
|
|
52
|
-
20: event.source, //
|
|
53
|
-
21: event.target, //
|
|
54
|
-
22: taintRanges //
|
|
51
|
+
19: eventSources, // 20 event_sources
|
|
52
|
+
20: event.source, // 21 source
|
|
53
|
+
21: event.target, // 22 target
|
|
54
|
+
22: taintRanges // 23 taint_ranges
|
|
55
55
|
});
|
|
56
56
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/agent",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.0",
|
|
4
4
|
"description": "Node.js security instrumentation by Contrast Security",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"security",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"Michael Woytowitz"
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
|
+
"preinstall": "npx npm-force-resolutions",
|
|
25
26
|
"docs": "jsdoc -c ../.jsdoc.json",
|
|
26
27
|
"release": "scripts/make-release.js",
|
|
27
28
|
"tag": "scripts/tag-release.js",
|
|
@@ -72,7 +73,7 @@
|
|
|
72
73
|
"@babel/types": "^7.12.1",
|
|
73
74
|
"@contrast/distringuish-prebuilt": "^2.2.0",
|
|
74
75
|
"@contrast/flat": "^4.1.1",
|
|
75
|
-
"@contrast/fn-inspect": "^2.4.
|
|
76
|
+
"@contrast/fn-inspect": "^2.4.3",
|
|
76
77
|
"@contrast/heapdump": "^1.1.0",
|
|
77
78
|
"@contrast/protobuf-api": "^3.2.0",
|
|
78
79
|
"@contrast/require-hook": "^2.0.6",
|
|
@@ -83,7 +84,7 @@
|
|
|
83
84
|
"bluebird": "^3.5.3",
|
|
84
85
|
"builtin-modules": "^3.2.0",
|
|
85
86
|
"cls-hooked": "^4.2.2",
|
|
86
|
-
"commander": "^
|
|
87
|
+
"commander": "^8.3.0",
|
|
87
88
|
"content-security-policy-parser": "^0.2.0",
|
|
88
89
|
"cookie": "^0.3.1",
|
|
89
90
|
"crc-32": "^1.0.0",
|
|
@@ -125,8 +126,8 @@
|
|
|
125
126
|
"codecov": "^3.7.0",
|
|
126
127
|
"config": "^3.3.3",
|
|
127
128
|
"csv-writer": "^1.2.0",
|
|
128
|
-
"deasync": "^0.1.
|
|
129
|
-
"dustjs-linkedin": "^3.0.
|
|
129
|
+
"deasync": "^0.1.24",
|
|
130
|
+
"dustjs-linkedin": "^3.0.1",
|
|
130
131
|
"ejs": "^3.1.6",
|
|
131
132
|
"escape-html": "^1.0.3",
|
|
132
133
|
"eslint": "^8.2.0",
|
|
@@ -190,5 +191,9 @@
|
|
|
190
191
|
"winston",
|
|
191
192
|
"winston-syslog",
|
|
192
193
|
"winston-daily-rotate-file"
|
|
193
|
-
]
|
|
194
|
+
],
|
|
195
|
+
"resolutions": {
|
|
196
|
+
"markdown-it": ">=12.3.2",
|
|
197
|
+
"marked": ">=4.0.10"
|
|
198
|
+
}
|
|
194
199
|
}
|