@contrast/agent 4.5.2 → 4.6.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.
Files changed (121) hide show
  1. package/lib/assess/policy/propagators.json +20 -0
  2. package/lib/assess/policy/signatures.json +10 -0
  3. package/lib/core/config/options.js +3 -2
  4. package/lib/core/stacktrace.js +2 -1
  5. package/lib/hooks/frameworks/base.js +8 -2
  6. package/lib/hooks/frameworks/http.js +23 -16
  7. package/lib/hooks/frameworks/http2.js +73 -0
  8. package/lib/hooks/frameworks/index.js +8 -3
  9. package/lib/hooks/http.js +112 -128
  10. package/lib/hooks/require.js +16 -22
  11. package/lib/instrumentation.js +0 -3
  12. package/package.json +12 -15
  13. package/lib/hooks/frameworks/https.js +0 -42
  14. package/node_modules/bindings/LICENSE.md +0 -22
  15. package/node_modules/bindings/README.md +0 -98
  16. package/node_modules/bindings/bindings.js +0 -221
  17. package/node_modules/bindings/package.json +0 -32
  18. package/node_modules/file-uri-to-path/.npmignore +0 -1
  19. package/node_modules/file-uri-to-path/.travis.yml +0 -30
  20. package/node_modules/file-uri-to-path/History.md +0 -21
  21. package/node_modules/file-uri-to-path/LICENSE +0 -20
  22. package/node_modules/file-uri-to-path/README.md +0 -74
  23. package/node_modules/file-uri-to-path/index.d.ts +0 -2
  24. package/node_modules/file-uri-to-path/index.js +0 -66
  25. package/node_modules/file-uri-to-path/package.json +0 -36
  26. package/node_modules/file-uri-to-path/test/test.js +0 -24
  27. package/node_modules/file-uri-to-path/test/tests.json +0 -13
  28. package/node_modules/glossy/LICENSE +0 -19
  29. package/node_modules/glossy/README.md +0 -129
  30. package/node_modules/glossy/index.js +0 -12
  31. package/node_modules/glossy/lib/glossy/parse.js +0 -520
  32. package/node_modules/glossy/lib/glossy/produce.js +0 -459
  33. package/node_modules/glossy/package.json +0 -47
  34. package/node_modules/glossy/test/decide.js +0 -7
  35. package/node_modules/glossy/test/decode_pri.js +0 -24
  36. package/node_modules/glossy/test/parse_3164.js +0 -104
  37. package/node_modules/glossy/test/parse_5424.js +0 -106
  38. package/node_modules/glossy/test/parse_5848.js +0 -40
  39. package/node_modules/glossy/test/parse_8601.js +0 -14
  40. package/node_modules/glossy/test/parse_rfc3339.js +0 -9
  41. package/node_modules/glossy/test/produce.js +0 -162
  42. package/node_modules/glossy/test/runner.js +0 -40
  43. package/node_modules/glossy/test/structure_data.js +0 -24
  44. package/node_modules/nan/CHANGELOG.md +0 -537
  45. package/node_modules/nan/LICENSE.md +0 -13
  46. package/node_modules/nan/README.md +0 -455
  47. package/node_modules/nan/doc/asyncworker.md +0 -146
  48. package/node_modules/nan/doc/buffers.md +0 -54
  49. package/node_modules/nan/doc/callback.md +0 -76
  50. package/node_modules/nan/doc/converters.md +0 -41
  51. package/node_modules/nan/doc/errors.md +0 -226
  52. package/node_modules/nan/doc/json.md +0 -62
  53. package/node_modules/nan/doc/maybe_types.md +0 -583
  54. package/node_modules/nan/doc/methods.md +0 -664
  55. package/node_modules/nan/doc/new.md +0 -147
  56. package/node_modules/nan/doc/node_misc.md +0 -123
  57. package/node_modules/nan/doc/object_wrappers.md +0 -263
  58. package/node_modules/nan/doc/persistent.md +0 -296
  59. package/node_modules/nan/doc/scopes.md +0 -73
  60. package/node_modules/nan/doc/script.md +0 -38
  61. package/node_modules/nan/doc/string_bytes.md +0 -62
  62. package/node_modules/nan/doc/v8_internals.md +0 -199
  63. package/node_modules/nan/doc/v8_misc.md +0 -85
  64. package/node_modules/nan/include_dirs.js +0 -1
  65. package/node_modules/nan/nan.h +0 -2898
  66. package/node_modules/nan/nan_callbacks.h +0 -88
  67. package/node_modules/nan/nan_callbacks_12_inl.h +0 -514
  68. package/node_modules/nan/nan_callbacks_pre_12_inl.h +0 -520
  69. package/node_modules/nan/nan_converters.h +0 -72
  70. package/node_modules/nan/nan_converters_43_inl.h +0 -68
  71. package/node_modules/nan/nan_converters_pre_43_inl.h +0 -42
  72. package/node_modules/nan/nan_define_own_property_helper.h +0 -29
  73. package/node_modules/nan/nan_implementation_12_inl.h +0 -430
  74. package/node_modules/nan/nan_implementation_pre_12_inl.h +0 -263
  75. package/node_modules/nan/nan_json.h +0 -166
  76. package/node_modules/nan/nan_maybe_43_inl.h +0 -356
  77. package/node_modules/nan/nan_maybe_pre_43_inl.h +0 -268
  78. package/node_modules/nan/nan_new.h +0 -340
  79. package/node_modules/nan/nan_object_wrap.h +0 -156
  80. package/node_modules/nan/nan_persistent_12_inl.h +0 -132
  81. package/node_modules/nan/nan_persistent_pre_12_inl.h +0 -242
  82. package/node_modules/nan/nan_private.h +0 -73
  83. package/node_modules/nan/nan_string_bytes.h +0 -305
  84. package/node_modules/nan/nan_typedarray_contents.h +0 -96
  85. package/node_modules/nan/nan_weak.h +0 -437
  86. package/node_modules/nan/package.json +0 -41
  87. package/node_modules/nan/tools/1to2.js +0 -412
  88. package/node_modules/nan/tools/README.md +0 -14
  89. package/node_modules/nan/tools/package.json +0 -19
  90. package/node_modules/unix-dgram/LICENSE +0 -13
  91. package/node_modules/unix-dgram/README.md +0 -107
  92. package/node_modules/unix-dgram/binding.gyp +0 -20
  93. package/node_modules/unix-dgram/build/Makefile +0 -324
  94. package/node_modules/unix-dgram/build/Release/.deps/Release/obj.target/unix_dgram/src/unix_dgram.o.d +0 -58
  95. package/node_modules/unix-dgram/build/Release/.deps/Release/obj.target/unix_dgram.node.d +0 -1
  96. package/node_modules/unix-dgram/build/Release/.deps/Release/unix_dgram.node.d +0 -1
  97. package/node_modules/unix-dgram/build/Release/obj.target/unix_dgram/src/unix_dgram.o +0 -0
  98. package/node_modules/unix-dgram/build/Release/obj.target/unix_dgram.node +0 -0
  99. package/node_modules/unix-dgram/build/Release/unix_dgram.node +0 -0
  100. package/node_modules/unix-dgram/build/binding.Makefile +0 -6
  101. package/node_modules/unix-dgram/build/config.gypi +0 -213
  102. package/node_modules/unix-dgram/build/unix_dgram.target.mk +0 -159
  103. package/node_modules/unix-dgram/lib/unix_dgram.js +0 -168
  104. package/node_modules/unix-dgram/package.json +0 -36
  105. package/node_modules/unix-dgram/src/unix_dgram.cc +0 -404
  106. package/node_modules/unix-dgram/src/win_dummy.cc +0 -7
  107. package/node_modules/unix-dgram/test/test-connect-callback.js +0 -68
  108. package/node_modules/unix-dgram/test/test-connect.js +0 -53
  109. package/node_modules/unix-dgram/test/test-dgram-unix.js +0 -58
  110. package/node_modules/unix-dgram/test/test-send-error.js +0 -26
  111. package/node_modules/winston-syslog/.eslintrc +0 -7
  112. package/node_modules/winston-syslog/.travis.yml +0 -14
  113. package/node_modules/winston-syslog/CHANGELOG.md +0 -9
  114. package/node_modules/winston-syslog/LICENSE +0 -20
  115. package/node_modules/winston-syslog/README.md +0 -135
  116. package/node_modules/winston-syslog/lib/utils.js +0 -26
  117. package/node_modules/winston-syslog/lib/winston-syslog.js +0 -385
  118. package/node_modules/winston-syslog/package.json +0 -56
  119. package/node_modules/winston-syslog/test/format-test.js +0 -122
  120. package/node_modules/winston-syslog/test/syslog-test.js +0 -95
  121. package/node_modules/winston-syslog/test/unix-connect-test.js +0 -133
@@ -67,6 +67,26 @@
67
67
  "type": "keep"
68
68
  }
69
69
  },
70
+ "dust.escapeHtml": {
71
+ "enabled": true,
72
+ "source": "P",
73
+ "target": "R",
74
+ "tags": ["html-encoded"],
75
+ "type": "overload",
76
+ "command": {
77
+ "type": "keep"
78
+ }
79
+ },
80
+ "dust.escapeJs": {
81
+ "enabled": true,
82
+ "source": "P",
83
+ "target": "R",
84
+ "tags": ["javascript-encoded"],
85
+ "type": "overload",
86
+ "command": {
87
+ "type": "keep"
88
+ }
89
+ },
70
90
  "pug.compile": {
71
91
  "enabled": true,
72
92
  "provider": "./propagators/pug-compile.js"
@@ -233,6 +233,16 @@
233
233
  "methodName": "escape",
234
234
  "isModule": true
235
235
  },
236
+ "dust.escapeHtml": {
237
+ "moduleName": "dustjs-linkedin",
238
+ "methodName": "escapeHtml",
239
+ "isModule": true
240
+ },
241
+ "dust.escapeJs": {
242
+ "moduleName": "dustjs-linkedin",
243
+ "methodName": "escapeJs",
244
+ "isModule": true
245
+ },
236
246
  "express.response.send": {
237
247
  "moduleName": "express",
238
248
  "version": ">=4.0.0",
@@ -481,9 +481,10 @@ const agent = [
481
481
  {
482
482
  name: 'agent.stack_trace_limit',
483
483
  arg: '<limit>',
484
- default: 10,
484
+ default: 25,
485
485
  fn: parseNum,
486
- desc: 'set limit for stack trace size'
486
+ desc:
487
+ 'set limit for stack trace size (larger limits will improve accuracy but increase memory usage)'
487
488
  },
488
489
  {
489
490
  name: 'agent.polling.app_activity_ms',
@@ -15,11 +15,12 @@ Copyright: 2021 Contrast Security, Inc
15
15
  'use strict';
16
16
 
17
17
  const semver = require('semver');
18
+ const agent = require('../agent');
18
19
  const process = require('process');
19
20
  const sourceMap = require('../util/source-map');
20
21
  const _isAgentPath = require('../util/is-agent-path');
21
22
 
22
- const STACK_TRACE_LIMIT = 25;
23
+ const STACK_TRACE_LIMIT = agent.config.agent.stack_trace_limit;
23
24
  const EVAL_ORIGIN_REGEX = /\((.*?):(\d+):\d+\)/;
24
25
  const EVENTS_FILE = semver.gte(process.version, '16.0.0')
25
26
  ? 'node:events'
@@ -22,8 +22,14 @@ const RequestFactory = require('../../reporter/models/utils/request-factory');
22
22
  const CleanStack = require('../../util/clean-stack');
23
23
  const agentEmitter = require('../../agent-emitter');
24
24
 
25
+ /** @typedef {import('../../agent').ContrastAgent} Agent */
26
+
25
27
  class BaseFramework {
26
- // sets options
28
+ /**
29
+ * @param {Object} opts
30
+ * @param {string} opts.id
31
+ * @param {Agent} opts.agent
32
+ */
27
33
  constructor({ id, agent } = {}) {
28
34
  this.id = id;
29
35
  this.agent = agent;
@@ -46,7 +52,7 @@ class BaseFramework {
46
52
  * -- Note --
47
53
  * The current imlementation uses a CleanStack instance in order to generate
48
54
  * a stackframe from which to obtain the information. This can be expensive.
49
- * @returns {String}
55
+ * @returns {string}
50
56
  */
51
57
  getAppCodeLocation() {
52
58
  const limit = Error.stackTraceLimit;
@@ -19,39 +19,41 @@ const moduleHook = require('../require');
19
19
  const patcher = require('../patcher');
20
20
  const { HTTP_EVENTS, PATCH_TYPES } = require('../../constants');
21
21
 
22
- class HttpFramework extends BaseFramework {
23
- constructor(agent, id = 'http') {
24
- super({ id, agent });
25
-
26
- this.init();
27
- }
22
+ /** @typedef {import('../../agent').ContrastAgent} Agent */
23
+ /** @typedef {import('net').Server} Server */
28
24
 
25
+ class HttpFramework extends BaseFramework {
29
26
  /**
30
- * Initialization logic.
27
+ * @param {Agent} agent
28
+ * @param {string} id
31
29
  */
32
- init() {
30
+ constructor(agent, id = 'http') {
31
+ super({ agent, id });
33
32
  moduleHook.resolve({ name: this.id }, this.onRequire.bind(this));
34
33
  }
35
34
 
35
+ /**
36
+ * @template {import('http') | import('https')} T
37
+ * @param {T} xport
38
+ * @returns {T}
39
+ */
36
40
  onRequire(xport) {
37
- const { id } = this;
38
-
39
41
  patcher.patch(xport, 'Server', {
40
- name: `${id}.Server`,
42
+ name: `${this.id}.Server`,
41
43
  patchType: PATCH_TYPES.FRAMEWORK,
42
44
  alwaysRun: true,
43
45
  post: (fnData) => this.handleServerCreate(fnData.args, fnData.result)
44
46
  });
45
47
 
46
48
  patcher.patch(xport, 'createServer', {
47
- name: `${id}.createServer`,
49
+ name: `${this.id}.createServer`,
48
50
  patchType: PATCH_TYPES.FRAMEWORK,
49
51
  alwaysRun: true,
50
52
  post: (fnData) => this.handleServerCreate(fnData.args, fnData.result)
51
53
  });
52
54
 
53
55
  patcher.patch(xport.Server.prototype, 'listen', {
54
- name: `${id}.Server.prototype`,
56
+ name: `${this.id}.Server.prototype`,
55
57
  patchType: PATCH_TYPES.FRAMEWORK,
56
58
  alwaysRun: true,
57
59
  pre: (fnData) => this.handleServerListen(fnData.args, fnData.obj)
@@ -62,6 +64,7 @@ class HttpFramework extends BaseFramework {
62
64
 
63
65
  /**
64
66
  * Emits a listen event if one has not been pubished for the server instance.
67
+ * @param {any[]}
65
68
  * @param {Server} server Server on which `listen` was called
66
69
  */
67
70
  handleServerListen(args, server) {
@@ -70,12 +73,16 @@ class HttpFramework extends BaseFramework {
70
73
 
71
74
  /**
72
75
  * Emits a create event for the new Server instance.
73
- * @param {*[]} args The arguments passed to the Server constructor
76
+ * @param {any[]} args The arguments passed to the Server constructor
74
77
  * @param {Server} server The http Server instance
75
78
  */
76
79
  handleServerCreate(args, server) {
77
- const [callback] = args;
78
- this.emitter.emit(HTTP_EVENTS.SERVER_CREATE, callback, server);
80
+ const [options] = args;
81
+ let [, handler] = args;
82
+ if (typeof options === 'function') {
83
+ handler = options;
84
+ }
85
+ this.emitter.emit(HTTP_EVENTS.SERVER_CREATE, handler, server);
79
86
  }
80
87
  }
81
88
 
@@ -0,0 +1,73 @@
1
+ /**
2
+ Copyright: 2021 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 { PATCH_TYPES } = require('../../constants');
18
+ const patcher = require('../patcher');
19
+ const HttpFramework = require('./http');
20
+
21
+ /** @typedef {import('../../agent').ContrastAgent} Agent */
22
+
23
+ class Http2Framework extends HttpFramework {
24
+ /**
25
+ * @param {Agent} agent
26
+ */
27
+ constructor(agent) {
28
+ super(agent, 'http2');
29
+ }
30
+
31
+ /**
32
+ * @param {import('http2')} http2
33
+ * @returns {import('http2')}
34
+ */
35
+ onRequire(http2) {
36
+ patcher.patch(http2, 'createServer', {
37
+ name: `${this.id}.createServer`,
38
+ patchType: PATCH_TYPES.FRAMEWORK,
39
+ alwaysRun: true,
40
+ post: ({ args, result }) => this.handleServerCreate(args, result)
41
+ });
42
+
43
+ patcher.patch(http2, 'createSecureServer', {
44
+ name: `${this.id}.createSecureServer`,
45
+ patchType: PATCH_TYPES.FRAMEWORK,
46
+ alwaysRun: true,
47
+ post: ({ args, result }) => this.handleServerCreate(args, result)
48
+ });
49
+
50
+ return http2;
51
+ }
52
+
53
+ /**
54
+ * Emits a create event for the new Server instance.
55
+ * @param {any[]} args The arguments passed to the Server constructor
56
+ * @param {import('net').Server} server The http Server instance
57
+ */
58
+ handleServerCreate(args, server) {
59
+ super.handleServerCreate(args, server);
60
+
61
+ // Since the `Http2Server` class is not exported, we can patch it here after
62
+ // creation.
63
+ // NOTE: could we just patch `net.Server` here and in `http`?
64
+ patcher.patch(server, 'listen', {
65
+ name: `${this.id}.Http2Server.listen`,
66
+ patchType: PATCH_TYPES.FRAMEWORK,
67
+ alwaysRun: true,
68
+ pre: ({ args, obj }) => this.handleServerListen(args, obj)
69
+ });
70
+ }
71
+ }
72
+
73
+ module.exports = Http2Framework;
@@ -14,9 +14,14 @@ Copyright: 2021 Contrast Security, Inc
14
14
  */
15
15
  'use strict';
16
16
 
17
+ const Http = require('./http');
18
+ const Http2 = require('./http2');
19
+ const Hapi16 = require('./hapi16');
20
+
17
21
  module.exports = function(agent) {
18
22
  // load all the hooks
19
- new (require('./http'))(agent);
20
- new (require('./https'))(agent);
21
- new (require('./hapi16'))(agent);
23
+ new Http(agent);
24
+ new Http(agent, 'https');
25
+ new Http2(agent);
26
+ new Hapi16(agent);
22
27
  };
package/lib/hooks/http.js CHANGED
@@ -35,39 +35,45 @@ const logger = require('../core/logger')('contrast:hooks');
35
35
  const patcher = require('./patcher');
36
36
  const { PATCH_TYPES } = require('../constants');
37
37
 
38
- module.exports = function(agent) {
39
- logger.info('applying non-policy hook: http');
38
+ const flatten = (ary) => Array.prototype.join.apply(ary, [',']);
40
39
 
41
- moduleHook.resolve({ name: 'http' }, function(http) {
42
- hookServerPrototype(http.Server.prototype, agent);
43
- });
44
- moduleHook.resolve({ name: 'https' }, function(https) {
45
- hookServerPrototype(https.Server.prototype, agent);
40
+ /**
41
+ * Logs stack for every res.[end, writeHead, write] call
42
+ *
43
+ * @param {ServerResponse} response
44
+ * @param {string} method method to hook
45
+ */
46
+ const logHook = (response, method) => {
47
+ patcher.patch(response, method, {
48
+ alwaysRun: true,
49
+ name: 'req-log',
50
+ patchType: PATCH_TYPES.MISC,
51
+ post(data) {
52
+ const { stack } = new Error();
53
+ logger.info('res.%s(%s):%o', method, flatten(data.args), stack);
54
+ }
46
55
  });
47
56
  };
48
57
 
49
- module.exports.getId = getId;
50
- module.exports.hookServerPrototype = hookServerPrototype;
51
-
52
58
  /**
53
59
  * Hooks the Server prototype's <code>emit</code> method.
54
- * @param {Server} proto The Server prototype
60
+ * @param {Server} server The Server prototype
55
61
  */
56
- function hookServerPrototype(proto, agent) {
57
- patcher.patch(proto, 'listen', {
62
+ const hookServer = (server, agent, id) => {
63
+ patcher.patch(server, 'listen', {
58
64
  alwaysRun: true,
59
65
  name: 'server-listen-log-time',
60
66
  patchType: PATCH_TYPES.MISC,
61
- pre(data) {
67
+ pre() {
62
68
  logger.info(`${process.pid}: http listen after ${process.uptime()}`);
63
69
  }
64
70
  });
65
71
 
66
72
  // Wrap the request emit in a contrast domain to track non-dataflow hooks, and for storage.
67
- const { emit } = proto;
68
- proto.emit = function(...args) {
69
- const [event, req, res] = args;
73
+ const { emit } = server;
74
+ server.emit = function newEmit(...args) {
70
75
  const self = this;
76
+ const [event, req, res] = args;
71
77
 
72
78
  if (event === 'request') {
73
79
  logger.debug('Received request %s, method %s', req.url, req.method);
@@ -75,35 +81,77 @@ function hookServerPrototype(proto, agent) {
75
81
  agent,
76
82
  req,
77
83
  res,
78
- hookResponse,
79
- callback: () => {
84
+ hookResponse(response, agent) {
85
+ const { writeHead, end } = response;
86
+ // XXX(ehden): we keep the original method available to call when handling
87
+ // blocked requests because of MethodTampering or other rules whose sink
88
+ // type is RESPONSE_STATUS and which can block at perimeter. We call the original
89
+ // when we block to prevent recursing through blocks by setting our own 403.
90
+ response[WRITE_HEAD] = writeHead;
91
+ response[END] = end;
92
+
93
+ /**
94
+ * Patch writeHead to emit a sink event before calling writeHead
95
+ *
96
+ */
97
+ patcher.patch(response, 'writeHead', {
98
+ alwaysRun: true,
99
+ name: 'write-head',
100
+ patchType: PATCH_TYPES.PROTECT_SINK,
101
+ pre(data) {
102
+ const [statusCode, reason] = data.args;
103
+ let [, , obj] = data.args;
104
+ let contentType;
105
+ if (typeof reason !== 'string') {
106
+ obj = reason;
107
+ }
108
+ if (obj && typeof obj === 'object') {
109
+ for (const [key, value] of Object.entries(obj)) {
110
+ if (key.toLowerCase() === 'content-type') {
111
+ contentType = value;
112
+ }
113
+ }
114
+ }
115
+ if (contentType) {
116
+ AsyncStorage.set(KEYS.RESPONSE_CONTENT_TYPE, contentType);
117
+ }
118
+ emitSinkEvent(statusCode, SINK_TYPES.RESPONSE_STATUS, id, false);
119
+ }
120
+ });
121
+
122
+ patcher.patch(response, 'setHeader', {
123
+ alwaysRun: true,
124
+ name: 'set-header',
125
+ patchType: PATCH_TYPES.ASYNC_CONTEXT,
126
+ pre(data) {
127
+ const [name = '', value] = data.args;
128
+ if (name.toLowerCase() === 'content-type' && value) {
129
+ AsyncStorage.set(KEYS.RESPONSE_CONTENT_TYPE, value);
130
+ }
131
+ }
132
+ });
133
+
134
+ // special HTTP logging for responses.
135
+ if (agent.config.agent.logger.log_outbound_http) {
136
+ ['end', 'writeHead', 'write'].forEach((method) => {
137
+ logHook(response, method);
138
+ });
139
+ }
140
+ },
141
+ callback() {
80
142
  const ipEvent = createSourceEvent(
81
143
  req,
82
144
  req,
83
145
  res,
84
- 'connection.remoteAddress',
146
+ 'socket.remoteAddress',
85
147
  INPUT_TYPES.IP,
86
- getId(req)
148
+ id
87
149
  );
88
150
  agentEmitter.emit('http.requestStart', req, res, ipEvent);
89
151
 
90
- emitSourceEvent(
91
- req,
92
- req,
93
- res,
94
- 'method',
95
- INPUT_TYPES.METHOD,
96
- getId(req)
97
- );
98
- emitSourceEvent(
99
- req,
100
- req,
101
- res,
102
- 'headers',
103
- INPUT_TYPES.HEADER,
104
- getId(req)
105
- );
106
- emitSourceEvent(req, req, res, 'url', INPUT_TYPES.URL, getId(req));
152
+ emitSourceEvent(req, req, res, 'method', INPUT_TYPES.METHOD, id);
153
+ emitSourceEvent(req, req, res, 'headers', INPUT_TYPES.HEADER, id);
154
+ emitSourceEvent(req, req, res, 'url', INPUT_TYPES.URL, id);
107
155
 
108
156
  const rv = emit.apply(self, args);
109
157
 
@@ -115,101 +163,37 @@ function hookServerPrototype(proto, agent) {
115
163
  return emit.apply(self, args);
116
164
  }
117
165
  };
118
- }
166
+ };
119
167
 
120
- /**
121
- * Patches ServerResponse instances and runs in AsyncStorage
122
- * context.
123
- * @param {ServerResponse} res
124
- */
125
- function hookResponse(res, agent) {
126
- const flattenArgs = (args) => Array.prototype.join.apply(args, [',']);
127
-
128
- /*
129
- * Logs stack for every res.[end, writeHead, write] call
130
- *
131
- * @param {ServerResponse} res
132
- * @param {string} method method to hook
133
- */
134
- function logHook(res, method) {
135
- patcher.patch(res, method, {
136
- alwaysRun: true,
137
- name: 'req-log',
138
- patchType: PATCH_TYPES.MISC,
139
- post(data) {
140
- const { stack } = new Error();
141
- logger.info('res.%s(%s):%o', method, flattenArgs(data.args), stack);
142
- }
143
- });
144
- }
145
-
146
- const { writeHead, end } = res;
147
- // XXX(ehden): we keep the original method available to call when handling
148
- // blocked requests because of MethodTampering or other rules whose sink
149
- // type is RESPONSE_STATUS and which can block at perimeter. We call the original
150
- // when we block to prevent recursing through blocks by setting our own 403.
151
- res[WRITE_HEAD] = writeHead;
152
- res[END] = end;
153
-
154
- /**
155
- * Patch writeHead to emit a sink event before calling writeHead
156
- *
157
- */
158
- patcher.patch(res, 'writeHead', {
159
- alwaysRun: true,
160
- name: 'write-head',
161
- patchType: PATCH_TYPES.PROTECT_SINK,
162
- pre(data) {
163
- let contentType;
164
- if (data.args[1] && typeof data.args[1] === 'object') {
165
- for (const [key, value] of Object.entries(data.args[1])) {
166
- if (key.toLowerCase() === 'content-type') {
167
- contentType = value;
168
- }
169
- }
170
- }
168
+ module.exports = (agent) => {
169
+ logger.info('applying non-policy hook: http');
171
170
 
172
- if (contentType) {
173
- AsyncStorage.set(KEYS.RESPONSE_CONTENT_TYPE, contentType);
174
- }
175
- const [statusCode] = data.args;
176
- emitSinkEvent(statusCode, SINK_TYPES.RESPONSE_STATUS, getId(this), false);
177
- }
171
+ moduleHook.resolve({ name: 'http' }, (http) => {
172
+ hookServer(http.Server.prototype, agent, 'http');
178
173
  });
179
174
 
180
- patcher.patch(res, 'setHeader', {
181
- alwaysRun: true,
182
- name: 'set-header',
183
- patchType: PATCH_TYPES.ASYNC_CONTEXT,
184
- pre(data) {
185
- if (
186
- (data.args[0] || '').toLowerCase() === 'content-type' &&
187
- data.args[1]
188
- ) {
189
- AsyncStorage.set(KEYS.RESPONSE_CONTENT_TYPE, data.args[1]);
190
- }
191
- }
175
+ moduleHook.resolve({ name: 'https' }, (https) => {
176
+ hookServer(https.Server.prototype, agent, 'https');
192
177
  });
193
178
 
194
- // special HTTP logging for responses.
195
- if (agent.config.agent.logger.log_outbound_http) {
196
- ['end', 'writeHead', 'write'].forEach((method) => {
197
- logHook(res, method);
179
+ moduleHook.resolve({ name: 'http2' }, (http2) => {
180
+ patcher.patch(http2, 'createServer', {
181
+ name: `create-server-http2-hooks`,
182
+ patchType: PATCH_TYPES.MISC,
183
+ alwaysRun: true,
184
+ post({ result }) {
185
+ hookServer(result, agent);
186
+ }
198
187
  });
199
- }
200
- }
201
188
 
202
- /**
203
- * Returns http or https depending on information about the
204
- * incomingMessage
205
- *
206
- * @param {IncomingMessage} req
207
- * @return {string} http or https
208
- */
209
- function getId(req) {
210
- if (req == null || req.socket == null) {
211
- return 'http';
212
- } else {
213
- return req.socket.encrypted ? 'https' : 'http';
214
- }
215
- }
189
+ patcher.patch(http2, 'createSecureServer', {
190
+ name: `create-secure-server-http2-hooks`,
191
+ patchType: PATCH_TYPES.MISC,
192
+ alwaysRun: true,
193
+ post({ result }) {
194
+ hookServer(result, agent);
195
+ }
196
+ });
197
+ });
198
+ };
199
+ module.exports.hookServer = hookServer;
@@ -14,44 +14,38 @@ Copyright: 2021 Contrast Security, Inc
14
14
  */
15
15
  'use strict';
16
16
 
17
- /**
18
- * @module lib/hooks/moduleHook
19
- * @class ModuleHook
20
- */
21
-
22
17
  const Module = require('module');
23
- const agentEmitter = require('../agent-emitter');
24
18
  const RequireHook = require('@contrast/require-hook');
19
+ const agentEmitter = require('../agent-emitter');
25
20
  const logger = require('../core/logger')('hooks:require');
26
21
 
27
22
  class ModuleHook {
28
- constructor(options) {
23
+ constructor() {
29
24
  this.reqHook = new RequireHook(logger);
30
25
 
31
- // RequireHook takes care of hooking
32
- // but still need to patch require to emit 'require' events
33
- const origModRequire = Module.prototype.require;
34
- Module.prototype.require = function(name) {
35
- agentEmitter.emit('require', name);
36
- return origModRequire.call(this, name);
26
+ // RequireHook takes care of hooking but we still need to patch require to
27
+ // emit 'require' events
28
+ const origModLoad = Module._load;
29
+ Module._load = function __loadAndEmit(...args) {
30
+ const [request] = args;
31
+ agentEmitter.emit('require', request);
32
+ return Reflect.apply(origModLoad, this, args);
37
33
  };
38
34
  }
39
35
 
40
36
  /**
41
- *
42
37
  * Collects instrumentation functions that will run on the specified
43
- * module's file at the time it is loaded via <code>require</code>.
44
- * @param {Object} descriptor describes module you're hook(name, version, file)
45
- * @param {Function} instrumentation The function to run on the module at
46
- * the time it is required in the code
38
+ * module's file at the time it is loaded via `require`.
39
+ * @param {RequireHook.Descriptor} descriptor describes the module to hook
40
+ * @param {Function} handler The function to run on the module at the time it is required
47
41
  */
48
- resolve(descriptor, instrumentation) {
49
- this.reqHook.resolve(descriptor, instrumentation);
42
+ resolve(descriptor, handler) {
43
+ this.reqHook.resolve(descriptor, handler);
50
44
  }
51
45
 
52
46
  /**
53
- * reset the hooked state for a module so that it's handlers re-run (used in unit tests)
54
- *
47
+ * Reset the hooked state for a module so that it's handlers re-run.
48
+ * NOTE: used in unit tests.
55
49
  * @param {string} name the name of the module
56
50
  */
57
51
  reset(name) {
@@ -40,9 +40,6 @@ module.exports = function instrument(agent, reporter) {
40
40
  return;
41
41
  }
42
42
 
43
- const stackFactory = require('./core/stacktrace').singleton;
44
- stackFactory.stackTraceLimit = agent.config.agent.stack_trace_limit;
45
-
46
43
  // The order of these matters, do not re-order
47
44
  collectLibInfo(agent);
48
45
  // (CONTRAST-31526) this must be required first to load global.ContrastMethods