@contrast/agent 4.21.1 → 4.23.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/bin/VERSION +1 -1
- package/bin/contrast-service-darwin-arm64 +0 -0
- package/bin/{mac/contrast-service → contrast-service-darwin-x64} +0 -0
- package/bin/contrast-service-linux-arm64 +0 -0
- package/bin/{linux/contrast-service → contrast-service-linux-x64} +0 -0
- package/bin/{windows/contrast-service.exe → contrast-service-win32-x64} +0 -0
- package/lib/assess/models/call-context.js +13 -6
- package/lib/assess/propagators/JSON/stringify.js +3 -1
- package/lib/assess/sinks/dynamo.js +0 -1
- package/lib/core/arch-components/mongodb.js +140 -66
- package/lib/core/async-storage/index.js +1 -1
- package/lib/reporter/speedracer/index.js +44 -70
- package/lib/reporter/translations/to-protobuf/dtm/finding.js +11 -7
- package/lib/util/heap-dump.js +41 -29
- package/package.json +4 -5
package/bin/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.28.
|
|
1
|
+
2.28.22
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -103,18 +103,25 @@ module.exports = class CallContext {
|
|
|
103
103
|
return !!(str && typeof str === 'object' && str[PROXY_TARGET]);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
static getDisplayRange(arg) {
|
|
106
|
+
static getDisplayRange(arg, orgArg = arg, iteration = 0) {
|
|
107
107
|
if (tracker.getData(arg)) {
|
|
108
108
|
return new TagRange(0, arg.length - 1, 'untrusted');
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
if (arg && typeof arg === 'object') {
|
|
112
112
|
for (const key in arg) {
|
|
113
|
+
if (arg[key] && typeof arg[key] === 'object' && iteration < 5) {
|
|
114
|
+
const nestedDisplayRange = CallContext.getDisplayRange(arg[key], orgArg, iteration += 1);
|
|
115
|
+
if (!_.isEmpty(nestedDisplayRange)) {
|
|
116
|
+
return nestedDisplayRange;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
113
119
|
const trackedData = tracker.getData(arg[key]);
|
|
114
|
-
if (trackedData) {
|
|
120
|
+
if (trackedData && trackedData.tagRanges.length > 0) {
|
|
115
121
|
const { start, stop } = trackedData.tagRanges[0];
|
|
122
|
+
const offset = Array.isArray(orgArg) ? 2 : 0;
|
|
116
123
|
const taintedString = arg[key].substring(start, stop + 1);
|
|
117
|
-
const taintRangeStart = CallContext.valueString(
|
|
124
|
+
const taintRangeStart = CallContext.valueString(orgArg).indexOf(taintedString) + offset;
|
|
118
125
|
if (taintRangeStart === -1) {
|
|
119
126
|
// If tracked string is not in the abbreviated stringified obj, disable highlighting
|
|
120
127
|
return new TagRange(0, 0, 'disable-highlighting');
|
|
@@ -178,9 +185,9 @@ module.exports = class CallContext {
|
|
|
178
185
|
return value.toString();
|
|
179
186
|
}
|
|
180
187
|
|
|
181
|
-
const
|
|
188
|
+
const type = _.get(value, 'constructor.name', 'null');
|
|
182
189
|
|
|
183
|
-
if (
|
|
190
|
+
if ((type === 'Object' || type === 'Array') && value) {
|
|
184
191
|
// make string representation uniform with no new lines and consistent spaces
|
|
185
192
|
let str = util
|
|
186
193
|
.inspect(value)
|
|
@@ -192,7 +199,7 @@ module.exports = class CallContext {
|
|
|
192
199
|
|
|
193
200
|
return str;
|
|
194
201
|
}
|
|
195
|
-
return
|
|
202
|
+
return type;
|
|
196
203
|
}
|
|
197
204
|
};
|
|
198
205
|
|
|
@@ -331,7 +331,9 @@ module.exports.handle = function() {
|
|
|
331
331
|
// always exist, even if an empty array, for production code.
|
|
332
332
|
if (metadata.sourceEvents && !metadata.sourceEvents.length) {
|
|
333
333
|
const { sourceEvents, braned } = data.metadata;
|
|
334
|
-
|
|
334
|
+
// If it is a source membrane (could be deserialization membrane), then
|
|
335
|
+
// we need to get a source event.
|
|
336
|
+
if (braned && braned.membrane && braned.membrane.makeReqSourceEvent) {
|
|
335
337
|
sourceEvents.push(
|
|
336
338
|
braned.membrane.makeReqSourceEvent(data.result.length)
|
|
337
339
|
);
|
|
@@ -19,36 +19,74 @@ const { PATCH_TYPES } = require('../../constants');
|
|
|
19
19
|
const ModuleHook = require('../../hooks/require');
|
|
20
20
|
const patcher = require('../../hooks/patcher');
|
|
21
21
|
const logger = require('../logger')('contrast:arch-component');
|
|
22
|
-
const semver = require('semver');
|
|
23
22
|
|
|
23
|
+
// Architecture component for versions <3.0.0
|
|
24
24
|
ModuleHook.resolve(
|
|
25
25
|
{
|
|
26
26
|
name: 'mongodb',
|
|
27
27
|
file: 'lib/mongo_client.js',
|
|
28
|
-
version: '
|
|
28
|
+
version: '<3.0.0'
|
|
29
29
|
},
|
|
30
|
-
(MongoClient
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
(MongoClient) => {
|
|
31
|
+
patcher.patch(MongoClient, 'connect', {
|
|
32
|
+
name: 'MongoClient.connect.arch_component',
|
|
33
|
+
patchType: PATCH_TYPES.ARCH_COMPONENT,
|
|
34
|
+
alwaysRun: true,
|
|
35
|
+
pre(ctx) {
|
|
36
|
+
// check if typeof callback == 'function'
|
|
37
|
+
// if yes:
|
|
38
|
+
// - MongoClient.connect executes a cb function, which has access to the connection status
|
|
39
|
+
// if not:
|
|
40
|
+
// - MongoClient.connect should return a promise and we can check it's result in another hook, just as before
|
|
41
|
+
const callbackIndex = ctx.args[2] ? 2 : 1;
|
|
42
|
+
if (ctx.args[callbackIndex] instanceof Function || typeof ctx.args[callbackIndex] === 'function') {
|
|
43
|
+
ctx.args[callbackIndex] = patcher.patch(ctx.args[callbackIndex], {
|
|
44
|
+
name: 'MongoClient.connect.callback.arch_component',
|
|
45
|
+
patchType: PATCH_TYPES.ARCH_COMPONENT,
|
|
46
|
+
alwaysRun: true,
|
|
47
|
+
pre(ctx) {
|
|
48
|
+
const [, db] = ctx.args;
|
|
49
|
+
if (db && db.s.topology && db.s.topology.s) {
|
|
50
|
+
try {
|
|
51
|
+
const server = db.s.topology.s.server.s;
|
|
52
|
+
if (server.pool && server.pool.state == 'connected') {
|
|
53
|
+
const connections = server.pool.availableConnections;
|
|
54
|
+
for (const c of connections) {
|
|
55
|
+
agentEmitter.emit('architectureComponent', {
|
|
56
|
+
vendor: 'MongoDB',
|
|
57
|
+
url: `mongodb://${c.host}:${c.port}`,
|
|
58
|
+
remoteHost: '',
|
|
59
|
+
remotePort: c.port
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
logger.warn('unable to report MongoDB architecture component, err: %o', err);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
post(ctx) {
|
|
72
|
+
if (!ctx.result || !ctx.result.then) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// it never gets here if callbacks are used, because the result won't be then-able
|
|
76
|
+
ctx.result.then(db => {
|
|
77
|
+
if (db && db.s && db.s.topology && db.s.topology.s) {
|
|
43
78
|
try {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
79
|
+
const server = db.s.topology.s.server.s;
|
|
80
|
+
if (server.pool && server.pool.state == 'connected') {
|
|
81
|
+
const connections = server.pool.availableConnections;
|
|
82
|
+
for (const c of connections) {
|
|
83
|
+
agentEmitter.emit('architectureComponent', {
|
|
84
|
+
vendor: 'MongoDB',
|
|
85
|
+
url: `mongodb://${c.host}:${c.port}`,
|
|
86
|
+
remoteHost: '',
|
|
87
|
+
remotePort: c.port
|
|
88
|
+
});
|
|
89
|
+
}
|
|
52
90
|
}
|
|
53
91
|
} catch (err) {
|
|
54
92
|
logger.warn(
|
|
@@ -56,60 +94,96 @@ ModuleHook.resolve(
|
|
|
56
94
|
err,
|
|
57
95
|
);
|
|
58
96
|
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Architecture component for versions >=3.3.0 <4.0.0
|
|
105
|
+
ModuleHook.resolve(
|
|
106
|
+
{
|
|
107
|
+
name: 'mongodb',
|
|
108
|
+
file: 'lib/mongo_client.js',
|
|
109
|
+
version: '>=3.0.0 <4.0.0'
|
|
110
|
+
},
|
|
111
|
+
(MongoClient, { version }) => {
|
|
112
|
+
patcher.patch(MongoClient.prototype, 'connect', {
|
|
113
|
+
name: 'MongoClient.connect.arch_component',
|
|
114
|
+
patchType: PATCH_TYPES.ARCH_COMPONENT,
|
|
115
|
+
alwaysRun: true,
|
|
116
|
+
post(ctx) {
|
|
117
|
+
if (!ctx.result || !ctx.result.then) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// We should report only when connection is successful
|
|
122
|
+
ctx.result.then(function(client) {
|
|
123
|
+
try {
|
|
124
|
+
const { servers = [] } = ctx.obj.s && ctx.obj.s.options;
|
|
125
|
+
for (const server of servers) {
|
|
126
|
+
agentEmitter.emit('architectureComponent', {
|
|
127
|
+
vendor: 'MongoDB',
|
|
128
|
+
url: `mongodb://${server.host}:${server.port}`,
|
|
129
|
+
remoteHost: '',
|
|
130
|
+
remotePort: server.port,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
} catch (err) {
|
|
134
|
+
logger.warn(
|
|
135
|
+
'unable to report MongoDB architecture component, err: %o',
|
|
136
|
+
err,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
});
|
|
63
142
|
},
|
|
64
143
|
);
|
|
65
144
|
|
|
66
|
-
|
|
67
|
-
* It's not limited in the require hook to >=4.0.0 because
|
|
68
|
-
* that would result in confusing logs for the customer that
|
|
69
|
-
* we don't support older versions (which is not true) */
|
|
145
|
+
// Architecture component for versions >=4.0.0
|
|
70
146
|
ModuleHook.resolve(
|
|
71
147
|
{
|
|
72
148
|
name: 'mongodb',
|
|
73
|
-
version: '>=
|
|
149
|
+
version: '>=4.0.0'
|
|
74
150
|
},
|
|
75
151
|
(MongoDB, { version }) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
152
|
+
patcher.patch(MongoDB.MongoClient.prototype, 'connect', {
|
|
153
|
+
name: 'MongoClient.connect.arch_component',
|
|
154
|
+
patchType: PATCH_TYPES.ARCH_COMPONENT,
|
|
155
|
+
alwaysRun: true,
|
|
156
|
+
post(ctx) {
|
|
157
|
+
if (!ctx.result || !ctx.result.then) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
85
160
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
161
|
+
// We should report only when connection is successful
|
|
162
|
+
ctx.result.then(function(client) {
|
|
163
|
+
if (client && client.topology && client.topology.s) {
|
|
164
|
+
try {
|
|
165
|
+
const { servers } = client.topology.s;
|
|
166
|
+
for (const [, server] of servers) {
|
|
167
|
+
if (server.s && server.s.state === 'connected') {
|
|
168
|
+
const { srvServiceName } = server.s.options;
|
|
169
|
+
const { address } = server.s.description;
|
|
170
|
+
agentEmitter.emit('architectureComponent', {
|
|
171
|
+
vendor: 'MongoDB',
|
|
172
|
+
url: `${srvServiceName}://${address}`,
|
|
173
|
+
remoteHost: '',
|
|
174
|
+
remotePort: address.split(':').pop()
|
|
175
|
+
});
|
|
102
176
|
}
|
|
103
|
-
} catch (err) {
|
|
104
|
-
logger.warn(
|
|
105
|
-
'unable to report MongoDB architecture component, err: %o',
|
|
106
|
-
err,
|
|
107
|
-
);
|
|
108
177
|
}
|
|
178
|
+
} catch (err) {
|
|
179
|
+
logger.warn(
|
|
180
|
+
'unable to report MongoDB architecture component, err: %o',
|
|
181
|
+
err,
|
|
182
|
+
);
|
|
109
183
|
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
});
|
|
114
188
|
},
|
|
115
189
|
);
|
|
@@ -16,7 +16,6 @@ Copyright: 2022 Contrast Security, Inc
|
|
|
16
16
|
|
|
17
17
|
const cp = require('child_process');
|
|
18
18
|
const fs = require('fs');
|
|
19
|
-
const os = require('os');
|
|
20
19
|
const path = require('path');
|
|
21
20
|
// according to https://nodejs.org/api/process.html#process_process the process
|
|
22
21
|
// global can be required. We're only doing so here so we can mock it out in tests
|
|
@@ -37,6 +36,21 @@ const SuccessConnectionState = require('./success-connection-state');
|
|
|
37
36
|
|
|
38
37
|
const RESEND_WAIT_MS = 100;
|
|
39
38
|
|
|
39
|
+
/**
|
|
40
|
+
* We might have the /bin/* files in /target directory if we're in
|
|
41
|
+
* development. Packaged/installed agents will have the executables in a
|
|
42
|
+
* top-level directory, e.g. /node_modules/@contrast/agent/bin/*
|
|
43
|
+
* @returns {'target' | '.'}
|
|
44
|
+
*/
|
|
45
|
+
const maybeTargetDir = () => {
|
|
46
|
+
try {
|
|
47
|
+
fs.statSync(path.resolve(__dirname, '..', '..', '..', 'target'));
|
|
48
|
+
return 'target';
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return '.';
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
40
54
|
class Speedracer {
|
|
41
55
|
constructor({ agent, logger }) {
|
|
42
56
|
this.agent = agent;
|
|
@@ -155,7 +169,35 @@ class Speedracer {
|
|
|
155
169
|
this.logger.info('starting contrast-service');
|
|
156
170
|
this.startTime = Date.now();
|
|
157
171
|
|
|
158
|
-
|
|
172
|
+
const speedracerPath = path.resolve(
|
|
173
|
+
__dirname,
|
|
174
|
+
'..',
|
|
175
|
+
'..',
|
|
176
|
+
'..',
|
|
177
|
+
maybeTargetDir(),
|
|
178
|
+
'bin',
|
|
179
|
+
`contrast-service-${process.platform}-${process.arch}`
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
fs.statSync(speedracerPath);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
this.logger.error(
|
|
186
|
+
'unable to locate a speedracer binary for the current platform (%s) and architecture (%s)',
|
|
187
|
+
process.platform,
|
|
188
|
+
process.arch
|
|
189
|
+
);
|
|
190
|
+
return Promise.reject();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this.serviceProcess = cp.spawn(speedracerPath, {
|
|
194
|
+
env: {
|
|
195
|
+
...process.env,
|
|
196
|
+
CONTRAST_CONFIG_PATH: this.agent.config.configFile
|
|
197
|
+
},
|
|
198
|
+
stdio: 'ignore',
|
|
199
|
+
windowsHide: true
|
|
200
|
+
});
|
|
159
201
|
|
|
160
202
|
this.serviceProcess.on('error', (err) => {
|
|
161
203
|
this.serviceProcess = null;
|
|
@@ -262,39 +304,6 @@ class Speedracer {
|
|
|
262
304
|
}
|
|
263
305
|
}
|
|
264
306
|
|
|
265
|
-
/**
|
|
266
|
-
* The start command for spawning the speedracer
|
|
267
|
-
*/
|
|
268
|
-
get startCommand() {
|
|
269
|
-
return path.resolve(
|
|
270
|
-
__dirname,
|
|
271
|
-
path.join(
|
|
272
|
-
'..',
|
|
273
|
-
'..',
|
|
274
|
-
'..',
|
|
275
|
-
utils.maybeTargetDir(),
|
|
276
|
-
'bin',
|
|
277
|
-
utils.getOsDir(),
|
|
278
|
-
'contrast-service'
|
|
279
|
-
)
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* The start options for spawning the speedracer child process. We provide the
|
|
285
|
-
* service the location of our config file via environment variable.
|
|
286
|
-
*/
|
|
287
|
-
get startOptions() {
|
|
288
|
-
return {
|
|
289
|
-
env: {
|
|
290
|
-
...process.env,
|
|
291
|
-
CONTRAST_CONFIG_PATH: this.agent.config.configFile
|
|
292
|
-
},
|
|
293
|
-
stdio: 'ignore',
|
|
294
|
-
windowsHide: true
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
307
|
/**
|
|
299
308
|
* GRPC is enabled when `agent.service.grpc` is not false, e.g., undefined
|
|
300
309
|
* defaults to enabled, and `agent.service.socket` is not configured
|
|
@@ -314,39 +323,4 @@ class Speedracer {
|
|
|
314
323
|
}
|
|
315
324
|
}
|
|
316
325
|
|
|
317
|
-
const utils = {
|
|
318
|
-
/**
|
|
319
|
-
* We might have the /bin/* files in /target directory if we're in
|
|
320
|
-
* development. Packaged/installed agents will have the executables in a
|
|
321
|
-
* top-level directory, e.g. /node_modules/@contrast/agent/bin/*
|
|
322
|
-
* @returns {String}
|
|
323
|
-
*/
|
|
324
|
-
maybeTargetDir() {
|
|
325
|
-
try {
|
|
326
|
-
fs.statSync(
|
|
327
|
-
path.resolve(__dirname, path.join('..', '..', '..', 'target'))
|
|
328
|
-
);
|
|
329
|
-
return 'target';
|
|
330
|
-
} catch (error) {
|
|
331
|
-
return '';
|
|
332
|
-
}
|
|
333
|
-
},
|
|
334
|
-
/**
|
|
335
|
-
* Map platform values to folder names based on OS. The folder names are
|
|
336
|
-
* determined by the packaging of the S-R artifacts and how they unzip.
|
|
337
|
-
* @returns {String}
|
|
338
|
-
*/
|
|
339
|
-
getOsDir() {
|
|
340
|
-
switch (os.platform()) {
|
|
341
|
-
case 'linux':
|
|
342
|
-
return 'linux';
|
|
343
|
-
case 'darwin':
|
|
344
|
-
return 'mac';
|
|
345
|
-
case 'win32':
|
|
346
|
-
return 'windows';
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
};
|
|
350
|
-
|
|
351
326
|
module.exports = Speedracer;
|
|
352
|
-
module.exports.utils = utils;
|
|
@@ -29,15 +29,19 @@ module.exports = function Finding(details = {}) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const routes = RoutesListWithObservations(details.routes);
|
|
32
|
+
const properties = objToMap(details.properties).map(
|
|
33
|
+
// ensure string values
|
|
34
|
+
([key, value]) => [key, String(value)]
|
|
35
|
+
);
|
|
32
36
|
|
|
33
37
|
return new dtm.Finding({
|
|
34
|
-
0: String(details.hash || ''), //
|
|
35
|
-
2: details.ruleId, //
|
|
36
|
-
5:
|
|
37
|
-
6: mapToModelArray(TraceEvent, details.events), //
|
|
38
|
-
7: preflight, //
|
|
39
|
-
8: details.tags, //
|
|
40
|
-
9: details.version, //
|
|
38
|
+
0: String(details.hash || ''), // 1 hash_code
|
|
39
|
+
2: details.ruleId, // 3 rule_id
|
|
40
|
+
5: properties, // 6 properties
|
|
41
|
+
6: mapToModelArray(TraceEvent, details.events), // 7 events
|
|
42
|
+
7: preflight, // 8 preflight
|
|
43
|
+
8: details.tags, // 9 tags
|
|
44
|
+
9: details.version, // 10 version
|
|
41
45
|
10: routes // 11 routes
|
|
42
46
|
});
|
|
43
47
|
};
|
package/lib/util/heap-dump.js
CHANGED
|
@@ -12,51 +12,63 @@ Copyright: 2022 Contrast Security, Inc
|
|
|
12
12
|
engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
13
|
way not consistent with the End User License Agreement.
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
16
17
|
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const v8 = require('v8');
|
|
17
20
|
const logger = require('../core/logger')('contrast:heapDump');
|
|
18
|
-
const heapUtil = module.exports;
|
|
19
|
-
let heapdump;
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
|
-
*
|
|
23
|
+
* @param {string} localPath location from config.path
|
|
24
|
+
*/
|
|
25
|
+
const writeHeapSnapshot = function writeHeapSnapshot(localPath) {
|
|
26
|
+
const dumpPath = path.join(process.cwd(), localPath);
|
|
27
|
+
|
|
28
|
+
fs.mkdir(dumpPath, { recursive: true }, (err) => {
|
|
29
|
+
if (err) {
|
|
30
|
+
logger.error('Unable to create directory for heap snapshots: %o', err);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// create dump at ${path}/${time}.heapdump
|
|
35
|
+
const filename = path.format({
|
|
36
|
+
dir: dumpPath,
|
|
37
|
+
name: `${Date.now()}-contrast`,
|
|
38
|
+
ext: '.heapsnapshot',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
logger.info('Writing heap snapshot at %s', filename);
|
|
42
|
+
v8.writeHeapSnapshot(filename);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* setup x heap dumps to be created every y seconds after z seconds.
|
|
23
48
|
* @param {Object} config config blob from config/options.js
|
|
24
|
-
*
|
|
49
|
+
* @param {boolean} config.enable
|
|
50
|
+
* @param {string} config.path
|
|
51
|
+
* @param {number} config.count x
|
|
52
|
+
* @param {number} config.window_ms y
|
|
53
|
+
* @param {number} config.delay_ms z
|
|
25
54
|
*/
|
|
26
|
-
|
|
55
|
+
const init = function init(config) {
|
|
27
56
|
if (!config.enable) return;
|
|
28
|
-
// NODE-1200: make this optional based on if config
|
|
29
|
-
// bit is flipped
|
|
30
|
-
heapdump = require('@contrast/heapdump');
|
|
31
57
|
|
|
32
58
|
setTimeout(() => {
|
|
33
59
|
let count = 0;
|
|
34
|
-
|
|
60
|
+
|
|
61
|
+
const interval = setInterval(() => {
|
|
35
62
|
if (count >= config.count) {
|
|
36
|
-
clearInterval(
|
|
63
|
+
clearInterval(interval);
|
|
37
64
|
return;
|
|
38
65
|
}
|
|
39
|
-
|
|
66
|
+
|
|
67
|
+
writeHeapSnapshot(config.path);
|
|
40
68
|
|
|
41
69
|
count++;
|
|
42
70
|
}, config.window_ms);
|
|
43
71
|
}, config.delay_ms).unref();
|
|
44
72
|
};
|
|
45
73
|
|
|
46
|
-
|
|
47
|
-
// create directory in cwd
|
|
48
|
-
dumpPath = `${path.join(process.cwd(), dumpPath)}`;
|
|
49
|
-
fs.mkdir(dumpPath, (err) => {
|
|
50
|
-
if (!err || (err && err.code == 'EEXIST')) {
|
|
51
|
-
// create dump at ${path}/${time}.heapdump
|
|
52
|
-
const fileName = `${path.join(
|
|
53
|
-
dumpPath,
|
|
54
|
-
String(Date.now())
|
|
55
|
-
)}-contrast.heapsnapshot`;
|
|
56
|
-
logger.info(`Building heap snapshot at ${fileName}`);
|
|
57
|
-
heapdump.writeSnapshot(fileName);
|
|
58
|
-
} else {
|
|
59
|
-
logger.error(`Unable to create directory for heap snapshots: ${err}`);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
};
|
|
74
|
+
module.exports = { writeHeapSnapshot, init };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/agent",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.23.0",
|
|
4
4
|
"description": "Node.js security instrumentation by Contrast Security",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"security",
|
|
@@ -76,11 +76,10 @@
|
|
|
76
76
|
"@babel/template": "^7.10.4",
|
|
77
77
|
"@babel/traverse": "^7.12.1",
|
|
78
78
|
"@babel/types": "^7.12.1",
|
|
79
|
-
"@contrast/agent-lib": "^4.
|
|
79
|
+
"@contrast/agent-lib": "^4.3.0",
|
|
80
80
|
"@contrast/distringuish-prebuilt": "^3.2.0",
|
|
81
81
|
"@contrast/flat": "^4.1.1",
|
|
82
|
-
"@contrast/fn-inspect": "^3.
|
|
83
|
-
"@contrast/heapdump": "^1.1.0",
|
|
82
|
+
"@contrast/fn-inspect": "^3.1.0",
|
|
84
83
|
"@contrast/protobuf-api": "^3.2.5",
|
|
85
84
|
"@contrast/require-hook": "^3.2.1",
|
|
86
85
|
"@contrast/synchronous-source-maps": "^1.1.0",
|
|
@@ -164,7 +163,7 @@
|
|
|
164
163
|
"mock-fs": "^5.1.2",
|
|
165
164
|
"mongodb": "file:test/mock/mongodb",
|
|
166
165
|
"mongodb-npm": "npm:mongodb@^3.6.5",
|
|
167
|
-
"mongoose": "^6.
|
|
166
|
+
"mongoose": "^6.4.6",
|
|
168
167
|
"mustache": "^3.0.1",
|
|
169
168
|
"mysql": "file:test/mock/mysql",
|
|
170
169
|
"mysql2": "file:test/mock/mysql2",
|