@contrast/protect 1.0.1 → 1.1.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 (29) hide show
  1. package/lib/error-handlers/index.js +2 -0
  2. package/lib/error-handlers/install/koa2.js +52 -0
  3. package/lib/input-analysis/index.js +2 -0
  4. package/lib/input-analysis/install/co-body.js +51 -0
  5. package/lib/input-analysis/install/cookie-parser.js +48 -0
  6. package/lib/input-analysis/install/formidable.js +53 -0
  7. package/lib/input-analysis/install/koa2.js +137 -0
  8. package/lib/input-analysis/install/multer.js +52 -0
  9. package/lib/input-analysis/install/qs.js +40 -0
  10. package/lib/input-analysis/install/universal-cookie.js +34 -0
  11. package/package.json +2 -1
  12. package/lib/error-handlers/install/fastify3.test.js +0 -142
  13. package/lib/esm-loader.test.mjs +0 -11
  14. package/lib/index.test.js +0 -32
  15. package/lib/input-analysis/handlers.test.js +0 -898
  16. package/lib/input-analysis/index.test.js +0 -28
  17. package/lib/input-analysis/install/fastify3.test.js +0 -71
  18. package/lib/input-analysis/install/http.test.js +0 -315
  19. package/lib/input-tracing/handlers/index.test.js +0 -395
  20. package/lib/input-tracing/install/child-process.test.js +0 -112
  21. package/lib/input-tracing/install/fs.test.js +0 -118
  22. package/lib/input-tracing/install/mysql.test.js +0 -108
  23. package/lib/input-tracing/install/postgres.test.js +0 -125
  24. package/lib/input-tracing/install/sequelize.test.js +0 -79
  25. package/lib/input-tracing/install/sqlite3.test.js +0 -88
  26. package/lib/make-response-blocker.test.js +0 -88
  27. package/lib/make-source-context.test.js +0 -298
  28. package/lib/throw-security-exception.test.js +0 -50
  29. package/lib/utils.test.js +0 -40
@@ -1,298 +0,0 @@
1
- 'use strict';
2
-
3
- const { expect } = require('chai');
4
- const sinon = require('sinon');
5
-
6
- const mocks = require('../../test/mocks');
7
-
8
- // rules in config
9
- const rulesCfg = [
10
- { id: 'bot-blocker', where: 'agent-lib-input' },
11
- { id: 'cmd-injection', where: 'agent-lib-input' },
12
- { id: 'cmd-injection-command-backdoors', where: 'agent' },
13
- { id: 'cmd-injection-semantic-chained-commands', where: 'agent' },
14
- { id: 'cmd-injection-semantic-dangerous-paths', where: 'agent' },
15
- { id: 'ip-denylist', where: 'agent' },
16
- { id: 'method-tampering', where: 'agent-lib-input' },
17
- { id: 'nosql-injection-mongo', where: 'agent-lib-input' },
18
- { id: 'path-traversal', where: 'agent-lib-input' },
19
- { id: 'reflected-xss', where: 'agent-lib-input' },
20
- { id: 'sql-injection', where: 'agent-lib-input' },
21
- { id: 'ssjs-injection', where: 'agent-lib-input' },
22
- { id: 'virtual-patch', where: 'agent' },
23
- { id: 'untrusted-deserialization', where: 'agent' },
24
- { id: 'unsafe-file-upload', where: 'agent-lib-input' },
25
- { id: 'xxe', where: 'agent' },
26
- ];
27
-
28
- const rules = rulesCfg.map((r) => r.id);
29
- const agentLibRules = rulesCfg.filter((r) => r.where === 'agent-lib-input').map((r) => r.id);
30
- const agentRules = rulesCfg.filter((r) => r.where !== 'agent-lib-input').map((r) => r.id);
31
-
32
- /* eslint-disable newline-per-chained-call */
33
-
34
- describe('protect make-source-context', function() {
35
- let core;
36
-
37
- beforeEach(function() {
38
- core = mocks.core();
39
- core.logger = mocks.logger();
40
- core.config = mocks.config();
41
- core.scopes = mocks.scopes();
42
- core.config.protect.rules = makeProtectRulesConfig(rules);
43
- core.scopes = mocks.scopes();
44
- require('../../protect')(core);
45
- });
46
-
47
- it('mock core.config.protect.rules are initialized correctly', function() {
48
- // shorthand
49
- const protectRules = core.config.protect.rules;
50
- for (const ruleId of rules) {
51
- expect(protectRules).property(ruleId).eql({ mode: 'monitor' });
52
- }
53
- });
54
-
55
- it('core.protect.rules is initialized correctly from mock config', function() {
56
- const { protect } = core;
57
-
58
- expect(protect).property('agentLib').an('object');
59
- expect(protect.agentLib).property('RuleType').eql({
60
- 'unsafe-file-upload': 1,
61
- 'path-traversal': 2,
62
- 'reflected-xss': 4,
63
- 'sql-injection': 8,
64
- 'cmd-injection': 16,
65
- 'nosql-injection-mongo': 32,
66
- 'bot-blocker': 64,
67
- 'ssjs-injection': 128,
68
- 'method-tampering': 256,
69
- });
70
- expect(protect.agentLib).property('MongoQueryType');
71
- expect(protect.agentLib).property('DbType');
72
-
73
- expect(protect).property('rules').an('object');
74
- const { rules } = protect;
75
-
76
- expect(rules).property('agentLibRules').keys(agentLibRules);
77
- expect(rules).property('agentLibRulesMask').equal(511);
78
- expect(rules).property('agentRules').keys(agentRules);
79
- });
80
-
81
- describe('makeSourceContext() for rules', function() {
82
- const alRules = ['reflected-xss', 'path-traversal', 'method-tampering'];
83
- const aRules = ['virtual-patch'];
84
- const aSlice = aRules.at.length > 1 ? 1 : 0;
85
- const tests = [
86
- // config, agent-lib, agent
87
- { desc: 'handles no rules', rules: [], alr: [], ar: [] },
88
- { desc: 'handles all rules', rules, alr: agentLibRules, ar: agentRules },
89
- { desc: 'handles some agent-lib rules', rules: alRules, alr: alRules, ar: [] },
90
- { desc: 'handles some agent rules', rules: aRules, alr: [], ar: aRules },
91
- { desc: 'handle some of both rules', rules: alRules.concat(aRules), alr: alRules, ar: aRules },
92
- { desc: 'handles a single agent-lib rule', rules: alRules.slice(1), alr: alRules.slice(1), ar: [] },
93
- { desc: 'handles a single agent rule', rules: aRules.slice(aSlice), alr: [], ar: aRules.slice(aSlice) },
94
- ];
95
-
96
- tests.forEach((t) => {
97
- it(t.desc, function() {
98
- const core = mocks.core();
99
- core.patcher = mocks.patcher();
100
- core.logger = mocks.logger();
101
- core.scopes = mocks.scopes();
102
- core.config = mocks.config();
103
- // make the rules config based on the test's rules
104
- core.config.protect.rules = makeProtectRulesConfig(t.rules);
105
- core.scopes = mocks.scopes();
106
-
107
- // create protect with the rules config
108
- require('../../protect')(core);
109
- const { makeSourceContext } = core.protect;
110
- const [req, res] = makeReqRes({}, {});
111
-
112
- // this is what is really being tested.
113
- const sc = makeSourceContext(req, res);
114
-
115
- // make expected source context
116
- const expectedAlr = {};
117
- let alrm = 0;
118
- t.alr.forEach((alr) => {
119
- expectedAlr[alr] = { mode: 'monitor' };
120
- alrm |= core.protect.agentLib.RuleType[alr];
121
- });
122
- const expectedAr = {};
123
- t.ar.forEach((ar) => expectedAr[ar] = { mode: 'monitor' });
124
-
125
- // check everything
126
- expect(sc.block).a('function');
127
- expect(sc.rules).eql({
128
- agentLibRules: expectedAlr,
129
- agentLibRulesMask: alrm,
130
- agentRules: expectedAr,
131
- });
132
- expect(sc.exclusions).eql([]);
133
- expect(sc.virtualPatches).eql([]);
134
- expect(sc.findings).eql({
135
- trackRequest: false,
136
- securityException: undefined,
137
- bodyType: undefined,
138
- resultsMap: Object.create(null),
139
- });
140
- });
141
- });
142
- });
143
-
144
- describe('makeSourceContext() for req, res', function() {
145
- const rules = ['reflected-xss'];
146
- const expectedAlr = { 'reflected-xss': { mode: 'monitor' } };
147
- const expectedAlrm = 4; // RuleType['reflected-xss']
148
- const expectedAr = {};
149
-
150
- const tests = [
151
- { desc: 'works with unmodified req', req: {}, res: {} },
152
- { desc: 'handles an uppercase header name', req: { rawHeaders: ['Accept', '*'] }, res: {} },
153
- { desc: 'does not modify a header value', req: { rawHeaders: ['Accept', 'TEXT/HTmL'] }, res: {} },
154
- { desc: 'handles an empty url', req: { url: '' }, res: {} },
155
- { desc: 'handles search params', req: { url: '/mimsy?borogroves' }, res: {} },
156
- { desc: 'handles only search params', req: { url: '?alice' }, res: {} },
157
- { desc: 'detects multipart content-type', req: { rawHeaders: ['Content-type', 'multipart/x-www-form-urlencoded'] }, res: {} },
158
- { desc: 'blocks when headers not sent', req: {}, res: { writeHead: sinon.stub(), end: sinon.stub() } },
159
- { desc: 'blocks when headers sent', req: {}, res: { writeHead: sinon.stub(), end: sinon.stub(), headersSent: true } },
160
- ];
161
-
162
- tests.forEach((t) => {
163
- it(t.desc, function() {
164
- const core = mocks.core();
165
- core.patcher = mocks.patcher();
166
- core.logger = mocks.logger();
167
- core.scopes = mocks.scopes();
168
- core.config = mocks.config();
169
- core.config.protect.rules = makeProtectRulesConfig(rules);
170
- core.scopes = mocks.scopes();
171
- // create protect with the rules config
172
- require('../../protect')(core);
173
- const { makeSourceContext } = core.protect;
174
-
175
- const [req, res] = makeReqRes(t.req, t.res);
176
-
177
- // this is what is really being tested.
178
- const sc = makeSourceContext(req, res);
179
-
180
- // default to setting headers but not including a content-type
181
- let contentType = '';
182
- if (!t.req.rawHeaders) {
183
- // the test doesn't set the headers so they are the default
184
- contentType = 'application/x-www-form-urlencoded';
185
- } else if (t.req.rawHeaders[t.req.rawHeaders.length - 1].startsWith('multipart')) {
186
- // the test does set the content-type header (inferred)
187
- contentType = t.req.rawHeaders[t.req.rawHeaders.length - 1];
188
- }
189
- const headers = req.rawHeaders.map((h, ix) => ix & 1 ? h : h.toLowerCase());
190
- const expectedReqData = {
191
- method: 'get',
192
- headers,
193
- uriPath: '/',
194
- queries: '',
195
- contentType,
196
- standardUrlParsing: false,
197
- };
198
- if (t.req.rawHeaders) {
199
- expectedReqData.headers = t.req.rawHeaders.map((h, i) => {
200
- if (i & 1 && h.startsWith('multipart')) {
201
- expectedReqData.contentType = h;
202
- }
203
- return i & 1 ? h : h.toLowerCase();
204
- });
205
- }
206
- if (t.req.url !== undefined) {
207
- expectedReqData.uriPath = t.req.url;
208
- expectedReqData.queries = '';
209
- const ix = t.req.url.indexOf('?');
210
- if (ix >= 0) {
211
- expectedReqData.uriPath = t.req.url.slice(0, ix);
212
- expectedReqData.queries = t.req.url.slice(ix + 1);
213
- }
214
- }
215
-
216
- // check that req and res make the expected abstract request
217
- expect(sc).property('reqData').eql(expectedReqData);
218
-
219
- // if a block test, does it work as expected?
220
- if (t.res.writeHead) {
221
- sc.block('mode', 'reflected-xss');
222
- if (!t.res.headersSent) {
223
- expect(t.res.writeHead.callCount).equal(1);
224
- } else {
225
- expect(t.res.writeHead.callCount).equal(0);
226
- }
227
- expect(t.res.end.callCount).equal(1);
228
- }
229
-
230
- // check constant items
231
- expect(sc.block).a('function');
232
- expect(sc.rules).eql({
233
- agentLibRules: expectedAlr,
234
- agentLibRulesMask: expectedAlrm,
235
- agentRules: expectedAr,
236
- });
237
- expect(sc.exclusions).eql([]);
238
- expect(sc.virtualPatches).eql([]);
239
- expect(sc.findings).eql({
240
- trackRequest: false,
241
- securityException: undefined,
242
- bodyType: undefined,
243
- resultsMap: Object.create(null)
244
- });
245
- });
246
- });
247
-
248
- });
249
- });
250
-
251
- //
252
- // make a req and res pair
253
- //
254
- function makeReqRes(req = {}, res = {}) {
255
- const defaultReq = {
256
- method: 'get',
257
- url: '/',
258
- rawHeaders: getRawHeaders(),
259
- };
260
-
261
- const defaultRes = {
262
- headersSent: false,
263
- writeHead() {},
264
- end() {},
265
- };
266
-
267
- return [
268
- Object.assign({}, defaultReq, req),
269
- Object.assign({}, defaultRes, res),
270
- ];
271
- }
272
-
273
- function getRawHeaders() {
274
- return [
275
- 'accept', 'text/html',
276
- 'accept-encoding', 'gzip, deflate, br',
277
- 'user-agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.',
278
- 'content-type', 'application/x-www-form-urlencoded'
279
- ];
280
- }
281
-
282
- //
283
- // create the config for protect's rules
284
- //
285
- function makeProtectRulesConfig(rules, mode) {
286
- if (mode === undefined) {
287
- mode = 'monitor';
288
- }
289
- if (['monitor', 'block', 'off'].indexOf(mode) < 0) {
290
- throw new Error('valid modes are monitor, block, and off');
291
- }
292
- const rulesOptions = {};
293
- for (const ruleId of rules) {
294
- rulesOptions[ruleId] = { mode };
295
- }
296
-
297
- return rulesOptions;
298
- }
@@ -1,50 +0,0 @@
1
- 'use strict';
2
-
3
- const { expect } = require('chai');
4
- const sinon = require('sinon');
5
- const proxyquire = require('proxyquire');
6
- const mocks = require('../../test/mocks');
7
-
8
- describe('protect throw-security-exception', function() {
9
- let Domain;
10
- let core;
11
- let protect;
12
- let sourceContext;
13
-
14
- beforeEach(function() {
15
- core = mocks.core();
16
- core.logger = mocks.logger();
17
- protect = core.protect = mocks.protect();
18
- sourceContext = {
19
- block: sinon.stub(),
20
- findings: {
21
- securityException: ['block', 'cmd-injection']
22
- }
23
- };
24
- Domain = sinon.stub().callsFake(function(cb) {
25
- cb();
26
- });
27
- proxyquire('./throw-security-exception', {
28
- 'async-hook-domain': Domain
29
- })(core);
30
- });
31
-
32
- it('domain listener will block in response to thrown exception', function() {
33
- const { findings: { securityException } } = sourceContext;
34
- expect(function() {
35
- protect.throwSecurityException(sourceContext);
36
- }).to.throw('SecurityException');
37
- expect(sourceContext.block).calledWith(...securityException);
38
- expect(core.logger.info).to.have.been.calledWith(sinon.match({
39
- ruleId: 'cmd-injection',
40
- mode: 'block',
41
- }));
42
- });
43
-
44
- it('method is noop if source context is not provided', function() {
45
- expect(function() {
46
- protect.throwSecurityException();
47
- expect(sourceContext.block).to.not.have.been.called;
48
- }).not.to.throw('SecurityException');
49
- });
50
- });
package/lib/utils.test.js DELETED
@@ -1,40 +0,0 @@
1
- 'use strict';
2
-
3
- const { expect } = require('chai');
4
-
5
- describe('protect utils', function () {
6
- let getSymbolProperty, target;
7
-
8
- beforeEach(function() {
9
- ({ getSymbolProperty } = require('./utils'));
10
- });
11
-
12
- it('returns `undefined` if the target is falsey', function() {
13
- [null, '', undefined].forEach((target) => {
14
- expect(getSymbolProperty(target, 'test')).to.equal(undefined);
15
- });
16
- });
17
-
18
- it('returns `undefined` if the target does not have the required symbol', function() {
19
- target = {
20
- foo: 'test',
21
- [Symbol('not foo')]: 'test'
22
- };
23
-
24
- const result = getSymbolProperty(target, 'foo');
25
-
26
- expect(result).to.equal(undefined);
27
- });
28
-
29
- it('returns value of the required Symbol property', function() {
30
- target = {
31
- foo: 'normal property',
32
- [Symbol('not foo')]: 'another symbol',
33
- [Symbol('foo')]: 'searched value'
34
- };
35
-
36
- const result = getSymbolProperty(target, 'foo');
37
-
38
- expect(result).to.equal('searched value');
39
- });
40
- });