@contrast/agentify 1.3.0 → 1.4.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.
@@ -15,7 +15,7 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- module.exports = function(core) {
18
+ module.exports = function (core) {
19
19
  const { logger, patcher } = core;
20
20
 
21
21
  const getOrig = (v) => patcher.unwrap(v);
@@ -28,70 +28,123 @@ module.exports = function(core) {
28
28
  * 2) patch those functions on `contrastMethods.api` in order to add instrumentation
29
29
  */
30
30
  const contrastMethods = {
31
- // TODO: Assess will require add'l methods
32
- // TODO: ESM will require add'l methods
33
31
  api: {
34
32
  eval(v) {
35
33
  return v;
36
34
  },
37
- plus(left, right) {
35
+
36
+ // Binary Operators
37
+ add(left, right) {
38
38
  const ret = getOrig(left) + getOrig(right);
39
39
  return ret;
40
40
  },
41
- doubleEqual(left, right) {
41
+ eqEq(left, right) {
42
42
  left = getOrig(left);
43
43
  right = getOrig(right);
44
44
 
45
45
  return left == right;
46
46
  },
47
- notTripleEqual(left, right) {
47
+ eqEqEq(left, right) {
48
48
  left = getOrig(left);
49
49
  right = getOrig(right);
50
50
 
51
- return left !== right;
51
+ return left === right;
52
52
  },
53
- tripleEqual(left, right) {
53
+ notEqEq(left, right) {
54
54
  left = getOrig(left);
55
55
  right = getOrig(right);
56
56
 
57
- return left === right;
57
+ return left !== right;
58
58
  },
59
- notDoubleEqual(left, right) {
59
+ notEq(left, right) {
60
60
  left = getOrig(left);
61
61
  right = getOrig(right);
62
62
 
63
63
  return left != right;
64
64
  },
65
+
66
+ // Computed Properties
67
+ // Used to force a copy of handle:
68
+ // https://stackoverflow.com/a/31733628
69
+ // This is a workaround for CONTRAST-30333, in which something we
70
+ // suspect is a v8 bug means that handle will otherwise be clobbered
71
+ // and if it was externalized, we will lose the data associated with it
65
72
  forceCopy(handle) {
66
- // Used to force a copy of handle:
67
- // https://stackoverflow.com/a/31733628
68
- // This is a workaround for CONTRAST-30333, in which something we
69
- // suspect is a v8 bug means that handle will otherwise be clobbered
70
- // and if it was externalized, we will lose the data associated with it
71
73
  if (typeof handle === 'string') {
72
74
  handle = toUntrackedString(handle);
73
75
  }
74
76
  return handle;
75
77
  },
76
- cast: function __cast(val) {
78
+
79
+ // Switch Case
80
+ cast(val) {
77
81
  return getOrig(val);
78
82
  },
79
- tag: function __contrastTag(strings) {
80
- // XXX implemented in lib/assess/propagators/templates.js
83
+
84
+ /**
85
+ * Tagged Templates
86
+ * The first argument received by the tag function is an array of strings.
87
+ * For any template literal, its length is equal to the number of
88
+ * substitutions (occurrences of ${...}) plus one, and is therefore always
89
+ * non-empty.
90
+ * see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates
91
+ * @param {string[]} strings length n+1
92
+ * @param {string[]} args length n
93
+ * @returns {string}
94
+ */
95
+ tag(strings, ...args) {
96
+ const [first, ...rest] = strings;
97
+
98
+ return rest.reduce(
99
+ (result, str, i) => result.concat(args[i], str),
100
+ first,
101
+ );
102
+ },
103
+
104
+ // Import Statements
105
+ importDefault(...args) {
106
+ // noop
81
107
  },
82
108
 
109
+ importStarAs(...args) {
110
+ // noop
111
+ },
112
+
113
+ importNamed(...args) {
114
+ // noop
115
+ },
116
+
117
+ // Injections
83
118
  Function: core.patcher.patch(global.Function, {
84
119
  name: 'global.Function',
85
- patchType: 'rewrite-injection'
120
+ patchType: 'rewrite-injection',
121
+ }),
122
+ JSON: core.patcher.patch(global.JSON, {
123
+ name: 'global.JSON',
124
+ patchType: 'rewrite-injection',
125
+ }),
126
+ Number: core.patcher.patch(global.Number, {
127
+ name: 'global.Number',
128
+ patchType: 'rewrite-injection',
129
+ }),
130
+ Object: core.patcher.patch(global.Object, {
131
+ name: 'global.Object',
132
+ patchType: 'rewrite-injection',
133
+ }),
134
+ String: core.patcher.patch(global.String, {
135
+ name: 'global.String',
136
+ patchType: 'rewrite-injection',
86
137
  }),
87
138
  },
139
+
88
140
  installed: false,
141
+
89
142
  getGlobal() {
90
143
  return global;
91
- }
144
+ },
92
145
  };
93
146
 
94
- contrastMethods.install = function() {
147
+ contrastMethods.install = function () {
95
148
  if (contrastMethods.installed) {
96
149
  return;
97
150
  }
@@ -102,15 +155,15 @@ module.exports = function(core) {
102
155
  Object.defineProperty(global, 'ContrastMethods', {
103
156
  enumerable: true,
104
157
  configurable: false,
105
- value: contrastMethods.api
158
+ value: contrastMethods.api,
106
159
  });
107
160
  contrastMethods.installed = true;
108
161
  } catch (err) {
109
162
  // We should never expect this since the installation process is well
110
163
  // controlled, but still we should have the defensive code.
111
- logger.error({ err }, 'Unable to install global.ContrastMethods');
164
+ logger.error({ err }, 'Unable to inject global.ContrastMethods');
112
165
  }
113
166
  };
114
167
 
115
- return core.contrastMethods = contrastMethods;
168
+ return (core.contrastMethods = contrastMethods);
116
169
  };
@@ -81,7 +81,7 @@ module.exports = function (deps) {
81
81
  unwritten = rewriter.unwrite(unwritten);
82
82
  unwritten = unwritten.replace(METHOD_CONTEXT, '');
83
83
  unwritten = unwritten.replace(FUNCTION_CONTEXT, '');
84
- unwritten = unwritten.replace(/;$/, ''); // removes trailing semicolon
84
+ unwritten = unwritten.replace(/;\s*$/, ''); // removes trailing semicolon/whitespace
85
85
  return unwritten;
86
86
  } catch (err) {
87
87
  logger.warn({ err, code }, 'Failed to unwrite function code');
package/lib/index.js CHANGED
@@ -18,7 +18,6 @@
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
20
  const Module = require('module');
21
- const { env } = require('process');
22
21
 
23
22
  const defaultOpts = {
24
23
  install: true,
@@ -32,8 +31,10 @@ const defaultOpts = {
32
31
  'assess',
33
32
  'protect',
34
33
  'depHooks',
34
+ 'routeCoverage',
35
+ 'libraryAnalysis',
35
36
  'rewriteHooks',
36
- 'functionHooks',
37
+ 'functionHooks'
37
38
  ]
38
39
  };
39
40
 
@@ -106,7 +107,7 @@ class Agent {
106
107
  await self.handleInstallFailure(err);
107
108
  }
108
109
 
109
- self.logEffectiveConfig();
110
+ self.logDiagnosticFiles();
110
111
  return self.runMain.apply(this, args);
111
112
  };
112
113
  }
@@ -135,24 +136,35 @@ class Agent {
135
136
  }
136
137
  }
137
138
 
138
- logEffectiveConfig() {
139
- const { config, getEffectiveConfig } = this.core;
139
+ logDiagnosticFiles() {
140
+ const { config, getEffectiveConfig, getSystemInfo } = this.core;
140
141
 
141
- if (env.CONTRAST__AGENT__DIAGNOSTICS__ENABLE !== 'false') {
142
- const content = JSON.stringify(getEffectiveConfig(), null, 2).concat('\n\n');
142
+ if (config.agent.diagnostics.enable !== false) {
143
+ const effectiveConfig = JSON.stringify(getEffectiveConfig(), null, 2).concat('\n\n');
144
+ const systemInfo = JSON.stringify(getSystemInfo(), null, 2).concat('\n\n');
143
145
 
144
- if (env.CONTRAST__AGENT__DIAGNOSTICS__QUIET !== 'true') {
145
- fs.writeFileSync(1, content, 'utf8');
146
+ if (!config.agent.diagnostics.quiet) {
147
+ fs.writeFileSync(1, effectiveConfig, 'utf8');
148
+ fs.writeFileSync(1, systemInfo, 'utf8');
146
149
  }
147
150
 
148
- let outputDir = env.CONTRAST__AGENT__DIAGNOSTICS__REPORT_PATH;
151
+ let outputDir = config.agent.diagnostics.report_path;
149
152
  if (!outputDir && config.agent.logger.path) {
150
- outputDir = path.join(config.agent.logger.path, '../contrast_effective_config.json');
151
- } else {
152
- outputDir = path.join(__dirname, 'contrast_effective_config.json');
153
+ outputDir = path.join(config.agent.logger.path, '..');
153
154
  }
154
155
 
155
- fs.writeFileSync(outputDir, content, 'utf-8');
156
+ try {
157
+ fs.writeFileSync(path.join(outputDir, 'contrast_effective_config.json'), effectiveConfig, 'utf-8');
158
+ fs.writeFileSync(path.join(outputDir, 'contrast_system_info.json'), systemInfo, 'utf-8');
159
+ } catch (err) {
160
+ outputDir = path.join(process.cwd());
161
+ try {
162
+ fs.writeFileSync(path.join(outputDir, 'contrast_effective_config.json'), effectiveConfig, 'utf-8');
163
+ fs.writeFileSync(path.join(outputDir, 'contrast_system_info.json'), systemInfo, 'utf-8');
164
+ } catch (err) {
165
+ fs.writeFileSync(1, `Couldn't create the diagnostic files: ${err}`, 'utf-8');
166
+ }
167
+ }
156
168
  }
157
169
  }
158
170
  }
@@ -27,7 +27,12 @@ module.exports = function(deps) {
27
27
 
28
28
  Module.prototype._compile = function(content, filename) {
29
29
  let compiled;
30
- const { code } = deps.rewriter.rewrite(content, { filename, inject: true, wrap: true });
30
+ const { code } = deps.rewriter.rewrite(content, {
31
+ filename,
32
+ isModule: false,
33
+ inject: true,
34
+ wrap: true,
35
+ });
31
36
 
32
37
  try {
33
38
  compiled = origCompile.call(this, code, filename);
package/lib/sources.js CHANGED
@@ -14,73 +14,66 @@
14
14
  */
15
15
 
16
16
  'use strict';
17
+ const patchType = 'http-sources';
17
18
 
18
19
  module.exports = function(core) {
19
- const { depHooks, patcher, logger, scopes: { sources: sourcesStorage } } = core;
20
- const sources = core.sources = {};
20
+ const {
21
+ instrumentation: { instrument },
22
+ scopes: { sources: sourcesStorage }
23
+ } = core;
21
24
 
22
- sources.install = function install() {
23
- depHooks.resolve({ name: 'http' }, http => patchServerEmit(http, 'httpServer'));
24
- depHooks.resolve({ name: 'https' }, https => patchServerEmit(https, 'httpsServer'));
25
- depHooks.resolve({ name: 'http2' }, http2 => {
26
- patchCreateServer(http2, 'http2Server');
27
- patchCreateServer(http2, 'http2SecureServer', 'createSecureServer');
28
- });
29
- depHooks.resolve({ name: 'spdy' }, spdy => patchServerEmit(spdy, 'spdyServer'));
30
- };
31
-
32
- function patchServerEmit(serverSource, sourceName) {
33
- serverSource.Server.prototype = patcher.patch(serverSource.Server.prototype, 'emit', {
34
- name: `${sourceName}.Server.prototype.emit`,
35
- patchType: 'http-sources',
36
- around: emitAroundHook(sourceName)
37
- });
38
- }
39
-
40
- function patchCreateServer(serverSource, sourceName, constructorName = 'createServer') {
41
- patcher.patch(serverSource, constructorName, {
42
- name: sourceName,
43
- patchType: 'http-sources',
44
- post: createServerPostHook(sourceName)
45
- });
46
- }
47
-
48
- function createServerPostHook(serverType) {
49
- return function(data) {
50
- const { result: server } = data;
51
- const serverPrototype = server ? Object.getPrototypeOf(server) : null;
52
-
53
- if (!serverPrototype) {
54
- logger.error('Unable to patch server prototype, continue without instrumentation');
55
- return;
56
- }
57
-
58
- patcher.patch(serverPrototype, 'emit', {
59
- name: 'Server.prototype.emit',
60
- patchType: 'req-async-storage',
61
- around: emitAroundHook(serverType)
62
- });
63
- };
64
- }
65
-
66
- function emitAroundHook(serverType) {
67
- return function emitAroundHook(next, data) {
25
+ function aroundHook(serverType) {
26
+ return function around(next, data) {
68
27
  const { args: [event] } = data;
69
-
70
28
  if (event !== 'request') return next();
71
-
72
29
  const store = { serverType };
73
-
74
30
  return sourcesStorage.run(store, next);
75
31
  };
76
32
  }
77
33
 
78
- return {
79
- install: sources.install,
80
- functions: {
81
- patchCreateServer,
82
- createServerPostHook,
83
- emitAroundHook
84
- }
34
+ function install() {
35
+ [{
36
+ moduleName: 'http'
37
+ },
38
+ {
39
+ moduleName: 'https'
40
+ },
41
+ {
42
+ moduleName: 'spdy'
43
+ },
44
+ {
45
+ moduleName: 'http2',
46
+ patchObjectsProps: [
47
+ {
48
+ methods: ['createServer', 'createSecureServer'],
49
+ patchType,
50
+ patchObjects: [
51
+ {
52
+ name: 'Server.prototype',
53
+ methods: ['emit'],
54
+ patchType,
55
+ around: aroundHook('http2')
56
+ }
57
+ ]
58
+ }
59
+ ]
60
+ }].forEach(({ moduleName, patchObjectsProps }) => {
61
+ const patchObjects = patchObjectsProps || [
62
+ {
63
+ name: 'Server.prototype',
64
+ methods: ['emit'],
65
+ patchType,
66
+ around: aroundHook(moduleName)
67
+ }
68
+ ];
69
+ instrument({
70
+ moduleName,
71
+ patchObjects
72
+ });
73
+ });
74
+ }
75
+ return core.sources = {
76
+ install,
77
+ aroundHook
85
78
  };
86
79
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/agentify",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Configures Contrast agent services and instrumentation within an application",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -16,4 +16,4 @@
16
16
  "scripts": {
17
17
  "test": "../scripts/test.sh"
18
18
  }
19
- }
19
+ }