@contrast/protect 1.38.0 → 1.40.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 (58) hide show
  1. package/lib/error-handlers/common-handler.test.js +52 -0
  2. package/lib/error-handlers/index.test.js +32 -0
  3. package/lib/error-handlers/init-domain.test.js +34 -0
  4. package/lib/error-handlers/install/express4.test.js +238 -0
  5. package/lib/error-handlers/install/fastify.test.js +130 -0
  6. package/lib/error-handlers/install/hapi.test.js +102 -0
  7. package/lib/error-handlers/install/koa2.test.js +83 -0
  8. package/lib/error-handlers/install/restify.test.js +57 -0
  9. package/lib/get-source-context.test.js +35 -0
  10. package/lib/hardening/handlers.test.js +89 -0
  11. package/lib/hardening/index.test.js +31 -0
  12. package/lib/hardening/install/node-serialize0.test.js +61 -0
  13. package/lib/index.test.js +53 -0
  14. package/lib/input-analysis/handlers.test.js +1604 -0
  15. package/lib/input-analysis/index.test.js +45 -0
  16. package/lib/input-analysis/install/body-parser1.test.js +134 -0
  17. package/lib/input-analysis/install/busboy1.test.js +81 -0
  18. package/lib/input-analysis/install/cookie-parser1.test.js +144 -0
  19. package/lib/input-analysis/install/express4.test.js +208 -0
  20. package/lib/input-analysis/install/fastify.test.js +96 -0
  21. package/lib/input-analysis/install/formidable1.test.js +114 -0
  22. package/lib/input-analysis/install/hapi.test.js +300 -0
  23. package/lib/input-analysis/install/http.test.js +264 -0
  24. package/lib/input-analysis/install/koa-body5.test.js +92 -0
  25. package/lib/input-analysis/install/koa-bodyparser4.test.js +92 -0
  26. package/lib/input-analysis/install/koa2.test.js +259 -0
  27. package/lib/input-analysis/install/multer1.test.js +209 -0
  28. package/lib/input-analysis/install/qs6.test.js +79 -0
  29. package/lib/input-analysis/install/restify.test.js +98 -0
  30. package/lib/input-analysis/install/universal-cookie4.test.js +70 -0
  31. package/lib/input-analysis/ip-analysis.test.js +71 -0
  32. package/lib/input-analysis/virtual-patches.test.js +106 -0
  33. package/lib/input-tracing/handlers/index.test.js +1236 -0
  34. package/lib/input-tracing/index.test.js +62 -0
  35. package/lib/input-tracing/install/child-process.test.js +133 -0
  36. package/lib/input-tracing/install/eval.test.js +78 -0
  37. package/lib/input-tracing/install/fs.test.js +108 -0
  38. package/lib/input-tracing/install/function.test.js +81 -0
  39. package/lib/input-tracing/install/http.test.js +85 -0
  40. package/lib/input-tracing/install/http2.test.js +83 -0
  41. package/lib/input-tracing/install/marsdb.test.js +126 -0
  42. package/lib/input-tracing/install/mongodb.test.js +282 -0
  43. package/lib/input-tracing/install/mssql.test.js +81 -0
  44. package/lib/input-tracing/install/mysql.test.js +108 -0
  45. package/lib/input-tracing/install/postgres.test.js +125 -0
  46. package/lib/input-tracing/install/sequelize.test.js +78 -0
  47. package/lib/input-tracing/install/spdy.test.js +76 -0
  48. package/lib/input-tracing/install/sqlite3.test.js +88 -0
  49. package/lib/input-tracing/install/vm.test.js +176 -0
  50. package/lib/make-response-blocker.test.js +99 -0
  51. package/lib/make-source-context.test.js +219 -0
  52. package/lib/policy.test.js +446 -0
  53. package/lib/semantic-analysis/handlers.test.js +379 -0
  54. package/lib/semantic-analysis/index.test.js +38 -0
  55. package/lib/semantic-analysis/install/libxmljs.test.js +156 -0
  56. package/lib/semantic-analysis/utils/xml-analysis.test.js +156 -0
  57. package/lib/throw-security-exception.test.js +37 -0
  58. package/package.json +5 -5
@@ -0,0 +1,219 @@
1
+ 'use strict';
2
+
3
+ const sinon = require('sinon');
4
+ const { expect } = require('chai');
5
+ const { Rule, Event } = require('@contrast/common');
6
+ const { initProtectFixture } = require('@contrast/test/fixtures');
7
+
8
+ /* eslint-disable newline-per-chained-call */
9
+
10
+ describe('protect make-source-context', function () {
11
+ let core;
12
+
13
+ beforeEach(function () {
14
+ ({ core } = initProtectFixture());
15
+ core.config.protect.rules = makeProtectRulesConfig('monitor');
16
+ });
17
+
18
+ describe('makeSourceContext() for policy', function () {
19
+ it('builds policy with rulesMask and modes', function () {
20
+ require('../../protect')(core);
21
+
22
+ const [req, res] = makeReqRes({}, {});
23
+ const sc = core.protect.makeSourceContext(req, res);
24
+
25
+ expect(sc.block).to.be.a('function');
26
+ expect(sc.policy).to.deep.equal({
27
+ rulesMask: 511,
28
+ exclusions: {
29
+ ignoreQuerystring: false,
30
+ querystringPolicy: null,
31
+ ignoreBody: false,
32
+ bodyPolicy: null,
33
+ header: [],
34
+ cookie: [],
35
+ parameter: [],
36
+ },
37
+ ...Object.values(Rule).reduce((acc, ruleId) => ({ ...acc, [ruleId]: 'monitor' }), {}),
38
+ });
39
+ expect(sc.exclusions).to.deep.equal([]);
40
+ expect(sc.virtualPatchesEvaluators).to.deep.equal([]);
41
+ expect(sc.trackRequest).to.equal(false);
42
+ expect(sc.securityException).to.be.undefined;
43
+ expect(sc.bodyType).to.be.undefined;
44
+ expect(sc.resultsMap).to.eql(Object.create(null));
45
+ });
46
+ });
47
+
48
+ describe('makeSourceContext() for req, res', function () {
49
+ const ruleId = 'reflected-xss';
50
+ const expectedMode = 'monitor';
51
+ const expectedRulesMask = 4;
52
+
53
+ const tests = [
54
+ { desc: 'works with unmodified req', req: {}, res: {} },
55
+ { desc: 'handles an uppercase header name', req: { rawHeaders: ['Accept', '*'] }, res: {} },
56
+ { desc: 'does not modify a header value', req: { rawHeaders: ['Accept', 'TEXT/HTmL'] }, res: {} },
57
+ { desc: 'handles an empty url', req: { url: '' }, res: {} },
58
+ { desc: 'handles search params', req: { url: '/mimsy?borogroves' }, res: {} },
59
+ { desc: 'handles only search params', req: { url: '?alice' }, res: {} },
60
+ { desc: 'detects multipart content-type', req: { rawHeaders: ['Content-type', 'multipart/x-www-form-urlencoded'] }, res: {} },
61
+ { desc: 'blocks when headers not sent', req: {}, res: { writeHead: sinon.stub(), end: sinon.stub() } },
62
+ { desc: 'blocks when headers sent', req: {}, res: { writeHead: sinon.stub(), end: sinon.stub(), headersSent: true } },
63
+ ];
64
+
65
+ tests.forEach((t) => {
66
+ it(t.desc, function () {
67
+ core.config.protect.rules = makeProtectRulesConfig('off');
68
+ core.config.protect.rules['reflected-xss'] = { mode: 'monitor' };
69
+
70
+ require('../../protect')(core);
71
+
72
+ const [req, res] = makeReqRes(t.req, t.res);
73
+
74
+ // default to setting headers but not including a content-type
75
+ let contentType = '';
76
+ if (!t.req.rawHeaders) {
77
+ // the test doesn't set the headers so they are the default
78
+ contentType = 'application/x-www-form-urlencoded';
79
+ } else if (t.req.rawHeaders[t.req.rawHeaders.length - 1].startsWith('multipart')) {
80
+ // the test does set the content-type header (inferred)
81
+ contentType = t.req.rawHeaders[t.req.rawHeaders.length - 1];
82
+ }
83
+ const headers = req.rawHeaders.map((h, ix) => ix & 1 ? h : h.toLowerCase());
84
+
85
+ // this is what is really being tested.
86
+ const sc = core.protect.makeSourceContext(req, res);
87
+
88
+
89
+ const expectedReqData = {
90
+ method: 'get',
91
+ headers,
92
+ uriPath: '/',
93
+ queries: '',
94
+ httpVersion: '1',
95
+ ip: '127.1.1.0',
96
+ contentType,
97
+ };
98
+ if (t.req.rawHeaders) {
99
+ expectedReqData.headers = t.req.rawHeaders.map((h, i) => {
100
+ if (i & 1 && h.startsWith('multipart')) {
101
+ expectedReqData.contentType = h;
102
+ }
103
+ return i & 1 ? h : h.toLowerCase();
104
+ });
105
+ }
106
+ if (t.req.url !== undefined) {
107
+ expectedReqData.uriPath = t.req.url;
108
+ expectedReqData.queries = '';
109
+ const ix = t.req.url.indexOf('?');
110
+ if (ix >= 0) {
111
+ expectedReqData.uriPath = t.req.url.slice(0, ix);
112
+ expectedReqData.queries = t.req.url.slice(ix + 1);
113
+ }
114
+ }
115
+
116
+ // check that req and res make the expected abstract request
117
+ expect(sc).property('reqData').to.deep.equal(expectedReqData);
118
+
119
+ // if a block test, does it work as expected?
120
+ if (t.res.writeHead) {
121
+ sc.block('mode', 'reflected-xss');
122
+ if (!t.res.headersSent) {
123
+ expect(t.res.writeHead.callCount).to.equal(1);
124
+ } else {
125
+ expect(t.res.writeHead.callCount).to.equal(0);
126
+ }
127
+ expect(t.res.end.callCount).to.equal(1);
128
+ }
129
+
130
+ // check constant items
131
+ expect(sc.block).to.be.a('function');
132
+ expect(sc.policy.rulesMask).to.equal(expectedRulesMask);
133
+ expect(sc.policy[ruleId]).to.equal(expectedMode);
134
+ expect(sc.exclusions).to.deep.equal([]);
135
+ expect(sc.virtualPatchesEvaluators).to.deep.equal([]);
136
+
137
+ expect(sc.trackRequest).to.equal(false);
138
+ expect(sc.securityException).to.be.undefined;
139
+ expect(sc.bodyType).to.be.undefined;
140
+ expect(sc.resultsMap).to.eql(Object.create(null));
141
+ });
142
+ });
143
+ });
144
+
145
+ describe('handles a case where the returned policy is null so the request is allowed', function () {
146
+ beforeEach(function () {
147
+ require('../../protect')(core);
148
+ const exclusionDtm = {
149
+ urls: [
150
+ '.*'
151
+ ],
152
+ matchStrategy: 'ONLY',
153
+ protect_rules: [],
154
+ modes: [
155
+ 'defend'
156
+ ],
157
+ name: 'UrlExclusionAllRules'
158
+ };
159
+
160
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
161
+ exclusions: {
162
+ input: [],
163
+ url: [exclusionDtm]
164
+ }
165
+ });
166
+ });
167
+
168
+ it('returns an sourceContext with only allowed property set to true', function () {
169
+ const sc = core.protect.makeSourceContext(...makeReqRes());
170
+
171
+ expect(sc).to.deep.equal({ allowed: true });
172
+ });
173
+ });
174
+ });
175
+
176
+ //
177
+ // make a req and res pair
178
+ //
179
+ function makeReqRes(req = {}, res = {}) {
180
+ const defaultReq = {
181
+ method: 'get',
182
+ url: '/',
183
+ httpVersion: '1',
184
+ socket: {
185
+ remoteAddress: '127.1.1.0'
186
+ },
187
+ rawHeaders: getRawHeaders(),
188
+ };
189
+
190
+ const defaultRes = {
191
+ headersSent: false,
192
+ writeHead() { },
193
+ end() { },
194
+ };
195
+
196
+ return [
197
+ Object.assign({}, defaultReq, req),
198
+ Object.assign({}, defaultRes, res),
199
+ ];
200
+ }
201
+
202
+ function getRawHeaders() {
203
+ return [
204
+ 'accept', 'text/html',
205
+ 'accept-encoding', 'gzip, deflate, br',
206
+ 'user-agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.',
207
+ 'content-type', 'application/x-www-form-urlencoded'
208
+ ];
209
+ }
210
+
211
+ //
212
+ // create the config for protect's rules
213
+ //
214
+ function makeProtectRulesConfig(mode = 'monitor') {
215
+ return Object.values(Rule).reduce((acc, ruleId) => ({
216
+ ...acc,
217
+ [ruleId]: { mode }
218
+ }), { disabled_rules: [] });
219
+ }
@@ -0,0 +1,446 @@
1
+ 'use strict';
2
+
3
+ const { expect } = require('chai');
4
+ const sinon = require('sinon');
5
+ const {
6
+ ConfigSource: { ENVIRONMENT_VARIABLE }, ConfigSource
7
+ } = require('@contrast/config');
8
+ const { Rule, Event } = require('@contrast/common');
9
+ const { initProtectFixture } = require('@contrast/test/fixtures');
10
+ const mocks = require('@contrast/test/mocks');
11
+ const exclusionSettingsMessage = require('@contrast/test/data/server-settings/exclusions.json');
12
+ const policyFactory = require('./policy');
13
+
14
+ describe('protect policy', function () {
15
+ let core;
16
+
17
+ beforeEach(function () {
18
+ [
19
+ ['cmd-injection', 'block'],
20
+ ['path-traversal', 'off'],
21
+ ['reflected-xss', 'block_at_perimeter'],
22
+ ['sql-injection', 'monitor'],
23
+ ].forEach(([ruleId, mode]) => {
24
+ process.env[`CONTRAST__PROTECT__RULES__${ruleId.toUpperCase().replace('-', '_')}__MODE`] = mode;
25
+ });
26
+
27
+ ({ core } = initProtectFixture());
28
+ [
29
+ ['cmd-injection', 'block'],
30
+ ['path-traversal', 'off'],
31
+ ['reflected-xss', 'block_at_perimeter'],
32
+ ['sql-injection', 'monitor'],
33
+ ].forEach(([ruleId, mode]) => {
34
+ const name = `protect.rules.${ruleId.replace('-', '_')}.mode`;
35
+ core.config.setValue(name, mode, ENVIRONMENT_VARIABLE);
36
+ });
37
+
38
+ core.protect = mocks.protect();
39
+ });
40
+
41
+ describe('initialzation and updates', function () {
42
+ beforeEach(function () {
43
+ core.config.protect.rules.disabled_rules = ['sql-injection'];
44
+ [
45
+ ['cmd-injection', 'block'],
46
+ ['path-traversal', 'off'],
47
+ ['reflected-xss', 'block_at_perimeter'],
48
+ ['sql-injection', 'monitor'],
49
+ ].forEach(([ruleId, mode]) => {
50
+ core.config.setValue(`protect.rules.${ruleId}.mode`, mode, ENVIRONMENT_VARIABLE);
51
+ });
52
+
53
+ policyFactory(core);
54
+ });
55
+
56
+ it('initializes policy from configuration', function () {
57
+ const policy = core.protect.getPolicy();
58
+
59
+ expect(policy).to.deep.include({
60
+ rulesMask: 20,
61
+ 'bot-blocker': 'off',
62
+ 'cmd-injection': 'block',
63
+ 'cmd-injection-command-backdoors': 'off',
64
+ 'cmd-injection-semantic-chained-commands': 'off',
65
+ 'cmd-injection-semantic-dangerous-paths': 'off',
66
+ 'ip-denylist': 'off',
67
+ 'method-tampering': 'off',
68
+ 'nosql-injection': 'off',
69
+ 'nosql-injection-mongo': 'off',
70
+ 'path-traversal': 'off',
71
+ 'path-traversal-semantic-file-security-bypass': 'off',
72
+ 'reflected-xss': 'block_at_perimeter',
73
+ 'sql-injection': 'off',
74
+ 'ssjs-injection': 'off',
75
+ 'ssrf': 'off',
76
+ 'unsafe-code-execution': 'off',
77
+ 'unsafe-file-upload': 'off',
78
+ 'untrusted-deserialization': 'off',
79
+ 'unvalidated-redirect': 'off',
80
+ 'virtual-patch': 'off',
81
+ 'xxe': 'off',
82
+ exclusions: {
83
+ bodyPolicy: null,
84
+ cookie: [],
85
+ header: [],
86
+ ignoreBody: false,
87
+ ignoreQuerystring: false,
88
+ parameter: [],
89
+ querystringPolicy: null,
90
+ }
91
+ });
92
+ });
93
+
94
+ it('updates policy from app settings and server features', function () {
95
+ let policy = core.protect.getPolicy();
96
+
97
+ expect(policy).to.deep.include({
98
+ rulesMask: 20,
99
+ 'bot-blocker': 'off',
100
+ 'cmd-injection': 'block',
101
+ 'cmd-injection-command-backdoors': 'off',
102
+ 'cmd-injection-semantic-chained-commands': 'off',
103
+ 'cmd-injection-semantic-dangerous-paths': 'off',
104
+ 'ip-denylist': 'off',
105
+ 'method-tampering': 'off',
106
+ 'nosql-injection': 'off',
107
+ 'nosql-injection-mongo': 'off',
108
+ 'path-traversal': 'off',
109
+ 'path-traversal-semantic-file-security-bypass': 'off',
110
+ 'reflected-xss': 'block_at_perimeter',
111
+ 'sql-injection': 'off',
112
+ 'ssjs-injection': 'off',
113
+ 'ssrf': 'off',
114
+ 'unsafe-code-execution': 'off',
115
+ 'unsafe-file-upload': 'off',
116
+ 'untrusted-deserialization': 'off',
117
+ 'unvalidated-redirect': 'off',
118
+ 'virtual-patch': 'off',
119
+ 'xxe': 'off',
120
+ exclusions: {
121
+ bodyPolicy: null,
122
+ cookie: [],
123
+ header: [],
124
+ ignoreBody: false,
125
+ ignoreQuerystring: false,
126
+ parameter: [],
127
+ querystringPolicy: null,
128
+ }
129
+ });
130
+
131
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
132
+ protect: {
133
+ rules: {
134
+ 'cmd-injection': { mode: 'MONITOR' },
135
+ 'method-tampering': { mode: 'BLOCK_AT_PERIMETER' },
136
+ 'nosql-injection': { mode: 'MONITOR' },
137
+ 'ssjs-injection': { mode: 'BLOCK' },
138
+ }
139
+ }
140
+ });
141
+
142
+ policy = core.protect.getPolicy();
143
+
144
+ expect(policy).to.deep.include({
145
+ rulesMask: 436,
146
+ 'bot-blocker': 'off',
147
+ 'cmd-injection': 'block',
148
+ 'cmd-injection-command-backdoors': 'off',
149
+ 'cmd-injection-semantic-chained-commands': 'off',
150
+ 'cmd-injection-semantic-dangerous-paths': 'off',
151
+ 'ip-denylist': 'off',
152
+ 'method-tampering': 'block_at_perimeter',
153
+ 'nosql-injection': 'monitor',
154
+ 'nosql-injection-mongo': 'monitor',
155
+ 'path-traversal': 'off',
156
+ 'path-traversal-semantic-file-security-bypass': 'off',
157
+ 'reflected-xss': 'block_at_perimeter',
158
+ 'sql-injection': 'off',
159
+ 'ssjs-injection': 'block',
160
+ 'ssrf': 'off',
161
+ 'unsafe-code-execution': 'off',
162
+ 'unsafe-file-upload': 'off',
163
+ 'untrusted-deserialization': 'off',
164
+ 'unvalidated-redirect': 'off',
165
+ 'virtual-patch': 'off',
166
+ 'xxe': 'off',
167
+ exclusions: {
168
+ bodyPolicy: null,
169
+ cookie: [],
170
+ header: [],
171
+ ignoreBody: false,
172
+ ignoreQuerystring: false,
173
+ parameter: [],
174
+ querystringPolicy: null,
175
+ }
176
+ });
177
+
178
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
179
+ protect: {
180
+ rules: {
181
+ bot_blocker: {
182
+ enable: true
183
+ }
184
+ }
185
+ }
186
+ });
187
+
188
+ policy = core.protect.getPolicy();
189
+
190
+ expect(policy).to.deep.include({
191
+ rulesMask: 500,
192
+ 'bot-blocker': 'block_at_perimeter',
193
+ 'cmd-injection': 'block',
194
+ 'cmd-injection-command-backdoors': 'off',
195
+ 'cmd-injection-semantic-chained-commands': 'off',
196
+ 'cmd-injection-semantic-dangerous-paths': 'off',
197
+ 'ip-denylist': 'off',
198
+ 'method-tampering': 'block_at_perimeter',
199
+ 'nosql-injection': 'monitor',
200
+ 'nosql-injection-mongo': 'monitor',
201
+ 'path-traversal': 'off',
202
+ 'path-traversal-semantic-file-security-bypass': 'off',
203
+ 'reflected-xss': 'block_at_perimeter',
204
+ 'sql-injection': 'off',
205
+ 'ssjs-injection': 'block',
206
+ 'ssrf': 'off',
207
+ 'unsafe-code-execution': 'off',
208
+ 'unsafe-file-upload': 'off',
209
+ 'untrusted-deserialization': 'off',
210
+ 'unvalidated-redirect': 'off',
211
+ 'virtual-patch': 'off',
212
+ 'xxe': 'off',
213
+ exclusions: {
214
+ bodyPolicy: null,
215
+ cookie: [],
216
+ header: [],
217
+ ignoreBody: false,
218
+ ignoreQuerystring: false,
219
+ parameter: [],
220
+ querystringPolicy: null,
221
+ }
222
+ });
223
+ });
224
+
225
+ describe('special case: nosql-injection-mongo', function() {
226
+ it('nosql-injection-mongo will not get updated by nosql-injection settings if set in config', function () {
227
+ core.config.setValue(`protect.rules.${Rule.NOSQL_INJECTION_MONGO}.mode`, 'block', ConfigSource.USER_CONFIGURATION_FILE);
228
+ const getPolicy = policyFactory(core);
229
+ let policy = getPolicy();
230
+
231
+ expect(policy).to.deep.include({
232
+ 'nosql-injection': 'off',
233
+ 'nosql-injection-mongo': 'block',
234
+ });
235
+
236
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
237
+ protect: {
238
+ rules: {
239
+ 'nosql-injection': { mode: 'MONITOR' },
240
+ }
241
+ }
242
+ });
243
+
244
+ policy = core.protect.getPolicy();
245
+ expect(policy).to.deep.include({
246
+ 'nosql-injection': 'monitor',
247
+ 'nosql-injection-mongo': 'block',
248
+ });
249
+ });
250
+
251
+ it('nosql-injection-mongo will inherit nosql-injection config if unset itself', function () {
252
+ core.config.setValue(`protect.rules.${Rule.NOSQL_INJECTION}.mode`, 'block', ConfigSource.USER_CONFIGURATION_FILE);
253
+ const getPolicy = policyFactory(core);
254
+ const policy = getPolicy();
255
+
256
+ expect(policy).to.deep.include({
257
+ 'nosql-injection': 'block',
258
+ 'nosql-injection-mongo': 'block',
259
+ });
260
+ });
261
+ });
262
+ });
263
+
264
+ describe('policy building accounts for UI-defined exclusions', function () {
265
+ beforeEach(function () {
266
+ core.config.protect.rules['path-traversal'].mode = 'monitor';
267
+ core.config.protect.rules['sql-injection'].mode = 'monitor';
268
+ core.config.protect.rules['cmd-injection'].mode = 'monitor';
269
+ core.config.protect.rules['reflected-xss'].mode = 'monitor';
270
+ core.config.protect.rules.disabled_rules = [];
271
+ policyFactory(core);
272
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, exclusionSettingsMessage);
273
+ });
274
+
275
+ const baseline = {
276
+ rulesMask: 30,
277
+ 'bot-blocker': 'off',
278
+ 'cmd-injection': 'monitor',
279
+ 'cmd-injection-command-backdoors': 'off',
280
+ 'cmd-injection-semantic-chained-commands': 'off',
281
+ 'cmd-injection-semantic-dangerous-paths': 'off',
282
+ 'ip-denylist': 'off',
283
+ 'method-tampering': 'off',
284
+ 'nosql-injection': 'off',
285
+ 'nosql-injection-mongo': 'off',
286
+ 'path-traversal': 'monitor',
287
+ 'path-traversal-semantic-file-security-bypass': 'off',
288
+ 'reflected-xss': 'monitor',
289
+ 'sql-injection': 'monitor',
290
+ 'ssjs-injection': 'off',
291
+ 'unsafe-code-execution': 'off',
292
+ 'unsafe-file-upload': 'off',
293
+ 'untrusted-deserialization': 'off',
294
+ 'virtual-patch': 'off',
295
+ 'xxe': 'off',
296
+ exclusions: {
297
+ ignoreQuerystring: false,
298
+ querystringPolicy: null,
299
+ ignoreBody: false,
300
+ bodyPolicy: null,
301
+ cookie: [],
302
+ header: [],
303
+ parameter: [{
304
+ policy: { 'sql-injection': 'off' }
305
+ }],
306
+ }
307
+ };
308
+
309
+ [
310
+ { uriPath: 'baseline', expectedPolicy: baseline },
311
+ { uriPath: '/url-exclusion-all-rules', expectedPolicy: null },
312
+ {
313
+ uriPath: '/url-exclusion-some-rules',
314
+ expectedPolicy: {
315
+ ...baseline,
316
+ rulesMask: 28,
317
+ 'path-traversal': 'off',
318
+ }
319
+ },
320
+ {
321
+ uriPath: '/input-exclusion-some-rules',
322
+ expectedPolicy: {
323
+ ...baseline,
324
+ exclusions: {
325
+ parameter: [{
326
+ policy: {
327
+ 'sql-injection': 'off',
328
+ }
329
+ }]
330
+ }
331
+ }
332
+ },
333
+ {
334
+ uriPath: '/input-exclusion-more-rules',
335
+ expectedPolicy: {
336
+ ...baseline,
337
+ exclusions: {
338
+ header: [{
339
+ policy: {
340
+ 'cmd-injection': 'off',
341
+ 'sql-injection': 'off',
342
+ 'path-traversal': 'off',
343
+ 'reflected-xss': 'off',
344
+ }
345
+ }],
346
+ parameter: [{
347
+ policy: { 'sql-injection': 'off' }
348
+ }, {
349
+ policy: {
350
+ 'cmd-injection': 'off',
351
+ 'sql-injection': 'off',
352
+ 'path-traversal': 'off',
353
+ }
354
+ }]
355
+ }
356
+ }
357
+ },
358
+ {
359
+ uriPath: '/input-exclusion-body-some-rules',
360
+ expectedPolicy: {
361
+ ...baseline,
362
+ exclusions: {
363
+ bodyPolicy: {
364
+ 'cmd-injection': 'off',
365
+ 'sql-injection': 'off',
366
+ 'path-traversal': 'off',
367
+ 'reflected-xss': 'off',
368
+ }
369
+ }
370
+ }
371
+ },
372
+ {
373
+ uriPath: '/input-exclusion-body-all-rules',
374
+ expectedPolicy: {
375
+ ...baseline,
376
+ exclusions: {
377
+ ignoreBody: true
378
+ }
379
+ }
380
+ },
381
+ {
382
+ uriPath: '/input-exclusion-querystring-some-rules',
383
+ expectedPolicy: {
384
+ ...baseline,
385
+ exclusions: {
386
+ querystringPolicy: {
387
+ 'nosql-injection-mongo': 'off',
388
+ 'sql-injection': 'off',
389
+ 'path-traversal': 'off',
390
+ 'reflected-xss': 'off',
391
+ }
392
+ }
393
+ }
394
+ },
395
+ {
396
+ uriPath: '/input-exclusion-querystring-all-rules',
397
+ expectedPolicy: {
398
+ ...baseline,
399
+ exclusions: {
400
+ ignoreQuerystring: true
401
+ }
402
+ },
403
+ },
404
+ {
405
+ uriPath: '/input-exclusion-cookie-all-rules',
406
+ expectedPolicy: {
407
+ ...baseline,
408
+ exclusions: {
409
+ cookie: [{
410
+ name: '.*',
411
+ }],
412
+ }
413
+ }
414
+ }
415
+ ].forEach(({ uriPath, expectedPolicy }) => {
416
+ it(`builds exclusions correctly for uriPath=${uriPath}`, function () {
417
+ expect(core.protect.getPolicy({ uriPath })).to.be.like(expectedPolicy);
418
+
419
+ });
420
+ });
421
+ });
422
+
423
+ describe('handles errors during exclusion compilation', function () {
424
+ it('catches the error and logs a message for it', function () {
425
+ const exclusionDtm = {
426
+ urls: [
427
+ '.*******'
428
+ ],
429
+ matchStrategy: 'ONLY',
430
+ protect_rules: [],
431
+ modes: [
432
+ 'defend'
433
+ ],
434
+ name: 'UrlExclusionAllRules'
435
+ };
436
+
437
+ core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
438
+ exclusions: {
439
+ input: [],
440
+ url: [exclusionDtm]
441
+ }
442
+ });
443
+ expect(core.logger.error).to.have.been.calledOnceWithExactly({ err: sinon.match.any, exclusionDtm }, 'failed to process exclusion');
444
+ });
445
+ });
446
+ });