@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,28 +0,0 @@
1
- 'use strict';
2
-
3
- const { expect } = require('chai');
4
- const sinon = require('sinon');
5
- const proxyquire = require('proxyquire');
6
-
7
- describe('protect input-analysis', function () {
8
- let modulesMock, exportsStub, coreMock;
9
- beforeEach(function () {
10
- exportsStub = sinon.stub();
11
- modulesMock = () => exportsStub();
12
- coreMock = { protect: {} };
13
- });
14
-
15
- it('calls the required hooks', function () {
16
- const inputAnalysis = proxyquire('.', {
17
- './handlers': modulesMock,
18
- './install/http': modulesMock,
19
- './install/fastify3': modulesMock
20
- });
21
-
22
- expect(exportsStub).to.not.have.been.called;
23
- expect(coreMock.protect).to.not.haveOwnProperty('inputAnalysis');
24
- inputAnalysis(coreMock);
25
- expect(coreMock.protect).to.haveOwnProperty('inputAnalysis');
26
- expect(exportsStub).to.have.been.callCount(3);
27
- });
28
- });
@@ -1,71 +0,0 @@
1
- 'use strict';
2
-
3
- const { expect } = require('chai');
4
- const sinon = require('sinon');
5
- const mocks = require('../../../../test/mocks');
6
-
7
- describe('protect input-analysis fastify intrumentation', function () {
8
- let core;
9
- let inputAnalysis;
10
- let fastify;
11
- let serverMock;
12
- let reqMock;
13
- let resMock;
14
- let doneMock;
15
-
16
- beforeEach(function () {
17
- core = mocks.core();
18
- core.logger = mocks.logger();
19
- core.scopes = mocks.scopes();
20
- core.protect = mocks.protect();
21
- core.depHooks = mocks.depHooks();
22
- core.patcher = require('@contrast/patcher')(core);
23
-
24
- reqMock = {
25
- cookies: { foo: 'bar' },
26
- params: { foo: 'bar' },
27
- body: { foo: 'bar' },
28
- };
29
- resMock = {};
30
- doneMock = sinon.stub();
31
- serverMock = {
32
- addHook: sinon.stub().yields(reqMock, resMock, doneMock),
33
- };
34
-
35
- const fastifyOrig = () => serverMock;
36
- core.depHooks.resolve.callsFake(function(desc, cb) {
37
- fastify = cb(fastifyOrig);
38
- });
39
-
40
- const fastifyInstr = require('./fastify3')(core);
41
- inputAnalysis = core.protect.inputAnalysis;
42
- fastifyInstr.install();
43
- });
44
-
45
- describe('preValidationHook', function () {
46
- it('hook is added and calls appropriate analysis handlers', function () {
47
- core.scopes.sources.run({ protect: {} }, () => {
48
- fastify();
49
- });
50
-
51
- expect(serverMock.addHook).to.have.been.called;
52
- expect(doneMock).to.have.been.called;
53
- expect(core.logger.debug).not.to.have.been.called;
54
-
55
- expect(inputAnalysis.handleUrlParams).to.have.been.called;
56
- expect(inputAnalysis.handleCookies).to.have.been.called;
57
- expect(inputAnalysis.handleParsedBody).to.have.been.called;
58
- });
59
- it('input analysis does not occur if there is no protect source context', function () {
60
- fastify();
61
- expect(doneMock).to.have.been.called;
62
- expect(core.logger.debug).to.have.been.calledWith(
63
- 'source context not available in fastify prevalidation hook'
64
- );
65
-
66
- expect(inputAnalysis.handleUrlParams).to.not.have.been.called;
67
- expect(inputAnalysis.handleCookies).to.not.have.been.called;
68
- expect(inputAnalysis.handleParsedBody).to.not.have.been.called;
69
- });
70
- });
71
- });
@@ -1,315 +0,0 @@
1
- 'use strict';
2
-
3
- const EventEmitter = require('events');
4
-
5
- const { expect } = require('chai');
6
- const sinon = require('sinon');
7
-
8
- const aLib = require('@contrast/agent-lib');
9
- const Protect = require('../../..');
10
- const mocks = require('../../../../test/mocks');
11
-
12
- describe('protect input-analysis/http', function() {
13
-
14
- describe('initialization', function() {
15
- let core;
16
- let httpInstr;
17
-
18
- beforeEach(function() {
19
- core = mocks.core();
20
- core.depHooks = mocks.depHooks();
21
- core.config = mocks.config();
22
- core.logger = mocks.logger();
23
- core.scopes = mocks.scopes();
24
- core.patcher = mocks.patcher();
25
- core.protect = Protect(core);
26
- httpInstr = require('./http')(core);
27
- });
28
-
29
- it('should not be initialized after being required', function() {
30
- expect(httpInstr.install).a('function');
31
- expect(core.depHooks.install).not.called;
32
- expect(core.depHooks.resolve).not.called;
33
- });
34
-
35
- it('should be initialized after being installed', function() {
36
- httpInstr.install();
37
- // once for http, once for https
38
- expect(core.depHooks.resolve).callCount(2);
39
- // no way to check the sensor state. probably should attach it to ?
40
- });
41
- });
42
-
43
- describe('initiateRequestHandling()', function() {
44
- let core;
45
- let expectedRules;
46
- let httpInstr;
47
- let server;
48
- let serverEmit;
49
- let req, res;
50
- let reqEmitter;
51
- let context;
52
- let expectedReqData;
53
- let connectInputs;
54
-
55
- beforeEach(function() {
56
- //console.log(this.currentTest.title);
57
- core = mocks.core();
58
- core.depHooks = mocks.depHooks();
59
- core.config = mocks.config();
60
- Object.assign(core.config.protect.rules, {
61
- 'cmd-injection': { mode: 'monitor' }
62
- });
63
- core.logger = mocks.logger();
64
- core.scopes = mocks.scopes();
65
- core.patcher = mocks.patcher();
66
- core.protect = Protect(core);
67
-
68
- expectedRules = makeExpectedRules(core.config.protect.rules);
69
-
70
- sinon.spy(core.protect.inputAnalysis, 'handleConnect');
71
-
72
- httpInstr = require('./http')(core);
73
- sinon.spy(httpInstr, 'makeSourceContext');
74
- sinon.spy(httpInstr.scope, 'getStore');
75
-
76
- // mock Server thing...
77
- server = {};
78
- serverEmit = sinon.stub();
79
- server.prototype = { emit: serverEmit };
80
-
81
- // mock req, res
82
- reqEmitter = new EventEmitter();
83
- req = {
84
- url: '/',
85
- method: 'GET',
86
- rawHeaders: ['host', 'vogon.com', 'content-type', 'application/json'],
87
- // this needs to look sort of like a real incoming message
88
- emit: (...args) => reqEmitter.emit(...args),
89
- _events: {},
90
- socket: { _readableState: { autoDestroy: false } },
91
- resume: sinon.stub(),
92
- };
93
- res = {
94
- end: sinon.stub(),
95
- writeHead: sinon.stub(),
96
- headersSent: false,
97
- on: sinon.stub(),
98
- };
99
-
100
- // setup the context required
101
- context = {
102
- instance: server,
103
- method: serverEmit,
104
- args: ['request', req, res]
105
- };
106
-
107
- // setup a few expected results. these can be modified as needed by
108
- // different tests.
109
- expectedReqData = {
110
- method: req.method,
111
- headers: req.rawHeaders.map((h, i) => i & 1 ? h : h.toLowerCase()),
112
- uriPath: '/',
113
- queries: '',
114
- contentType: '',
115
- standardUrlParsing: false,
116
- };
117
- connectInputs = {
118
- method: expectedReqData.method,
119
- headers: expectedReqData.headers,
120
- uriPath: expectedReqData.uriPath,
121
- };
122
- });
123
-
124
- //
125
- // make sure basic things are as expected
126
- //
127
- function doBasicChecks(expectedReqData) {
128
- // scope.run() call invokes scope.getStore(), so it will be called once to execute
129
- // the test and once more unless no rules were in effect.
130
- if (core.protect.rules.agentLibRulesMask !== 0) {
131
- expect(httpInstr.scope.getStore).callCount(2);
132
- expect(httpInstr.makeSourceContext).calledOnceWith(req, res);
133
- } else {
134
- // source context wasn't available, so
135
- expect(httpInstr.scope.getStore).callCount(1);
136
- return;
137
- }
138
-
139
- const sourceContext = httpInstr.makeSourceContext.returnValues[0];
140
- const {
141
- block, exclusions, findings, reqData, rules, virtualPatches
142
- } = sourceContext;
143
-
144
- /* eslint-disable newline-per-chained-call */
145
- expect(block).a('function');
146
- expect(exclusions).an('array').eql([]);
147
- expect(findings).an('object').eql({
148
- trackRequest: false, securityException: undefined, bodyType: undefined, resultsMap: {}
149
- });
150
- expect(reqData.method).equal(expectedReqData.method);
151
- expect(reqData.uriPath).equal(expectedReqData.uriPath);
152
- expect(reqData.headers).eql(expectedReqData.headers);
153
- expect(rules).an('object').eql(expectedRules);
154
- expect(virtualPatches).an('array').eql([]);
155
-
156
- return sourceContext;
157
- }
158
-
159
- // i don't see how to unit test this; xport.Server.prototype.emit()
160
- // i.e., the real patching function. i think verifying that function
161
- // will require an integration test of some sort.
162
- // it('should handle requests on http, https, and http2', ...)
163
-
164
- // initiateRequestHandling() sets up everything but the inputs for all analysis
165
- // of this request.
166
- it('works as expected', function(done) {
167
- // this is called here because other tests will modify the default data setup in
168
- // beforeEach().
169
- httpInstr.scope.run(httpInstr.scope, () => httpInstr.initiateRequestHandling(context));
170
-
171
- const sourceContext = doBasicChecks(expectedReqData);
172
-
173
- // and finally, handleConnect() should have been called with context
174
- // and input.
175
- expect(core.protect.inputAnalysis.handleConnect).calledOnceWith(sourceContext, connectInputs);
176
-
177
- // the real emitter is called via setImmediate() so give it a chance to run.
178
- expect(serverEmit).callCount(0);
179
- setImmediate(function() {
180
- expect(serverEmit).callCount(1);
181
- done();
182
- });
183
- });
184
-
185
- it('debug logs if no async context', function() {
186
- // this is called here because other tests will modify the default data setup in
187
- // beforeEach().
188
- httpInstr.scope.getStore.restore();
189
- sinon.stub(httpInstr.scope, 'getStore').returns(undefined);
190
- core.logger.debug = sinon.stub();
191
- httpInstr.scope.run(httpInstr.scope, () => httpInstr.initiateRequestHandling(context));
192
-
193
- expect(core.logger.debug).calledOnceWith('cannot acquire store for initiateRequestHandling()');
194
- });
195
-
196
- it('error logs on exceptions', function() {
197
- const err = new Error('sometimes things do not work out');
198
- const { inputAnalysis } = core.protect;
199
- inputAnalysis.handleConnect.restore();
200
- sinon.stub(inputAnalysis, 'handleConnect').throws(err);
201
- // simulate an incoming request to the server.
202
- httpInstr.scope.run(httpInstr.scope, () => httpInstr.initiateRequestHandling(context));
203
-
204
- expect(core.logger.error).calledOnceWith({ err }, 'Error during input analysis');
205
- });
206
-
207
- it('connectInputs will contain not a cookies header', function(done) {
208
- const withoutCookies = req.rawHeaders.slice();
209
- req.rawHeaders.push('COOKIES', 'this;that;the other;thing');
210
- expectedReqData.headers.push('cookies', 'this;that;the other;thing');
211
-
212
- // simulate an incoming request to the server.
213
- httpInstr.scope.run(httpInstr.scope, () => httpInstr.initiateRequestHandling(context));
214
-
215
- const sourceContext = doBasicChecks(expectedReqData);
216
-
217
- // and finally, handleConnect() should have been called with sourceContext and connectInput.
218
- // the cookies header should not be present because protect waits until the framework has
219
- // decoded the cookies before scoring them.
220
- connectInputs.headers = withoutCookies;
221
- expect(core.protect.inputAnalysis.handleConnect).calledOnceWith(sourceContext, connectInputs);
222
-
223
- // the real emitter is called via setImmediate() so give it a chance
224
- // to run.
225
- expect(serverEmit).callCount(0);
226
- setImmediate(function() {
227
- expect(serverEmit).callCount(1);
228
- done();
229
- });
230
- });
231
-
232
- it('does not analyze the request when no rules are in effect', function() {
233
- // kind of hacky reset on the rules
234
- core.protect.rules = {
235
- agentLibRulesMask: 0,
236
- agentLibRules: {},
237
- agentRules: {},
238
- };
239
- expectedRules = core.protect.rules;
240
- core.logger.debug = sinon.stub();
241
-
242
- // simulate an incoming request to the server.
243
- httpInstr.scope.run(httpInstr.scope, () => httpInstr.initiateRequestHandling(context));
244
-
245
- doBasicChecks(expectedReqData);
246
-
247
- expect(core.logger.debug).calledOnceWith('no agent-lib rules are enabled, not checking request');
248
- });
249
-
250
- it('does not emit the original event when blocked', function(done) {
251
- req.url = '/need/<script%20this&that';
252
- const sourceContext = core.protect.makeSourceContext(req, res);
253
- sourceContext.rules.agentLibRules['reflected-xss'] = { mode: 'block' };
254
- sourceContext.rules.agentLibRulesMask |= aLib.constants.RuleType['reflected-xss'];
255
- sourceContext.block = sinon.stub();
256
- core.logger.debug = sinon.stub();
257
-
258
-
259
- httpInstr.scope.run(httpInstr.scope, () => httpInstr.initiateRequestHandling(context));
260
-
261
- const block = ['block', 'reflected-xss'];
262
-
263
- expect(core.protect.inputAnalysis.handleConnect).callCount(1).returned(block);
264
- expect(core.logger.debug).calledWith({ block }, 'request blocked by not emitting request event');
265
- setImmediate(function() {
266
- expect(serverEmit).callCount(0);
267
- done();
268
- });
269
- });
270
-
271
- it('does not handle a request body when it is not JSON', function(done) {
272
- req.rawHeaders = ['Content-Type', 'multipart/form-data'];
273
- expectedReqData.headers = ['content-type', 'multipart/form-data'];
274
- expectedReqData.contentType = 'multipart/form-data';
275
-
276
- // simulate an incoming request to the server.
277
- httpInstr.scope.run(httpInstr.scope, () => httpInstr.initiateRequestHandling(context));
278
-
279
- const sourceContext = doBasicChecks(expectedReqData);
280
-
281
- // and finally, handleConnect() should have been called with sourceContext and connectInput.
282
- connectInputs.headers = expectedReqData.headers;
283
- expect(core.protect.inputAnalysis.handleConnect).calledOnceWith(sourceContext, connectInputs);
284
-
285
- expect(serverEmit).callCount(0);
286
- setImmediate(function() {
287
- expect(serverEmit).callCount(1);
288
- done();
289
- });
290
- });
291
- });
292
- });
293
-
294
- function makeExpectedRules(rules) {
295
- const { RuleType } = aLib.constants;
296
- const agentLibRules = {};
297
- const agentRules = {};
298
- let agentLibRulesMask = 0;
299
-
300
- for (const rule in rules) {
301
- if (!(rule in RuleType)) {
302
- // it's a little random, but if the rule isn't an agent-lib rule, presume
303
- // that it's an agent rule.
304
- if (rule !== 'disabled_rules') {
305
- agentRules[rule] = { mode: undefined };
306
- }
307
- continue;
308
- }
309
- agentLibRules[rule] = rules[rule];
310
- agentLibRulesMask |= RuleType[rule];
311
- }
312
-
313
- return { agentLibRules, agentLibRulesMask, agentRules };
314
-
315
- }