@contrast/protect 1.29.0 → 1.30.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 (63) hide show
  1. package/LICENSE +1 -1
  2. package/lib/error-handlers/common-handler.js +1 -1
  3. package/lib/error-handlers/constants.js +1 -1
  4. package/lib/error-handlers/index.js +1 -1
  5. package/lib/error-handlers/init-domain.js +1 -1
  6. package/lib/error-handlers/install/express4.js +4 -4
  7. package/lib/error-handlers/install/fastify.js +1 -1
  8. package/lib/error-handlers/install/hapi.js +3 -3
  9. package/lib/error-handlers/install/koa2.js +3 -3
  10. package/lib/get-source-context.js +1 -1
  11. package/lib/hardening/constants.js +1 -1
  12. package/lib/hardening/handlers.js +1 -1
  13. package/lib/hardening/index.js +1 -1
  14. package/lib/hardening/install/node-serialize0.js +1 -1
  15. package/lib/index.d.ts +1 -1
  16. package/lib/index.js +61 -10
  17. package/lib/input-analysis/constants.js +1 -1
  18. package/lib/input-analysis/handlers.js +14 -14
  19. package/lib/input-analysis/index.js +1 -1
  20. package/lib/input-analysis/install/body-parser1.js +1 -1
  21. package/lib/input-analysis/install/busboy1.js +1 -1
  22. package/lib/input-analysis/install/cookie-parser1.js +5 -2
  23. package/lib/input-analysis/install/express4.js +5 -2
  24. package/lib/input-analysis/install/fastify.js +3 -3
  25. package/lib/input-analysis/install/formidable1.js +1 -1
  26. package/lib/input-analysis/install/hapi.js +12 -10
  27. package/lib/input-analysis/install/http.js +5 -5
  28. package/lib/input-analysis/install/koa-body5.js +1 -1
  29. package/lib/input-analysis/install/koa-bodyparser4.js +1 -1
  30. package/lib/input-analysis/install/koa2.js +1 -1
  31. package/lib/input-analysis/install/multer1.js +9 -3
  32. package/lib/input-analysis/install/qs6.js +1 -1
  33. package/lib/input-analysis/install/universal-cookie4.js +1 -1
  34. package/lib/input-analysis/ip-analysis.js +1 -1
  35. package/lib/input-analysis/virtual-patches.js +1 -1
  36. package/lib/input-tracing/constants.js +1 -1
  37. package/lib/input-tracing/handlers/index.js +1 -1
  38. package/lib/input-tracing/index.js +1 -1
  39. package/lib/input-tracing/install/child-process.js +1 -1
  40. package/lib/input-tracing/install/eval.js +1 -1
  41. package/lib/input-tracing/install/fs.js +1 -1
  42. package/lib/input-tracing/install/function.js +1 -1
  43. package/lib/input-tracing/install/http.js +1 -1
  44. package/lib/input-tracing/install/marsdb.js +1 -1
  45. package/lib/input-tracing/install/mongodb.js +1 -1
  46. package/lib/input-tracing/install/mssql.js +1 -1
  47. package/lib/input-tracing/install/mysql.js +1 -1
  48. package/lib/input-tracing/install/postgres.js +1 -1
  49. package/lib/input-tracing/install/sequelize.js +1 -1
  50. package/lib/input-tracing/install/sqlite3.js +1 -1
  51. package/lib/input-tracing/install/vm.js +1 -1
  52. package/lib/make-response-blocker.js +1 -1
  53. package/lib/make-source-context.js +1 -1
  54. package/lib/policy.js +42 -29
  55. package/lib/security-exception.js +1 -1
  56. package/lib/semantic-analysis/constants.js +1 -1
  57. package/lib/semantic-analysis/handlers.js +1 -1
  58. package/lib/semantic-analysis/index.js +1 -1
  59. package/lib/semantic-analysis/install/libxmljs.js +3 -3
  60. package/lib/semantic-analysis/utils/xml-analysis.js +1 -1
  61. package/lib/throw-security-exception.js +1 -1
  62. package/package.json +4 -3
  63. package/lib/esm-loader.mjs +0 -70
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright: 2023 Contrast Security, Inc
1
+ Copyright: 2024 Contrast Security, Inc
2
2
  Contact: support@contrastsecurity.com
3
3
  License: Commercial
4
4
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -43,7 +43,7 @@ module.exports = function (core) {
43
43
  }
44
44
 
45
45
  if (!sourceContext && isSecurityException) {
46
- logger.info('source context not found; unable to handle response');
46
+ logger.info({ funcKey: data.funcKey }, 'source context not found; unable to handle response');
47
47
  }
48
48
  return orig();
49
49
  };
@@ -103,7 +103,7 @@ module.exports = function (core) {
103
103
  return patcher.patch(fn, {
104
104
  name: 'express.route-handler',
105
105
  patchType,
106
- around(orig) {
106
+ around(orig, data) {
107
107
  const ret = orig();
108
108
  if (ret && ret.catch) {
109
109
  return ret.catch((err) => {
@@ -118,7 +118,7 @@ module.exports = function (core) {
118
118
  }
119
119
 
120
120
  if (!sourceContext && isSecurityException) {
121
- logger.info('source context not found; unable to handle response');
121
+ logger.info({ funcKey: data.funcKey }, 'source context not found; unable to handle response');
122
122
  return;
123
123
  }
124
124
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -46,13 +46,13 @@ module.exports = function (core) {
46
46
  err.output.payload = undefined;
47
47
  data.result = err;
48
48
 
49
- logger.info({ mode, ruleId }, 'Request blocked');
49
+ logger.info({ funcKey: data.funcKey, mode, ruleId }, 'Request blocked');
50
50
 
51
51
  return;
52
52
  }
53
53
 
54
54
  if (!sourceContext && isSecurityException) {
55
- logger.info('source context not found; unable to handle response');
55
+ logger.info({ funcKey: data.funcKey }, 'source context not found; unable to handle response');
56
56
  }
57
57
  }
58
58
  });
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -32,7 +32,7 @@ module.exports = function (core) {
32
32
  koa2ErrorHandler.install = function () {
33
33
  depHooks.resolve({ name: 'koa', version: '>=2.3.0' }, (Koa) => {
34
34
  patcher.patch(Koa.prototype, 'handleRequest', {
35
- name: 'Koa.Application',
35
+ name: 'Koa.Application.handleRequest',
36
36
  patchType,
37
37
  pre(data) {
38
38
  const [ctx] = data.args;
@@ -52,7 +52,7 @@ module.exports = function (core) {
52
52
  }
53
53
 
54
54
  if (!sourceContext && isSecurityException) {
55
- logger.info('source context not found; unable to handle response');
55
+ logger.info({ funcKey: data.funcKey }, 'source context not found; unable to handle response');
56
56
  }
57
57
 
58
58
  return orig();
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
package/lib/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -15,12 +15,13 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const agentLib = require('@contrast/agent-lib');
18
+ const { isMainThread } = require('node:worker_threads');
19
+
19
20
  const { callChildComponentMethodsSync } = require('@contrast/common');
20
21
 
21
22
  module.exports = function(core) {
22
23
  const protect = core.protect = {
23
- agentLib: module.exports.instantiateAgentLib(agentLib),
24
+ agentLib: module.exports.instantiateAgentLib(),
24
25
  };
25
26
 
26
27
  require('./policy')(core);
@@ -49,19 +50,69 @@ module.exports = function(core) {
49
50
 
50
51
  module.exports.instantiateAgentLib = instantiateAgentLib;
51
52
 
53
+ // this is just enough agentLib definition for initialization to work.
54
+ // it allows us to avoid instantiating agent-lib in the loader thread
55
+ // until we find the issue with napi-rs/agent-lib-node-bindings.
56
+ const skeletalAgentLib = {
57
+ RuleType: {
58
+ 'unsafe-file-upload': 1,
59
+ 'path-traversal': 2,
60
+ 'reflected-xss': 4,
61
+ 'sql-injection': 8,
62
+ 'cmd-injection': 16,
63
+ 'nosql-injection-mongo': 32,
64
+ 'bot-blocker': 64,
65
+ 'ssjs-injection': 128,
66
+ 'method-tampering': 256,
67
+ 'prototype-pollution': 512
68
+ },
69
+ InputType: {
70
+ CookieName: 1,
71
+ CookieValue: 2,
72
+ HeaderKey: 3,
73
+ HeaderValue: 4,
74
+ JsonKey: 5,
75
+ JsonValue: 6,
76
+ Method: 7,
77
+ ParameterKey: 8,
78
+ ParameterValue: 9,
79
+ UriPath: 10,
80
+ UrlParameter: 11,
81
+ MultipartName: 12,
82
+ XmlValue: 13,
83
+ RawBody: 14
84
+ },
85
+ DbType: {
86
+ DB2: 1,
87
+ MySQL: 2,
88
+ Oracle: 3,
89
+ Postgres: 4,
90
+ Sqlite: 5,
91
+ SqlServer: 6,
92
+ Unknown: 7
93
+ },
94
+ version: '8.2.0',
95
+ };
52
96
  /**
53
97
  * Create an instance of agent-lib and attach the constants directly to it.
54
98
  *
55
99
  * @param {Object} lib the value returned by require('@contrast/agent-lib')
56
100
  * @returns {Object} an agent-lib instance with the constants attached to it.
57
101
  */
58
- function instantiateAgentLib(lib) {
59
- const agentLib = new lib.Agent();
60
- for (const c in lib.constants) {
61
- agentLib[c] = lib.constants[c];
62
- }
63
- if ('version' in lib) {
64
- agentLib.version = lib.version;
102
+ function instantiateAgentLib() {
103
+ let agentLib;
104
+ if (isMainThread) {
105
+ const lib = require('@contrast/agent-lib');
106
+ agentLib = new lib.Agent();
107
+ for (const c in lib.constants) {
108
+ agentLib[c] = lib.constants[c];
109
+ }
110
+ if ('version' in lib) {
111
+ agentLib.version = lib.version;
112
+ }
113
+ } else {
114
+ agentLib = skeletalAgentLib;
65
115
  }
116
+
66
117
  return agentLib;
67
118
  }
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -92,7 +92,7 @@ const acceptedMethods = new Set([
92
92
  'version-control',
93
93
  ]);
94
94
 
95
- module.exports = function(core) {
95
+ module.exports = function (core) {
96
96
  const {
97
97
  logger,
98
98
  protect: {
@@ -225,7 +225,7 @@ module.exports = function(core) {
225
225
  * @param {Object} sourceContext
226
226
  * @param {Object} urlParams pojo
227
227
  */
228
- inputAnalysis.handleUrlParams = function(sourceContext, urlParams) {
228
+ inputAnalysis.handleUrlParams = function (sourceContext, urlParams) {
229
229
  if (sourceContext.analyzedUrlParams) return;
230
230
  sourceContext.analyzedUrlParams = true;
231
231
 
@@ -240,7 +240,7 @@ module.exports = function(core) {
240
240
  const resultsList = [];
241
241
  const { UrlParameter } = agentLib.InputType;
242
242
 
243
- traverseValues(urlParams, function(path, type, value) {
243
+ traverseValues(urlParams, function (path, type, value) {
244
244
  // url param names are not checked.
245
245
  if (type !== 'Value') {
246
246
  return;
@@ -292,7 +292,7 @@ module.exports = function(core) {
292
292
  * @param {Object} sourceContext
293
293
  * @param {Object} cookies pojo
294
294
  */
295
- inputAnalysis.handleCookies = function(sourceContext, cookies) {
295
+ inputAnalysis.handleCookies = function (sourceContext, cookies) {
296
296
  if (sourceContext.analyzedCookies) return;
297
297
  sourceContext.analyzedCookies = true;
298
298
 
@@ -324,7 +324,7 @@ module.exports = function(core) {
324
324
  * @param {Object} sourceContext
325
325
  * @param {Object} parsedBody
326
326
  */
327
- inputAnalysis.handleParsedBody = function(sourceContext, parsedBody) {
327
+ inputAnalysis.handleParsedBody = function (sourceContext, parsedBody) {
328
328
  if (sourceContext.analyzedBody) return;
329
329
 
330
330
  sourceContext.analyzedBody = true;
@@ -357,7 +357,7 @@ module.exports = function(core) {
357
357
 
358
358
  // was MULTIPART_NAME but maybe we should just call it what it is. it's kind
359
359
  // of a dumb rule anyway. but maybe some code actually uses the name provided.
360
- inputAnalysis.handleFileUploadName = function(sourceContext, names) {
360
+ inputAnalysis.handleFileUploadName = function (sourceContext, names) {
361
361
  const type = agentLib.InputType.MultipartName;
362
362
  const { policy } = sourceContext;
363
363
  const resultsList = [];
@@ -401,7 +401,7 @@ module.exports = function(core) {
401
401
  }
402
402
  };
403
403
 
404
- inputAnalysis.handleVirtualPatches = function(sourceContext, requestInput) {
404
+ inputAnalysis.handleVirtualPatches = function (sourceContext, requestInput) {
405
405
  const ruleId = Rule.VIRTUAL_PATCH;
406
406
 
407
407
  if (!Object.keys(requestInput).filter(Boolean).length || !sourceContext?.virtualPatchesEvaluators.length) return;
@@ -430,7 +430,7 @@ module.exports = function(core) {
430
430
  }
431
431
  };
432
432
 
433
- inputAnalysis.handleIpAllowlist = function(sourceContext, ipAllowlist) {
433
+ inputAnalysis.handleIpAllowlist = function (sourceContext, ipAllowlist) {
434
434
  if (!sourceContext || !ipAllowlist.length) return;
435
435
 
436
436
  const { ip: reqIp, headers: reqHeaders } = sourceContext.reqData;
@@ -443,7 +443,7 @@ module.exports = function(core) {
443
443
  }
444
444
  };
445
445
 
446
- inputAnalysis.handleIpDenylist = function(sourceContext, ipDenylist) {
446
+ inputAnalysis.handleIpDenylist = function (sourceContext, ipDenylist) {
447
447
  const ruleId = Rule.IP_DENYLIST;
448
448
 
449
449
  if (!sourceContext || !ipDenylist.length) return;
@@ -466,7 +466,7 @@ module.exports = function(core) {
466
466
  }
467
467
  };
468
468
 
469
- inputAnalysis.handleMethodTampering = function(sourceContext, connectInputs) {
469
+ inputAnalysis.handleMethodTampering = function (sourceContext, connectInputs) {
470
470
  const ruleId = Rule.METHOD_TAMPERING;
471
471
  const mode = sourceContext.policy[ruleId];
472
472
  if (mode !== OFF) {
@@ -651,7 +651,7 @@ module.exports = function(core) {
651
651
  // another day.
652
652
 
653
653
  /* eslint-disable-next-line complexity */
654
- traverseKeysAndValues(object, function(path, type, value) {
654
+ traverseKeysAndValues(object, function (path, type, value) {
655
655
  let itemType;
656
656
  let isMongoQueryType;
657
657
  // this is a bit awkward now because nosql-injection-mongo is not integrated
@@ -745,13 +745,13 @@ module.exports = function(core) {
745
745
 
746
746
  // Ignore bad IP values.
747
747
  if (!address.isValid(currentIp)) {
748
- logger.warn(`Unable to parse ${currentIp}.`);
748
+ logger.warn('Unable to parse %s.', currentIp);
749
749
  continue;
750
750
  }
751
751
  const expired = doesExpire ? expiresAt - now <= 0 : false;
752
752
 
753
753
  if (expired) {
754
- logger.info(`IP expired: ${listEntry.name}, ${listEntry.ip}`);
754
+ logger.info('IP expired: %s, %s', listEntry.name, listEntry.ip);
755
755
  continue;
756
756
  }
757
757
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -54,7 +54,10 @@ module.exports = (core) => {
54
54
  if (isSecurityException(err)) {
55
55
  securityException = err;
56
56
  } else {
57
- logger.error({ err }, 'Unexpected error during input analysis');
57
+ logger.error(
58
+ { err, funcKey: data.funcKey },
59
+ 'Unexpected error during input analysis'
60
+ );
58
61
  }
59
62
  }
60
63
  }
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -64,7 +64,10 @@ module.exports = (core) => {
64
64
  if (isSecurityException(err)) {
65
65
  securityException = err;
66
66
  } else {
67
- logger.error({ err }, 'Unexpected error during input analysis');
67
+ logger.error(
68
+ { err, funcKey: data.funcKey },
69
+ 'Unexpected error during input analysis'
70
+ );
68
71
  }
69
72
  }
70
73
  }
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -39,7 +39,7 @@ module.exports = (core) => {
39
39
  depHooks.resolve({ name: 'fastify', version: '>=3 <5' }, (fastify) => patcher.patch(fastify, {
40
40
  name: 'fastify.build',
41
41
  patchType,
42
- post({ result: server }) {
42
+ post({ result: server, funcKey }) {
43
43
  server.addHook('preValidation', function (request, reply, done) {
44
44
  let securityException;
45
45
  const sourceContext = protect.getSourceContext();
@@ -67,7 +67,7 @@ module.exports = (core) => {
67
67
  if (isSecurityException(err)) {
68
68
  securityException = err;
69
69
  } else {
70
- logger.error({ err }, 'Unexpected error during input analysis');
70
+ logger.error({ err, funcKey }, 'Unexpected error during input analysis');
71
71
  }
72
72
  }
73
73
  }
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -50,22 +50,24 @@ module.exports = (core) => {
50
50
  name: 'hapi.server',
51
51
  patchType,
52
52
  post(data) {
53
- const server = data.result;
54
- if (server) {
55
- instrumentServer(server);
53
+ if (data.result) {
54
+ instrumentServer(data);
56
55
  } else {
57
- logger.error('Hapi Server is called but there is no server instance!');
56
+ logger.error(
57
+ { funcKey: data.funcKey },
58
+ 'Hapi Server is called but there is no server instance!'
59
+ );
58
60
  }
59
61
  }
60
62
  });
61
63
  };
62
64
 
63
- const instrumentServer = (server) => {
64
- server.ext('onPreStart', function onPreStart(core) {
65
- logger.info('hapi version %s', core.version);
65
+ const instrumentServer = ({ result, funcKey }) => {
66
+ result.ext('onPreStart', function onPreStart(core) {
67
+ logger.debug('hapi version %s', core.version);
66
68
  });
67
69
 
68
- server.ext('onPreHandler', function onPreHandler(req, h) {
70
+ result.ext('onPreHandler', function onPreHandler(req, h) {
69
71
  const sourceContext = protect.getSourceContext();
70
72
 
71
73
  if (sourceContext) {
@@ -93,7 +95,7 @@ module.exports = (core) => {
93
95
  if (isSecurityException(err)) {
94
96
  throw err;
95
97
  } else {
96
- logger.error({ err }, 'Unexpected error during input analysis');
98
+ logger.error({ err, funcKey }, 'Unexpected error during input analysis');
97
99
  }
98
100
  }
99
101
  }
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -18,7 +18,7 @@
18
18
  const { Event, toLowerCase } = require('@contrast/common');
19
19
  const { patchType } = require('../constants');
20
20
 
21
- module.exports = function(core) {
21
+ module.exports = function (core) {
22
22
  const {
23
23
  logger,
24
24
  messages,
@@ -70,7 +70,7 @@ module.exports = function(core) {
70
70
  try {
71
71
  store = sources.getStore();
72
72
  if (!store) {
73
- logger.debug('request store not available during http input-analysis');
73
+ logger.debug({ funcKey: data.funcKey }, 'request store not available during http input-analysis');
74
74
  return;
75
75
  }
76
76
 
@@ -112,14 +112,14 @@ module.exports = function(core) {
112
112
 
113
113
  block = block || inputAnalysis.handleConnect(store.protect, connectInputs);
114
114
  } catch (err) {
115
- logger.error({ err }, 'Error during http input analysis');
115
+ logger.error({ err, funcKey: data.funcKey }, 'Error during http input analysis');
116
116
  }
117
117
 
118
118
  if (!block) {
119
119
  callNext();
120
120
  } else {
121
121
  store.protect.block(...block);
122
- logger.debug({ block }, 'request blocked by not emitting request event');
122
+ logger.debug({ block, funcKey: data.funcKey }, 'request blocked by not emitting request event');
123
123
  }
124
124
  }
125
125
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -59,7 +59,10 @@ module.exports = (core) => {
59
59
  if (isSecurityException(err)) {
60
60
  securityException = err;
61
61
  } else {
62
- logger.error({ err }, 'Unexpected error during input analysis');
62
+ logger.error(
63
+ { err, funcKey: data.funcKey },
64
+ 'Unexpected error during input analysis',
65
+ );
63
66
  }
64
67
  }
65
68
  }
@@ -81,7 +84,10 @@ module.exports = (core) => {
81
84
  if (isSecurityException(err)) {
82
85
  securityException = err;
83
86
  } else {
84
- logger.error({ err }, 'Unexpected error during input analysis');
87
+ logger.error(
88
+ { err, funcKey: data.funcKey },
89
+ 'Unexpected error during input analysis',
90
+ );
85
91
  }
86
92
  }
87
93
  }
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
package/lib/policy.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -17,33 +17,34 @@
17
17
 
18
18
  const {
19
19
  Rule,
20
- ProtectRuleMode: {
21
- BLOCK_AT_PERIMETER,
22
- OFF,
23
- },
24
- Rule: {
25
- BOT_BLOCKER,
26
- CMD_INJECTION,
27
- CMD_INJECTION_COMMAND_BACKDOORS,
28
- CMD_INJECTION_SEMANTIC_CHAINED_COMMANDS,
29
- CMD_INJECTION_SEMANTIC_DANGEROUS_PATHS,
30
- METHOD_TAMPERING,
31
- NOSQL_INJECTION,
32
- NOSQL_INJECTION_MONGO,
33
- PATH_TRAVERSAL,
34
- PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS,
35
- REFLECTED_XSS,
36
- SQL_INJECTION,
37
- SSJS_INJECTION,
38
- UNSAFE_FILE_UPLOAD,
39
- UNTRUSTED_DESERIALIZATION,
40
- XXE,
41
- },
20
+ ProtectRuleMode,
42
21
  Event,
43
22
  toLowerCase,
44
23
  split,
45
24
  join
46
25
  } = require('@contrast/common');
26
+ const { ConfigSource } = require('@contrast/config');
27
+
28
+ const { BLOCK_AT_PERIMETER, OFF } = ProtectRuleMode;
29
+ const {
30
+ BOT_BLOCKER,
31
+ CMD_INJECTION,
32
+ CMD_INJECTION_COMMAND_BACKDOORS,
33
+ CMD_INJECTION_SEMANTIC_CHAINED_COMMANDS,
34
+ CMD_INJECTION_SEMANTIC_DANGEROUS_PATHS,
35
+ METHOD_TAMPERING,
36
+ NOSQL_INJECTION,
37
+ NOSQL_INJECTION_MONGO,
38
+ PATH_TRAVERSAL,
39
+ PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS,
40
+ REFLECTED_XSS,
41
+ SQL_INJECTION,
42
+ SSJS_INJECTION,
43
+ UNSAFE_FILE_UPLOAD,
44
+ UNTRUSTED_DESERIALIZATION,
45
+ XXE,
46
+ } = Rule;
47
+ const { DEFAULT_VALUE } = ConfigSource;
47
48
 
48
49
  module.exports = function (core) {
49
50
  const {
@@ -116,8 +117,14 @@ module.exports = function (core) {
116
117
  if (config.protect.rules.disabled_rules.includes(ruleId)) {
117
118
  return OFF;
118
119
  }
119
- if (ruleId === 'nosql-injection-mongo') {
120
- return config.protect.rules?.[ruleId]?.mode || config.protect.rules?.['nosql-injection']?.mode;
120
+ // If nosql-injection is set in config but nosql-injection-mongo isn't,
121
+ // then have nosql-injection-mongo inherit config value from umbrella rule.
122
+ if (
123
+ ruleId === 'nosql-injection-mongo' &&
124
+ config.getEffectiveSource(`protect.rules.${ruleId}.mode`) === DEFAULT_VALUE &&
125
+ config.getEffectiveSource(`protect.rules.${NOSQL_INJECTION}.mode`) !== DEFAULT_VALUE
126
+ ) {
127
+ return config.protect.rules?.[NOSQL_INJECTION]?.mode;
121
128
  }
122
129
  return config.protect.rules?.[ruleId]?.mode;
123
130
  }
@@ -235,8 +242,6 @@ module.exports = function (core) {
235
242
  }
236
243
 
237
244
  function updateGlobalPolicy(remoteSettings) {
238
- let update;
239
-
240
245
  const protectionRules = remoteSettings?.protect?.rules;
241
246
  if (protectionRules) {
242
247
  [
@@ -246,7 +251,6 @@ module.exports = function (core) {
246
251
  CMD_INJECTION_SEMANTIC_DANGEROUS_PATHS,
247
252
  METHOD_TAMPERING,
248
253
  NOSQL_INJECTION,
249
- NOSQL_INJECTION_MONGO,
250
254
  PATH_TRAVERSAL,
251
255
  PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS,
252
256
  REFLECTED_XSS,
@@ -260,6 +264,15 @@ module.exports = function (core) {
260
264
  policy[ruleId] = OFF;
261
265
  } else {
262
266
  policy[ruleId] = config.getEffectiveValue(`protect.rules.${ruleId}.mode`);
267
+
268
+ // nosql-injection-mongo should inherit nosql-injection mode if unset in config
269
+ if (ruleId === NOSQL_INJECTION) {
270
+ if (config.getEffectiveSource(`protect.rules.${NOSQL_INJECTION_MONGO}.mode`) === DEFAULT_VALUE) {
271
+ policy[NOSQL_INJECTION_MONGO] = policy[ruleId];
272
+ } else {
273
+ policy[NOSQL_INJECTION_MONGO] = config.getEffectiveValue(`protect.rules.${NOSQL_INJECTION_MONGO}.mode`);
274
+ }
275
+ }
263
276
  }
264
277
  });
265
278
 
@@ -275,7 +288,7 @@ module.exports = function (core) {
275
288
 
276
289
  updateRulesMask();
277
290
  protect.policy.exclusions = compiled;
278
- logger.info({ policy: protect.policy }, `protect policy updated from ${update}`);
291
+ logger.info({ policy: protect.policy }, 'Protect policy updated');
279
292
  }
280
293
  }
281
294
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -44,7 +44,7 @@ module.exports = function (core) {
44
44
  patcher.patch(mod, method, {
45
45
  name,
46
46
  patchType,
47
- pre({ args, hooked, orig }) {
47
+ pre({ args, hooked, orig, funcKey }) {
48
48
  const sourceContext = protect.getSourceContext(name);
49
49
  const [value, options] = args;
50
50
 
@@ -68,7 +68,7 @@ module.exports = function (core) {
68
68
  throw err;
69
69
  }
70
70
 
71
- logger.error({ err }, 'Unexpected error during semantic analysis');
71
+ logger.error({ err, funcKey }, 'Unexpected error during semantic analysis');
72
72
  }
73
73
  }
74
74
  });
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright: 2023 Contrast Security, Inc
2
+ * Copyright: 2024 Contrast Security, Inc
3
3
  * Contact: support@contrastsecurity.com
4
4
  * License: Commercial
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/protect",
3
- "version": "1.29.0",
3
+ "version": "1.30.0",
4
4
  "description": "Contrast service providing framework-agnostic Protect support",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -19,8 +19,9 @@
19
19
  "dependencies": {
20
20
  "@contrast/agent-lib": "^7.0.1",
21
21
  "@contrast/common": "1.16.0",
22
- "@contrast/core": "1.27.0",
23
- "@contrast/esm-hooks": "1.23.0",
22
+ "@contrast/config": "1.23.0",
23
+ "@contrast/core": "1.27.1",
24
+ "@contrast/esm-hooks": "2.0.1",
24
25
  "@contrast/scopes": "1.4.0",
25
26
  "ipaddr.js": "^2.0.1",
26
27
  "semver": "^7.3.7"
@@ -1,70 +0,0 @@
1
- /*
2
- * Copyright: 2023 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
-
16
- import esmHooks from '@contrast/esm-hooks';
17
- import { createRequire } from 'node:module';
18
-
19
- const ERROR_MESSAGE = 'A fatal agent installation error has occurred. The application will be run without instrumentation.';
20
-
21
- let getSource = async function getSource(url, context, defaultGetSource) {
22
- return defaultGetSource(url, context, defaultGetSource);
23
- };
24
-
25
- let transformSource = async function transformSource(url, context, defaultTransformSource) {
26
- return defaultTransformSource(url, context, defaultTransformSource);
27
- };
28
-
29
- let load = async function load(url, context, defaultLoad) {
30
- return defaultLoad(url, context, defaultLoad);
31
- };
32
-
33
- const require = createRequire(import.meta.url);
34
- const { name: agentName, version: agentVersion } = require('../package.json');
35
- const core = { agentName, agentVersion };
36
- try {
37
- require('@contrast/core/lib/messages')(core);
38
- require('@contrast/config')(core);
39
- require('@contrast/logger').default(core);
40
-
41
- // @contrast/info ?
42
- require('@contrast/core/lib/agent-info')(core);
43
- require('@contrast/core/lib/system-info')(core);
44
- require('@contrast/core/lib/app-info')(core);
45
- require('@contrast/core/lib/sensitive-data-masking')(core);
46
- require('@contrast/core/lib/is-agent-path')(core);
47
- require('@contrast/core/lib/capture-stacktrace')(core);
48
-
49
- require('@contrast/patcher')(core);
50
- require('@contrast/rewriter')(core); // merge contrast-methods?
51
- require('@contrast/core/lib/contrast-methods')(core); // can we remove dependency on patcher?
52
-
53
- require('@contrast/dep-hooks')(core);
54
- require('@contrast/scopes')(core);
55
- require('@contrast/deadzones')(core);
56
- require('@contrast/reporter').default(core);
57
- require('@contrast/instrumentation')(core);
58
-
59
- // TODO: i think we still need to INSTALL components.
60
- ({ getSource, transformSource, load } = esmHooks(core));
61
- } catch (err) {
62
- // TODO: Consider proper UNINSTALLATION and normal startup w/o agent
63
- if (core.logger) {
64
- core.logger.error({ err }, ERROR_MESSAGE);
65
- } else {
66
- console.error(new Error(ERROR_MESSAGE, { cause: err }));
67
- }
68
- }
69
-
70
- export { getSource, transformSource, load };