@contrast/protect 1.3.0 → 1.5.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 (49) hide show
  1. package/lib/error-handlers/index.js +7 -4
  2. package/lib/error-handlers/install/express4.js +2 -3
  3. package/lib/error-handlers/install/{fastify3.js → fastify.js} +13 -15
  4. package/lib/error-handlers/install/hapi.js +75 -0
  5. package/lib/error-handlers/install/koa2.js +1 -2
  6. package/lib/{cli-rewriter.js → get-source-context.js} +13 -15
  7. package/lib/hardening/constants.js +20 -0
  8. package/lib/hardening/handlers.js +65 -0
  9. package/lib/hardening/index.js +29 -0
  10. package/lib/hardening/install/node-serialize0.js +59 -0
  11. package/lib/index.d.ts +3 -21
  12. package/lib/index.js +6 -46
  13. package/lib/input-analysis/handlers.js +198 -39
  14. package/lib/input-analysis/index.js +9 -6
  15. package/lib/input-analysis/install/body-parser1.js +20 -18
  16. package/lib/input-analysis/install/cookie-parser1.js +13 -15
  17. package/lib/input-analysis/install/express4.js +8 -13
  18. package/lib/input-analysis/install/fastify.js +86 -0
  19. package/lib/input-analysis/install/formidable1.js +4 -5
  20. package/lib/input-analysis/install/hapi.js +106 -0
  21. package/lib/input-analysis/install/http.js +80 -30
  22. package/lib/input-analysis/install/koa-body5.js +5 -10
  23. package/lib/input-analysis/install/koa-bodyparser4.js +6 -10
  24. package/lib/input-analysis/install/koa2.js +13 -24
  25. package/lib/input-analysis/install/multer1.js +5 -6
  26. package/lib/input-analysis/install/qs6.js +7 -11
  27. package/lib/input-analysis/install/universal-cookie4.js +3 -7
  28. package/lib/input-analysis/ip-analysis.js +76 -0
  29. package/lib/input-analysis/virtual-patches.js +109 -0
  30. package/lib/input-tracing/handlers/index.js +92 -23
  31. package/lib/input-tracing/index.js +14 -18
  32. package/lib/input-tracing/install/child-process.js +13 -7
  33. package/lib/input-tracing/install/eval.js +60 -0
  34. package/lib/input-tracing/install/fs.js +4 -2
  35. package/lib/input-tracing/install/function.js +60 -0
  36. package/lib/input-tracing/install/http.js +63 -0
  37. package/lib/input-tracing/install/mongodb.js +20 -20
  38. package/lib/input-tracing/install/mysql.js +3 -2
  39. package/lib/input-tracing/install/postgres.js +5 -4
  40. package/lib/input-tracing/install/sequelize.js +7 -5
  41. package/lib/input-tracing/install/sqlite3.js +6 -4
  42. package/lib/input-tracing/install/vm.js +132 -0
  43. package/lib/make-source-context.js +8 -49
  44. package/lib/policy.js +134 -0
  45. package/lib/semantic-analysis/handlers.js +161 -0
  46. package/lib/semantic-analysis/index.js +38 -0
  47. package/package.json +7 -9
  48. package/lib/input-analysis/install/fastify3.js +0 -107
  49. package/lib/utils.js +0 -84
@@ -22,8 +22,8 @@ module.exports = function (core) {
22
22
  const {
23
23
  depHooks,
24
24
  patcher,
25
- scopes: { sources },
26
25
  captureStacktrace,
26
+ protect,
27
27
  protect: { inputTracing },
28
28
  } = core;
29
29
 
@@ -61,15 +61,15 @@ module.exports = function (core) {
61
61
  patcher.patch(obj, method, {
62
62
  name: patchName,
63
63
  patchType,
64
- pre: ({ args, hooked, name }) => {
64
+ pre: ({ args, hooked, name, orig }) => {
65
65
  const value = getCursorQueryData(args, version);
66
- const sourceContext = sources.getStore()?.protect;
66
+ const sourceContext = protect.getSourceContext(patchName);
67
67
 
68
68
  if (!sourceContext || !value) return;
69
69
 
70
70
  const sinkContext = captureStacktrace(
71
71
  { name, value },
72
- { constructorOpt: hooked }
72
+ { constructorOpt: hooked, prependFrames: [orig] }
73
73
  );
74
74
  inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
75
75
  }
@@ -80,10 +80,10 @@ module.exports = function (core) {
80
80
  patcher.patch(obj, method, {
81
81
  name: patchName,
82
82
  patchType,
83
- pre: ({ args, hooked, name }) => {
84
- const sourceContext = sources.getStore()?.protect;
85
- if (!sourceContext) return;
83
+ pre: ({ args, hooked, name, orig }) => {
84
+ const sourceContext = protect.getSourceContext(patchName);
86
85
 
86
+ if (!sourceContext) return;
87
87
 
88
88
  const ops = Array.isArray(args[1])
89
89
  ? args[1]
@@ -93,7 +93,7 @@ module.exports = function (core) {
93
93
  if (value) {
94
94
  const sinkContext = captureStacktrace(
95
95
  { name, value },
96
- { constructorOpt: hooked }
96
+ { constructorOpt: hooked, prependFrames: [orig] }
97
97
  );
98
98
  inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
99
99
  }
@@ -126,15 +126,15 @@ module.exports = function (core) {
126
126
  patcher.patch(mongodb.Collection.prototype, method, {
127
127
  name: `mongodb.Collection.prototype.${method}`,
128
128
  patchType,
129
- pre: ({ args, hooked, name }) => {
129
+ pre: ({ args, hooked, name, orig }) => {
130
130
  const value = typeof args[0] == 'function' ? null : args[0];
131
- const sourceContext = sources.getStore()?.protect;
131
+ const sourceContext = protect.getSourceContext(`mongodb.Collection.prototype.${method}`);
132
132
 
133
133
  if (!sourceContext || !value) return;
134
134
 
135
135
  const sinkContext = captureStacktrace(
136
136
  { name, value },
137
- { constructorOpt: hooked }
137
+ { constructorOpt: hooked, prependFrames: [orig] }
138
138
  );
139
139
  inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
140
140
  },
@@ -144,15 +144,15 @@ module.exports = function (core) {
144
144
  patcher.patch(mongodb.Db.prototype, 'command', {
145
145
  name: 'mongodb.Db.prototype.command',
146
146
  patchType,
147
- pre: ({ args, hooked, name }) => {
147
+ pre: ({ args, hooked, name, orig }) => {
148
148
  const value = args[0]?.filter;
149
- const sourceContext = sources.getStore()?.protect;
149
+ const sourceContext = protect.getSourceContext('mongodb.Collection.prototype.command');
150
150
 
151
151
  if (!sourceContext || !value) return;
152
152
 
153
153
  const sinkContext = captureStacktrace(
154
154
  { name, value },
155
- { constructorOpt: hooked }
155
+ { constructorOpt: hooked, prependFrames: [orig] }
156
156
  );
157
157
  inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
158
158
  }
@@ -168,15 +168,15 @@ module.exports = function (core) {
168
168
  patcher.patch(mongodb.Db.prototype, 'eval', {
169
169
  name: 'mongodb.Db.prototype.eval',
170
170
  patchType,
171
- pre: ({ args, hooked, name }) => {
171
+ pre: ({ args, hooked, name, orig }) => {
172
172
  const value = args[0];
173
- const sourceContext = sources.getStore()?.protect;
173
+ const sourceContext = protect.getSourceContext('mongodb.Db.prototype.eval');
174
174
 
175
175
  if (!sourceContext || !value) return;
176
176
 
177
177
  const sinkContext = captureStacktrace(
178
178
  { name, value },
179
- { constructorOpt: hooked }
179
+ { constructorOpt: hooked, prependFrames: [orig] }
180
180
  );
181
181
 
182
182
  inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
@@ -187,15 +187,15 @@ module.exports = function (core) {
187
187
  depHooks.resolve({ name: 'mongodb', file: 'lib/cursor/find_cursor', version: '>=4.0.0' }, (cursor) => patcher.patch(cursor, 'FindCursor', {
188
188
  name: 'mongodb.FindCursor',
189
189
  patchType,
190
- pre: ({ args, hooked, name }) => {
190
+ pre: ({ args, hooked, name, orig }) => {
191
191
  const value = args[2];
192
- const sourceContext = sources.getStore()?.protect;
192
+ const sourceContext = protect.getSourceContext('mongodb.FindCursor');
193
193
 
194
194
  if (!sourceContext || !value) return;
195
195
 
196
196
  const sinkContext = captureStacktrace(
197
197
  { name, value },
198
- { constructorOpt: hooked }
198
+ { constructorOpt: hooked, prependFrames: [orig] }
199
199
  );
200
200
  inputTracing.nosqlInjectionMongo(sourceContext, sinkContext);
201
201
  }
@@ -23,7 +23,7 @@ module.exports = function(core) {
23
23
  depHooks,
24
24
  patcher,
25
25
  captureStacktrace,
26
- scopes: { sources },
26
+ protect,
27
27
  protect: { inputTracing }
28
28
  } = core;
29
29
 
@@ -49,7 +49,8 @@ module.exports = function(core) {
49
49
  patchType,
50
50
  pre(data) {
51
51
  const { args, hooked, name, orig } = data;
52
- const sourceContext = sources.getStore()?.protect;
52
+ const sourceContext = protect.getSourceContext(name);
53
+
53
54
  if (!sourceContext) return;
54
55
 
55
56
  const value = mysqlInstr.getValueFromArgs(args);
@@ -22,8 +22,8 @@ module.exports = function(core) {
22
22
  const {
23
23
  depHooks,
24
24
  patcher,
25
- scopes: { sources },
26
25
  captureStacktrace,
26
+ protect,
27
27
  protect: { inputTracing }
28
28
  } = core;
29
29
 
@@ -32,8 +32,9 @@ module.exports = function(core) {
32
32
  if (query && isString(query)) return query;
33
33
  }
34
34
 
35
- function preHook({ args, hooked, name }) {
36
- const sourceContext = sources.getStore()?.protect;
35
+ function preHook({ args, hooked, name, orig }) {
36
+ const sourceContext = protect.getSourceContext(name);
37
+
37
38
  if (!sourceContext) return;
38
39
 
39
40
  const value = getQueryFromArgs(args);
@@ -41,7 +42,7 @@ module.exports = function(core) {
41
42
 
42
43
  const sinkContext = captureStacktrace(
43
44
  { name, value },
44
- { constructorOpt: hooked }
45
+ { constructorOpt: hooked, prependFrames: [orig] }
45
46
  );
46
47
 
47
48
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
@@ -20,10 +20,11 @@ const { patchType } = require('../constants');
20
20
 
21
21
  module.exports = function(core) {
22
22
  const {
23
- scopes: { sources, instrumentation },
23
+ scopes: { instrumentation },
24
24
  patcher,
25
25
  depHooks,
26
26
  captureStacktrace,
27
+ protect,
27
28
  protect: { inputTracing }
28
29
  } = core;
29
30
 
@@ -38,18 +39,19 @@ module.exports = function(core) {
38
39
  patcher.patch(sequelize.prototype, 'query', {
39
40
  name,
40
41
  patchType,
41
- pre({ args, hooked, name }) {
42
+ pre({ args, hooked, name, orig }) {
42
43
  if (instrumentation.isLocked()) return;
43
44
 
44
- const sourceContext = sources.getStore()?.protect;
45
- if (!sourceContext) return;
45
+ const sourceContext = protect.getSourceContext(name);
46
+
47
+ if (!sourceContext || sourceContext.allowed) return;
46
48
 
47
49
  const value = getQueryFromArgs(args);
48
50
  if (!value) return;
49
51
 
50
52
  const sinkContext = captureStacktrace(
51
53
  { name, value },
52
- { constructorOpt: hooked }
54
+ { constructorOpt: hooked, prependFrames: [orig] }
53
55
  );
54
56
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
55
57
  }
@@ -20,10 +20,11 @@ const { patchType } = require('../constants');
20
20
 
21
21
  module.exports = function(core) {
22
22
  const {
23
- scopes: { sources, instrumentation },
23
+ scopes: { instrumentation },
24
24
  patcher,
25
25
  depHooks,
26
26
  captureStacktrace,
27
+ protect,
27
28
  protect: { inputTracing }
28
29
  } = core;
29
30
 
@@ -34,10 +35,10 @@ module.exports = function(core) {
34
35
  patcher.patch(sqlite3.Database.prototype, method, {
35
36
  name,
36
37
  patchType,
37
- pre({ args, hooked, name }) {
38
+ pre({ args, hooked, name, orig }) {
38
39
  if (instrumentation.isLocked()) return;
39
40
 
40
- const sourceContext = sources.getStore()?.protect;
41
+ const sourceContext = protect.getSourceContext(name);
41
42
  if (!sourceContext) return;
42
43
 
43
44
  const value = args[0];
@@ -45,8 +46,9 @@ module.exports = function(core) {
45
46
 
46
47
  const sinkContext = captureStacktrace(
47
48
  { name, value },
48
- { constructorOpt: hooked }
49
+ { constructorOpt: hooked, prependFrames: [orig] }
49
50
  );
51
+
50
52
  inputTracing.handleSqlInjection(sourceContext, sinkContext);
51
53
  }
52
54
  });
@@ -0,0 +1,132 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const { isString, isNonEmptyObject } = require('@contrast/common');
19
+ const { patchType } = require('../constants');
20
+
21
+ module.exports = function(core) {
22
+ const {
23
+ scopes: { instrumentation },
24
+ patcher,
25
+ depHooks,
26
+ captureStacktrace,
27
+ protect,
28
+ protect: { inputTracing }
29
+ } = core;
30
+
31
+ function install() {
32
+ depHooks.resolve({ name: 'vm' }, (vm) => {
33
+ ['Script', 'createScript', 'runInContext', 'runInThisContext'].forEach(
34
+ (method) => {
35
+ const name = `vm.${method}`;
36
+
37
+ patcher.patch(vm, method, {
38
+ name,
39
+ patchType,
40
+ pre({ args, hooked, name, orig }) {
41
+ if (instrumentation.isLocked()) return;
42
+
43
+ const sourceContext = protect.getSourceContext(name);
44
+ if (!sourceContext) return;
45
+
46
+ const codeString = args[0];
47
+ if (!codeString || !isString(codeString)) return;
48
+
49
+ const sinkContext = captureStacktrace(
50
+ { name, value: codeString },
51
+ { constructorOpt: hooked, prependFrames: [orig] }
52
+ );
53
+ inputTracing.ssjsInjection(sourceContext, sinkContext);
54
+ }
55
+ });
56
+ }
57
+ );
58
+
59
+ patcher.patch(vm, 'runInNewContext', {
60
+ name: 'vm.runInNewContext',
61
+ patchType,
62
+ pre: ({ args, hooked, orig }) => {
63
+ if (instrumentation.isLocked()) return;
64
+
65
+ const sourceContext = protect.getSourceContext('vm.runInNewContext');
66
+ if (!sourceContext) return;
67
+
68
+ const codeString = args[0];
69
+ const envObj = args[1];
70
+
71
+ if ((!codeString || !isString(codeString)) && (!isNonEmptyObject(envObj))) return;
72
+
73
+ const codeStringSinkContext = (codeString && isString(codeString)) ? captureStacktrace(
74
+ { name: 'vm.runInNewContext', value: codeString },
75
+ { constructorOpt: hooked, prependFrames: [orig] }
76
+ ) : null;
77
+ const envObjSinkContext = isNonEmptyObject(envObj) ? captureStacktrace(
78
+ { name: 'vm.runInNewContext', value: envObj },
79
+ { constructorOpt: hooked, prependFrames: [orig] }
80
+ ) : null;
81
+
82
+ codeStringSinkContext && inputTracing.ssjsInjection(sourceContext, codeStringSinkContext);
83
+ envObjSinkContext && inputTracing.ssjsInjection(sourceContext, envObjSinkContext);
84
+ }
85
+ });
86
+
87
+ patcher.patch(vm, 'createContext', {
88
+ name: 'vm.createContext',
89
+ patchType,
90
+ pre: ({ args, hooked, orig }) => {
91
+ if (instrumentation.isLocked()) return;
92
+
93
+ const sourceContext = protect.getSourceContext('vm.createContext');
94
+ if (!sourceContext) return;
95
+
96
+ const envObj = args[0];
97
+ if (!isNonEmptyObject(envObj)) return;
98
+
99
+ const sinkContext = captureStacktrace(
100
+ { name: 'vm.createContext', value: envObj },
101
+ { constructorOpt: hooked, prependFrames: [orig] }
102
+ );
103
+ inputTracing.ssjsInjection(sourceContext, sinkContext);
104
+ }
105
+ });
106
+
107
+ patcher.patch(vm.Script.prototype, 'runInNewContext', {
108
+ name: 'vm.Script.prototype.runInNewContext',
109
+ patchType,
110
+ pre: ({ args, hooked, orig }) => {
111
+ if (instrumentation.isLocked()) return;
112
+
113
+ const sourceContext = protect.getSourceContext('vm.Script.prototype.runInNewContext');
114
+ if (!sourceContext) return;
115
+
116
+ const envObj = args[0];
117
+ if (!isNonEmptyObject(envObj)) return;
118
+
119
+ const sinkContext = captureStacktrace(
120
+ { name: 'vm.Script.prototype.runInNewContext', value: envObj },
121
+ { constructorOpt: hooked, prependFrames: [orig] }
122
+ );
123
+ inputTracing.ssjsInjection(sourceContext, sinkContext);
124
+ }
125
+ });
126
+ });
127
+ }
128
+
129
+ const vmInstrumentation = inputTracing.vmInstrumentation = { install };
130
+
131
+ return vmInstrumentation;
132
+ };
@@ -16,6 +16,7 @@
16
16
  'use strict';
17
17
 
18
18
  module.exports = function(core) {
19
+ const { protect } = core;
19
20
 
20
21
  function makeSourceContext(req, res) {
21
22
  // make the abstract request. it is an abstraction of a request that
@@ -79,11 +80,10 @@ module.exports = function(core) {
79
80
  // block closure captures res so it isn't exposed to beyond here
80
81
  block: core.protect.makeResponseBlocker(res),
81
82
 
82
- // this should be changed to capture only the rules applicable to this
83
- // particular request (if any route exclusions, etc.)
84
- rules: core.protect.rules,
83
+ policy: protect.getPolicy(),
84
+
85
85
  exclusions: [],
86
- virtualPatches: [],
86
+ virtualPatchesEvaluators: [],
87
87
 
88
88
  // maybe better as result, findings... but my bad naming choice is
89
89
  // past the point of return.
@@ -94,55 +94,14 @@ module.exports = function(core) {
94
94
  // successfully.
95
95
  bodyType: undefined,
96
96
  resultsMap: Object.create(null),
97
+ hardeningResultsMap: Object.create(null),
98
+ semanticResultsMap: Object.create(null),
99
+ serverFeaturesResultsMap: Object.create(null)
97
100
  },
98
-
99
- /*
100
- findings: {
101
- trackRequest: true,
102
- resultsList: [
103
- // Example 2
104
- {
105
- // return value from agent-lib
106
- value: 'kill -9 1',
107
- type: 'PARAMETER_VALUE',
108
- ruleId: 'cmd-injection',
109
- path: ['path', 'to', 'val'],
110
- // other data added during lifecycle
111
- // could we add these by mutating agent-lib return values?
112
- // What if there are multiple injections for the same value? The `details` value
113
- // could be an array in that case, or is this too complicated.
114
- blocked: false,
115
- details: [
116
- {
117
- context: {
118
- id: 'child_process.exec',
119
- get stack() {}, // lazy
120
- command: 'sudo kill -9 1',
121
- index: 5,
122
- }
123
- },
124
- {
125
- sinkId: 'child_process.exec',
126
- get stack() {}, // lazy
127
- command: 'sudo kill -9 1',
128
- index: 5,
129
- }]
130
- },
131
- ]
132
- }
133
- // (scoreAtom() returns only the ruleId and score because the caller supplied
134
- // the input and type; no key or path is known to scoreAtom(). code calling
135
- // scoreAtom() will need to augment the finding to match the above.)
136
- //
137
- // each finding is augmented with additional properties
138
- // - blocked: false // set to true if the finding causes the request to be blocked
139
- // - mappedId: ruleId // normalized ruleId, e.g., nosql-injection-mongo => nosql-injection
140
- // -
141
- // */
142
101
  };
143
102
 
144
103
  return protectStore;
145
104
  }
146
105
 
147
- core.protect.makeSourceContext = makeSourceContext;
106
+ return core.protect.makeSourceContext = makeSourceContext;
148
107
  };
package/lib/policy.js ADDED
@@ -0,0 +1,134 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const {
19
+ Rule,
20
+ ProtectRuleMode: {
21
+ BLOCK_AT_PERIMETER,
22
+ BLOCK,
23
+ MONITOR,
24
+ OFF,
25
+ },
26
+ Rule: { BOT_BLOCKER },
27
+ Event: { SERVER_SETTINGS_UPDATE },
28
+ } = require('@contrast/common');
29
+
30
+
31
+ module.exports = function(core) {
32
+ const { config, logger, messages, protect } = core;
33
+ const policy = protect.policy = {};
34
+
35
+ function getModeFromConfig(ruleId) {
36
+ if (config.protect.disabled_rules.includes(ruleId)) {
37
+ return 'off';
38
+ }
39
+ return config.protect.rules?.[ruleId]?.mode;
40
+ }
41
+
42
+ /**
43
+ * Coerces ContrastUI mode names to match from common-agent-configuration
44
+ * @param {} remoteSetting
45
+ * @returns {string}
46
+ */
47
+ function readModeFromSetting(remoteSetting) {
48
+ switch (remoteSetting.mode) {
49
+ case 'OFF': return OFF;
50
+ case 'MONITORING': return MONITOR;
51
+ case 'BLOCKING': return remoteSetting.blockAtEntry ? BLOCK_AT_PERIMETER : BLOCK;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Build out initial policy from configuration data
57
+ */
58
+ function initPolicy() {
59
+ for (const ruleId of Object.values(Rule)) {
60
+ policy[ruleId] = getModeFromConfig(ruleId) || OFF;
61
+ }
62
+ updateRulesMask();
63
+ }
64
+
65
+ /**
66
+ * When updates are given at runtime, we update our local rule policy while
67
+ * respecting rules of precedence.
68
+ * @param {[]} protectionRules
69
+ */
70
+ function updateFromProtectionRules(protectionRules) {
71
+ for (const remoteSetting of Object.values(protectionRules)) {
72
+ const { id: ruleId } = remoteSetting;
73
+
74
+ if (getModeFromConfig(ruleId)) {
75
+ continue;
76
+ }
77
+
78
+ policy[ruleId] = readModeFromSetting(remoteSetting);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Rebuild rules mask based on which agent-lib rules are enabled
84
+ */
85
+ function updateRulesMask() {
86
+ let rulesMask = 0;
87
+ for (const [ruleId, mode] of Object.entries(policy)) {
88
+ if (protect.agentLib.RuleType[ruleId] && mode !== OFF) {
89
+ rulesMask = rulesMask | protect.agentLib.RuleType[ruleId];
90
+ }
91
+ }
92
+ policy.rulesMask = rulesMask;
93
+ }
94
+
95
+ /**
96
+ * This gets called by protect.makeSourceContext(). We return copy of policy to avoid
97
+ * inconsistent behavior if policy is updated during request handling.
98
+ */
99
+ function getPolicy() {
100
+ return { ...policy };
101
+ }
102
+
103
+ initPolicy();
104
+
105
+ messages.on(SERVER_SETTINGS_UPDATE, (remoteSettings) => {
106
+ let update;
107
+
108
+ const protectionRules = remoteSettings?.settings?.defend?.protectionRules
109
+ if (protectionRules) {
110
+ updateFromProtectionRules(protectionRules);
111
+ update = 'application-settings';
112
+ }
113
+
114
+ if (remoteSettings?.features?.defend) {
115
+ const bbEnabled = remoteSettings.features.defend[BOT_BLOCKER];
116
+
117
+ if (
118
+ bbEnabled != null &&
119
+ !config.protect.disabled_rules.includes(BOT_BLOCKER) &&
120
+ !config.protect.rules?.[BOT_BLOCKER]?.mode
121
+ ) {
122
+ policy[BOT_BLOCKER] = bbEnabled ? BLOCK_AT_PERIMETER : OFF;
123
+ update = 'server-features';
124
+ }
125
+ }
126
+
127
+ if (update) {
128
+ updateRulesMask();
129
+ logger.info({ policy: protect.policy }, `protect policy updated from ${update}`);
130
+ }
131
+ });
132
+
133
+ return protect.getPolicy = getPolicy;
134
+ };