@contrast/protect 1.39.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,125 @@
1
+ 'use strict';
2
+
3
+ const sinon = require('sinon');
4
+ const { expect } = require('chai');
5
+ const scopes = require('@contrast/scopes');
6
+ const patcher = require('@contrast/patcher');
7
+ const mocks = require('@contrast/test/mocks');
8
+
9
+ describe('protect input-tracing postgres', function () {
10
+ const sourceContext = {};
11
+ const store = { protect: sourceContext };
12
+ const query = 'SELECT "foo"';
13
+
14
+ let core, inputTracing;
15
+
16
+ beforeEach(function () {
17
+ core = mocks.core();
18
+ core.logger = mocks.logger();
19
+ core.patcher = patcher(core);
20
+ core.scopes = scopes(core);
21
+ sinon.stub(core.scopes.sources, 'getStore').returns(store);
22
+ core.depHooks = mocks.depHooks();
23
+ core.protect = mocks.protect();
24
+ require('../../get-source-context')(core);
25
+ ({ protect: { inputTracing } } = core);
26
+ });
27
+
28
+ describe('pg.Client.prototype.query', function () {
29
+ let Client;
30
+
31
+ beforeEach(function () {
32
+ Client = function () { };
33
+ Client.prototype.query = sinon.stub();
34
+ core
35
+ .depHooks
36
+ .resolve
37
+ .withArgs({ name: 'pg', file: 'lib/client.js' })
38
+ .yields(Client);
39
+ require('./postgres')(core).install();
40
+ });
41
+
42
+ it('handleSqlInjection() is called with expected values', function () {
43
+ const conn = new Client();
44
+ conn.query(query);
45
+ conn.query({ text: query });
46
+
47
+ const sinkContext = { name: 'pg.Client.prototype.query', value: query };
48
+
49
+ expect(inputTracing.handleSqlInjection.callCount).to.equal(2);
50
+ [0, 1].forEach(callNum => {
51
+ expect(inputTracing.handleSqlInjection.getCall(callNum).calledWithExactly(store, sinkContext));
52
+ });
53
+ });
54
+
55
+ it('handleSqlInjection() is not called if there is no sourceContext', function () {
56
+ core.scopes.sources.getStore.returns({ protect: undefined });
57
+
58
+ const conn = new Client();
59
+ conn.query(query);
60
+ conn.query({ text: query });
61
+
62
+ expect(inputTracing.handleSqlInjection).not.to.have.been.called;
63
+ });
64
+
65
+ it('handleSqlInjection() is not called if the sql value is empty or not a string', function () {
66
+ const conn = new Client();
67
+ conn.query('');
68
+ conn.query(100);
69
+ conn.query({ text: '' });
70
+ conn.query({ text: 100 });
71
+
72
+ expect(inputTracing.handleSqlInjection).not.to.have.been.called;
73
+ });
74
+ });
75
+
76
+ describe('pg-pool.Pool.prototype.query', function () {
77
+ let Pool;
78
+
79
+ beforeEach(function () {
80
+ Pool = function () { };
81
+ Pool.prototype.query = sinon.stub();
82
+ core
83
+ .depHooks
84
+ .resolve
85
+ .withArgs({ name: 'pg-pool' })
86
+ .yields(Pool);
87
+
88
+ require('./postgres')(core).install();
89
+ });
90
+
91
+ it('handleSqlInjection() is called with expected values', function () {
92
+ const pool = new Pool();
93
+ pool.query(query);
94
+ pool.query({ text: query });
95
+
96
+ const sinkContext = { name: 'pg-pool.Pool.prototype.query', value: query };
97
+
98
+ expect(inputTracing.handleSqlInjection.callCount).to.equal(2);
99
+ [0, 1].forEach(callNum => {
100
+ expect(inputTracing.handleSqlInjection.getCall(callNum).calledWithExactly(store, sinkContext));
101
+ });
102
+ });
103
+
104
+ it('handleSqlInjection() is not called if there is no sourceContext', function () {
105
+ core.scopes.sources.getStore.returns({ protect: undefined });
106
+
107
+ const pool = new Pool();
108
+ const value = 'SELECT "foo"';
109
+ pool.query(value);
110
+ pool.query({ text: value });
111
+
112
+ expect(inputTracing.handleSqlInjection).not.to.have.been.called;
113
+ });
114
+
115
+ it('handleSqlInjection() is not called if the sql value is empty or not a string', function () {
116
+ const pool = new Pool();
117
+ pool.query('');
118
+ pool.query(100);
119
+ pool.query({ text: '' });
120
+ pool.query({ text: 100 });
121
+
122
+ expect(inputTracing.handleSqlInjection).not.to.have.been.called;
123
+ });
124
+ });
125
+ });
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ const sinon = require('sinon');
4
+ const { expect } = require('chai');
5
+ const scopes = require('@contrast/scopes');
6
+ const patcher = require('@contrast/patcher');
7
+ const mocks = require('@contrast/test/mocks');
8
+
9
+ describe('protect input-tracing sequelize', function () {
10
+ let core, inputTracing;
11
+ const store = { protect: {} };
12
+
13
+ beforeEach(function () {
14
+ core = mocks.core();
15
+ core.logger = mocks.logger();
16
+ core.patcher = patcher(core);
17
+ core.scopes = scopes(core);
18
+ core.depHooks = mocks.depHooks();
19
+ core.protect = mocks.protect();
20
+ require('../../get-source-context')(core);
21
+ ({ protect: { inputTracing } } = core);
22
+
23
+ sinon.stub(core.scopes.sources, 'getStore').returns(store);
24
+ });
25
+
26
+ describe('sequelize', function () {
27
+ let sequelize;
28
+
29
+ beforeEach(function () {
30
+ sequelize = function () { };
31
+ sequelize.prototype.query = sinon.stub();
32
+ core.depHooks.resolve.yields(sequelize);
33
+ require('./sequelize')(core).install();
34
+ });
35
+
36
+ describe('instruments sequelize.query()', function () {
37
+ it('handleSqlInjection() is called with valid expected values', function () {
38
+ const conn = new sequelize();
39
+ const value = 'SELECT "foo"';
40
+ conn.query(value);
41
+ conn.query({ query: value });
42
+
43
+ expect(inputTracing.handleSqlInjection).to.have.been.calledTwice;
44
+ expect(inputTracing.handleSqlInjection).to.have.been.calledWith(
45
+ {},
46
+ { name: 'sequelize.prototype.query', value, stacktraceOpts: { constructorOpt: conn.query, prependFrames: [sinon.match.func] } }
47
+ );
48
+ });
49
+
50
+ it('handleSqlInjection() is not called if instrumentation is locked', function () {
51
+ sinon.stub(core.scopes.instrumentation, 'isLocked').returns(true);
52
+ const conn = new sequelize();
53
+ const value = 'SELECT "foo"';
54
+ conn.query(value);
55
+
56
+ expect(inputTracing.handleSqlInjection).not.to.have.been.called;
57
+ });
58
+
59
+ it('handleSqlInjection() is not called if there is no sourceContext', function () {
60
+ core.scopes.sources.getStore.returns({ protect: undefined });
61
+ const conn = new sequelize();
62
+ const value = 'SELECT "foo"';
63
+ conn.query(value);
64
+
65
+ expect(inputTracing.handleSqlInjection).not.to.have.been.called;
66
+ });
67
+
68
+ it('handleSqlInjection() is not called if the sql value is empty or not a string', function () {
69
+ const conn = new sequelize();
70
+ conn.query('');
71
+ conn.query(100);
72
+ conn.query({ query: '' });
73
+
74
+ expect(inputTracing.handleSqlInjection).not.to.have.been.called;
75
+ });
76
+ });
77
+ });
78
+ });
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ const sinon = require('sinon');
4
+ const { expect } = require('chai');
5
+ const scopes = require('@contrast/scopes');
6
+ const patcher = require('@contrast/patcher');
7
+ const mocks = require('@contrast/test/mocks');
8
+
9
+ describe('protect input-tracing spdy', function () {
10
+ let core, inputTracing;
11
+ const store = { protect: {} };
12
+
13
+ beforeEach(function () {
14
+ core = mocks.core();
15
+ core.logger = mocks.logger();
16
+ core.patcher = patcher(core);
17
+ core.scopes = scopes(core);
18
+ sinon.stub(core.scopes.sources, 'getStore').returns(store);
19
+ core.depHooks = mocks.depHooks();
20
+ core.protect = mocks.protect();
21
+ require('../../get-source-context')(core);
22
+ ({ protect: { inputTracing } } = core);
23
+ });
24
+
25
+ describe('spdy', function () {
26
+ let spdy;
27
+
28
+ beforeEach(function () {
29
+ spdy = function () {};
30
+ spdy.response = {};
31
+ spdy.response.end = sinon.stub();
32
+ spdy.response.spdyStream = { once: sinon.stub() };
33
+ core.depHooks.resolve.yields(spdy);
34
+ require('./spdy')(core).install();
35
+ });
36
+
37
+ it('handleReflectedXss() is not called if there is no value', function () {
38
+ spdy.response.end();
39
+
40
+ expect(inputTracing.handleReflectedXss).not.to.have.been.called;
41
+ });
42
+
43
+ it('handleReflectedXss() is not called if there is no sourceContext', function () {
44
+ core.scopes.sources.getStore.returns({ protect: undefined });
45
+ const value = "<script>alert('xss');</script>";
46
+ spdy.response.end(value);
47
+
48
+ expect(inputTracing.handleReflectedXss).not.to.have.been.called;
49
+ });
50
+
51
+ it('handleReflectedXss() is not called if instrumentation is locked', function () {
52
+ sinon.stub(core.scopes.instrumentation, 'isLocked').returns(true);
53
+ const value = "<script>alert('xss');</script>";
54
+ spdy.response.end(value);
55
+
56
+ expect(inputTracing.handleReflectedXss).not.to.have.been.called;
57
+ });
58
+
59
+ it('handleReflectedXss() is called with valid expected values', function () {
60
+ const value = "<script>alert('xss');</script>";
61
+ spdy.response.end(value);
62
+
63
+ expect(inputTracing.handleReflectedXss).to.have.been.calledOnceWith(
64
+ {},
65
+ {
66
+ name: 'spdy.response.end',
67
+ value,
68
+ stacktraceOpts: {
69
+ constructorOpt: spdy.response.end
70
+ }
71
+ }
72
+ );
73
+ });
74
+
75
+ });
76
+ });
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ const sinon = require('sinon');
4
+ const { expect } = require('chai');
5
+ const scopes = require('@contrast/scopes');
6
+ const patcher = require('@contrast/patcher');
7
+ const mocks = require('@contrast/test/mocks');
8
+
9
+ describe('protect input-tracing sqlite3', function () {
10
+ const store = { protect: {} };
11
+
12
+ let core, inputTracing;
13
+
14
+ beforeEach(function () {
15
+ core = mocks.core();
16
+ core.logger = mocks.logger();
17
+ core.patcher = patcher(core);
18
+ core.scopes = scopes(core);
19
+ core.depHooks = mocks.depHooks();
20
+ core.protect = mocks.protect();
21
+ require('../../get-source-context')(core);
22
+ ({ protect: { inputTracing } } = core);
23
+
24
+ sinon.stub(core.scopes.sources, 'getStore').returns(store);
25
+ });
26
+
27
+ describe('sqlite3', function () {
28
+ let sqlite3;
29
+
30
+ beforeEach(function () {
31
+ sqlite3 = {
32
+ // eslint-disable-next-line object-shorthand
33
+ Database: function () { }
34
+ };
35
+ sqlite3.Database.prototype = {
36
+ all: sinon.stub(),
37
+ run: sinon.stub(),
38
+ get: sinon.stub(),
39
+ each: sinon.stub(),
40
+ exec: sinon.stub(),
41
+ prepare: sinon.stub()
42
+ };
43
+ core.depHooks.resolve.yields(sqlite3);
44
+ require('./sqlite3')(core).install();
45
+ });
46
+
47
+ ['all', 'run', 'get', 'each', 'exec', 'prepare'].forEach((method) => {
48
+ describe(`instruments connection.${method}()`, function () {
49
+ it('handleSqlInjection() is called with valid expected values', function () {
50
+ const db = new sqlite3.Database();
51
+ const value = 'SELECT "foo"';
52
+ db[method](value);
53
+
54
+ expect(inputTracing.handleSqlInjection).to.have.been.calledWith(
55
+ {},
56
+ { name: `sqlite3.Database.prototype.${method}`, value, stacktraceOpts: { constructorOpt: db[method], prependFrames: [sinon.match.func] } }
57
+ );
58
+ });
59
+
60
+ it('handleSqlInjection() is not called if instrumentation is locked', function () {
61
+ sinon.stub(core.scopes.instrumentation, 'isLocked').returns(true);
62
+ const db = new sqlite3.Database();
63
+ const value = 'SELECT "foo"';
64
+ db[method](value);
65
+
66
+ expect(inputTracing.handleSqlInjection).not.to.have.been.called;
67
+ });
68
+
69
+ it('handleSqlInjection() is not called if there is no sourceContext', function () {
70
+ core.scopes.sources.getStore.returns({ protect: undefined });
71
+ const db = new sqlite3.Database();
72
+ const value = 'SELECT "foo"';
73
+ db[method](value);
74
+
75
+ expect(inputTracing.handleSqlInjection).not.to.have.been.called;
76
+ });
77
+
78
+ it('handleSqlInjection() is not called if the sql value is empty or not a string', function () {
79
+ const db = new sqlite3.Database();
80
+ db[method]('');
81
+ db[method](100);
82
+
83
+ expect(inputTracing.handleSqlInjection).not.to.have.been.called;
84
+ });
85
+ });
86
+ });
87
+ });
88
+ });
@@ -0,0 +1,176 @@
1
+ 'use strict';
2
+
3
+ const sinon = require('sinon');
4
+ const { expect } = require('chai');
5
+ const patcher = require('@contrast/patcher');
6
+ const mocks = require('@contrast/test/mocks');
7
+
8
+ describe('protect input-tracing vm', function () {
9
+ const store = { protect: {} };
10
+ let vm, origVm, core;
11
+
12
+ const testSetup = [
13
+ {
14
+ methods: ['Script', 'compileFunction', 'runInContext', 'runInNewContext', 'runInThisContext'],
15
+ vulnerableArgs: [{ type: 'string', index: 0 }],
16
+ },
17
+ {
18
+ methods: ['runInContext', 'runInNewContext'],
19
+ vulnerableArgs: [{ type: 'string', index: 0 }, { type: 'object', index: 1 }],
20
+ },
21
+ {
22
+ methods: ['createContext'],
23
+ vulnerableArgs: [{ type: 'object', index: 0 }],
24
+ },
25
+ {
26
+ methods: ['Script.prototype.runInContext', 'Script.prototype.runInNewContext'],
27
+ vulnerableArgs: [{ type: 'object', index: 0 }],
28
+ }
29
+ ];
30
+
31
+ function methodPick(vm, method) {
32
+ let fn = vm[method];
33
+
34
+ if (method.includes('.')) {
35
+ fn = vm;
36
+ method.split('.').forEach((prop) => {
37
+ fn = fn[prop];
38
+ });
39
+ }
40
+
41
+ return fn;
42
+ }
43
+
44
+ beforeEach(function () {
45
+ vm = {};
46
+ origVm = {
47
+ Script: sinon.stub(),
48
+ createContext: sinon.stub(),
49
+ compileFunction: sinon.stub(),
50
+ runInContext: sinon.stub(),
51
+ runInNewContext: sinon.stub(),
52
+ runInThisContext: sinon.stub(),
53
+ };
54
+ Object.assign(vm, origVm);
55
+ origVm['Script.prototype.runInContext'] = sinon.stub();
56
+ origVm['Script.prototype.runInNewContext'] = sinon.stub();
57
+ vm.Script.prototype.runInContext = origVm['Script.prototype.runInContext'];
58
+ vm.Script.prototype.runInNewContext = origVm['Script.prototype.runInNewContext'];
59
+
60
+ core = mocks.core();
61
+ core.logger = mocks.logger();
62
+ core.depHooks = mocks.depHooks();
63
+ core.depHooks.resolve.yields(vm);
64
+ core.scopes = mocks.scopes();
65
+ sinon.stub(core.scopes.sources, 'getStore').returns(store);
66
+ core.patcher = patcher(core);
67
+ core.protect = mocks.protect();
68
+ require('../../get-source-context')(core);
69
+
70
+ require('./vm')(core);
71
+
72
+ core.protect.inputTracing.vmInstrumentation.install();
73
+ });
74
+
75
+ describe('ssjsInjection() is called with expected values', function () {
76
+ testSetup.forEach(({ methods, vulnerableArgs }) => {
77
+ const methodArgs = [];
78
+
79
+ vulnerableArgs.forEach((arg) => {
80
+ const argValue = arg.type == 'string' ? 'function() { return "hi"; }' : { prop: 'foobar' };
81
+ methodArgs[arg.index] = argValue;
82
+ });
83
+
84
+ methods.forEach((method) => {
85
+ const name = `vm.${method}`;
86
+
87
+ it(name, function () {
88
+ const fn = methodPick(vm, method);
89
+ fn(...methodArgs);
90
+
91
+ expect(core.protect.inputTracing.ssjsInjection).to.have.callCount(vulnerableArgs.length);
92
+ vulnerableArgs.forEach(({ index }) => {
93
+ const value = methodArgs[index];
94
+
95
+ expect(core.protect.inputTracing.ssjsInjection).to.have.been.calledWith(
96
+ store.protect,
97
+ { name, value, stacktraceOpts: { constructorOpt: fn, prependFrames: [origVm[method]] } }
98
+ );
99
+ });
100
+ });
101
+ });
102
+ });
103
+ });
104
+
105
+ describe('ssjsInjection() is not called when method args are not relevant', function () {
106
+ const methodArgs = [1, undefined];
107
+
108
+ testSetup.forEach(({ methods }) => {
109
+ methods.forEach((method) => {
110
+ const name = `vm.${method}`;
111
+
112
+ it(name, function () {
113
+ const fn = methodPick(vm, method);
114
+
115
+ fn(...methodArgs);
116
+
117
+ expect(core.protect.inputTracing.ssjsInjection).not.to.have.been.called;
118
+ });
119
+ });
120
+ });
121
+ });
122
+
123
+ describe('ssjsInjection() is not called when instrumentation is locked', function () {
124
+ beforeEach(function () {
125
+ core.scopes.instrumentation.isLocked.returns(true);
126
+ });
127
+
128
+ testSetup.forEach(({ methods, vulnerableArgs }) => {
129
+ const methodArgs = [];
130
+
131
+ vulnerableArgs.forEach((arg) => {
132
+ const argValue = arg.type == 'string' ? 'function() { return "hi"; }' : { prop: 'function() { return "hi"; }' };
133
+ methodArgs[arg.index] = argValue;
134
+ });
135
+
136
+ methods.forEach((method) => {
137
+ const name = `vm.${method}`;
138
+
139
+ it(name, function () {
140
+ const fn = methodPick(vm, method);
141
+
142
+ fn(...methodArgs);
143
+
144
+ expect(core.protect.inputTracing.ssjsInjection).not.to.have.been.called;
145
+ });
146
+ });
147
+ });
148
+ });
149
+
150
+ describe('ssjsInjection() is not called when there is no Protect sourceContext', function () {
151
+ beforeEach(function () {
152
+ core.scopes.sources.getStore.returns({ protect: null });
153
+ });
154
+
155
+ testSetup.forEach(({ methods, vulnerableArgs }) => {
156
+ const methodArgs = [];
157
+
158
+ vulnerableArgs.forEach((arg) => {
159
+ const argValue = arg.type == 'string' ? 'function() { return "hi"; }' : { prop: 'function() { return "hi"; }' };
160
+ methodArgs[arg.index] = argValue;
161
+ });
162
+
163
+ methods.forEach((method) => {
164
+ const name = `vm.${method}`;
165
+
166
+ it(name, function () {
167
+ const fn = methodPick(vm, method);
168
+
169
+ fn(...methodArgs);
170
+
171
+ expect(core.protect.inputTracing.ssjsInjection).not.to.have.been.called;
172
+ });
173
+ });
174
+ });
175
+ });
176
+ });
@@ -0,0 +1,99 @@
1
+ 'use strict';
2
+
3
+ const sinon = require('sinon');
4
+ const { expect } = require('chai');
5
+ const { symbols: { kMetrics } } = require('@contrast/common');
6
+ const mocks = require('@contrast/test/mocks');
7
+ const patcher = require('@contrast/patcher');
8
+
9
+ describe('protect make-response-blocker', function () {
10
+
11
+ let res, core, makeResponseBlocker;
12
+ beforeEach(function () {
13
+
14
+ res = {
15
+ end: sinon.stub(),
16
+ headersSent: false,
17
+ writeHead: sinon.stub()
18
+ };
19
+
20
+ core = mocks.core();
21
+ core.logger = mocks.logger();
22
+ core.patcher = patcher(core);
23
+ core.protect = {};
24
+
25
+ sinon.spy(core.patcher, 'unwrap');
26
+ makeResponseBlocker = require('./make-response-blocker')(core);
27
+ });
28
+
29
+ it('Sends a blocked response', function () {
30
+ const block = makeResponseBlocker(res);
31
+ block('block', 'sql-injection');
32
+ expect(core.patcher.unwrap).to.have.been.calledWith(res.writeHead);
33
+ expect(core.patcher.unwrap).to.have.been.calledWith(res.end);
34
+ expect(res.writeHead).to.have.been.calledWith(403);
35
+ expect(res.end).to.have.been.calledWith('');
36
+ expect(core.logger.info).to.have.been.calledWith(
37
+ { mode: 'BLOCK', ruleId: 'sql-injection' },
38
+ 'Request blocked'
39
+ );
40
+ });
41
+
42
+ it('Sets blocked property', function () {
43
+ const block = makeResponseBlocker(res);
44
+ block('block', 'sql-injection');
45
+ expect(makeResponseBlocker.blocked.has(res)).to.be.true;
46
+ });
47
+
48
+ it('Set core.protect.makeResponseBlocker', function () {
49
+ expect(core.protect.makeResponseBlocker).to.equal(makeResponseBlocker);
50
+ });
51
+
52
+ it('Blocks on the same "res" object only once', function () {
53
+ const block = makeResponseBlocker(res);
54
+ block('block', 'sql-injection');
55
+ block('block', 'cmd-injection');
56
+ expect(makeResponseBlocker.blocked.has(res));
57
+ expect(res.end).to.have.been.calledOnce;
58
+ expect(res.writeHead).to.have.been.calledOnce;
59
+ expect(core.logger.info).to.have.been.calledOnce;
60
+ });
61
+
62
+ it('Adds multiple responses to blocked set', function () {
63
+ const res1 = Object.assign({}, res);
64
+ const res2 = Object.assign({}, res);
65
+ const block1 = makeResponseBlocker(res1);
66
+ const block2 = makeResponseBlocker(res2);
67
+ block1('block', 'sql-injection');
68
+ block2('block', 'cmd-injection');
69
+ expect(makeResponseBlocker.blocked.has(res1));
70
+ expect(makeResponseBlocker.blocked.has(res2));
71
+ });
72
+
73
+ it('Does not call writeHead when headers already sent', function () {
74
+ const block = makeResponseBlocker(res);
75
+ res.headersSent = true;
76
+ block('block_at_perimeter', 'reflected-xss');
77
+ expect(res.writeHead).not.to.have.been.called;
78
+ expect(res.end).to.have.been.calledWith('');
79
+ });
80
+
81
+ it('Logs error when exception thrown', function () {
82
+ const err = new Error('error');
83
+ res.end.throws(err);
84
+ const block = makeResponseBlocker(res);
85
+ block('block', 'path-traversal');
86
+ expect(core.logger.error).to.have.been.calledWith(
87
+ { err, mode: 'BLOCK', ruleId: 'path-traversal' },
88
+ 'Error blocking request'
89
+ );
90
+ });
91
+
92
+ it('removes the `metrics` object if present', function () {
93
+ const block = makeResponseBlocker(res);
94
+ res[kMetrics] = { timeout: setTimeout(sinon.stub(), 1) };
95
+ block('block', 'sql-injection');
96
+
97
+ expect(res).not.to.have.property(kMetrics);
98
+ });
99
+ });