@contrast/protect 1.0.1 → 1.2.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 +53 -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 +4 -3
  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,108 +0,0 @@
1
- 'use strict';
2
-
3
- const { expect } = require('chai');
4
- const sinon = require('sinon');
5
-
6
- describe('protect input-tracing installs: mysql interfaces', function() {
7
- const store = { protect: {} };
8
-
9
- let core;
10
- let inputTracing;
11
-
12
- beforeEach(function() {
13
- const mocks = require('../../../../test/mocks');
14
- const patcher = require('@contrast/patcher');
15
-
16
- core = mocks.core();
17
- core.logger = mocks.logger();
18
- core.patcher = patcher(core);
19
- core.scopes = mocks.scopes();
20
- core.depHooks = mocks.depHooks();
21
- core.protect = mocks.protect();
22
- ({ protect: { inputTracing } } = core);
23
-
24
- sinon.stub(core.scopes.sources, 'getStore').returns(store);
25
- });
26
-
27
- describe('mysql', function() {
28
- let Connection;
29
-
30
- beforeEach(function() {
31
- Connection = function() {};
32
- Connection.prototype.query = sinon.stub();
33
- core.depHooks.resolve.yields(Connection);
34
- require('./mysql')(core).install();
35
- });
36
-
37
- describe('instruments connection.query()', function() {
38
- it('handleSqlInjection() is called with valid expected values', function() {
39
- const conn = new Connection();
40
- const value = 'SELECT "foo"';
41
- conn.query(value);
42
-
43
- expect(inputTracing.handleSqlInjection).to.have.been.calledWith(
44
- {},
45
- { name: 'mysql.Connection.prototype.query', value }
46
- );
47
- });
48
-
49
- it('handleSqlInjection() is not called if there is no sourceContext', function() {
50
- core.scopes.sources.getStore.returns({ protect: undefined });
51
- const conn = new Connection();
52
- const value = 'SELECT "foo"';
53
- conn.query(value);
54
-
55
- expect(inputTracing.handleSqlInjection).to.have.not.been.called;
56
- });
57
-
58
- it('handleSqlInjection() is not called if the sql value is empty or not a string', function() {
59
- const conn = new Connection();
60
- conn.query('');
61
- conn.query(100);
62
-
63
- expect(inputTracing.handleSqlInjection).to.have.not.been.called;
64
- });
65
- });
66
- });
67
-
68
- describe('mysql2', function() {
69
- let Connection;
70
-
71
- beforeEach(function() {
72
- Connection = function() {};
73
- Connection.prototype.execute = sinon.stub();
74
- core.depHooks.resolve.yields(Connection);
75
- require('./mysql')(core).install();
76
- });
77
-
78
- describe('instruments connection.execute()', function() {
79
- it('handleSqlInjection() is called with expected values', function() {
80
- const conn = new Connection();
81
- const value = 'SELECT "foo"';
82
- conn.execute(value);
83
-
84
- expect(inputTracing.handleSqlInjection).to.have.been.calledWith(
85
- {},
86
- { name: 'mysql2.Connection.prototype.execute', value }
87
- );
88
- });
89
-
90
- it('handleSqlInjection() is not called if there is no sourceContext', function() {
91
- core.scopes.sources.getStore.returns({ protect: undefined });
92
- const conn = new Connection();
93
- const value = 'SELECT "foo"';
94
- conn.execute(value);
95
-
96
- expect(inputTracing.handleSqlInjection).to.have.not.been.called;
97
- });
98
-
99
- it('handleSqlInjection() is not called if the sql value is empty or not a string', function() {
100
- const conn = new Connection();
101
- conn.execute('');
102
- conn.execute(100);
103
-
104
- expect(inputTracing.handleSqlInjection).to.have.not.been.called;
105
- });
106
- });
107
- });
108
- });
@@ -1,125 +0,0 @@
1
- 'use strict';
2
-
3
- const { expect } = require('chai');
4
- const sinon = require('sinon');
5
-
6
- describe('protect input-tracing installs: postgres interfaces', function() {
7
- const sourceContext = {};
8
- const store = { protect: sourceContext };
9
- const query = 'SELECT "foo"';
10
-
11
- let core;
12
- let inputTracing;
13
-
14
- beforeEach(function() {
15
- const mocks = require('../../../../test/mocks');
16
- const patcher = require('@contrast/patcher');
17
-
18
- core = mocks.core();
19
- core.logger = mocks.logger();
20
- core.patcher = patcher(core);
21
- core.scopes = mocks.scopes();
22
- sinon.stub(core.scopes.sources, 'getStore').returns(store);
23
- core.depHooks = mocks.depHooks();
24
- core.protect = mocks.protect();
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.eql(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).to.have.not.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).to.have.not.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.eql(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).to.have.not.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).to.have.not.been.called;
123
- });
124
- });
125
- });
@@ -1,79 +0,0 @@
1
- 'use strict';
2
-
3
- const { expect } = require('chai');
4
- const sinon = require('sinon');
5
-
6
- describe('protect input-tracing installs: sequelize interfaces', function() {
7
- const store = { protect: {} };
8
-
9
- let core;
10
- let inputTracing;
11
-
12
- beforeEach(function() {
13
- const mocks = require('../../../../test/mocks');
14
- const patcher = require('@contrast/patcher');
15
-
16
- core = mocks.core();
17
- core.logger = mocks.logger();
18
- core.patcher = patcher(core);
19
- core.scopes = mocks.scopes();
20
- core.depHooks = mocks.depHooks();
21
- core.protect = mocks.protect();
22
- ({ protect: { inputTracing } } = core);
23
-
24
- sinon.stub(core.scopes.sources, 'getStore').returns(store);
25
- });
26
-
27
- describe('sequelize', function() {
28
- let sequelize;
29
-
30
- beforeEach(function() {
31
- sequelize = function() {};
32
- sequelize.prototype.query = sinon.stub();
33
- core.depHooks.resolve.yields(sequelize);
34
- require('./sequelize')(core).install();
35
- });
36
-
37
- describe('instruments sequelize.query()', function() {
38
- it('handleSqlInjection() is called with valid expected values', function() {
39
- const conn = new sequelize();
40
- const value = 'SELECT "foo"';
41
- conn.query(value);
42
- conn.query({ query: value });
43
-
44
- expect(inputTracing.handleSqlInjection).to.have.been.calledTwice;
45
- expect(inputTracing.handleSqlInjection).to.have.been.calledWith(
46
- {},
47
- { name: 'sequelize.prototype.query', value }
48
- );
49
- });
50
-
51
- it('handleSqlInjection() is not called if instrumentation is locked', function() {
52
- core.scopes.instrumentation.isLocked.returns(true);
53
- const conn = new sequelize();
54
- const value = 'SELECT "foo"';
55
- conn.query(value);
56
-
57
- expect(inputTracing.handleSqlInjection).to.have.not.been.called;
58
- });
59
-
60
- it('handleSqlInjection() is not called if there is no sourceContext', function() {
61
- core.scopes.sources.getStore.returns({ protect: undefined });
62
- const conn = new sequelize();
63
- const value = 'SELECT "foo"';
64
- conn.query(value);
65
-
66
- expect(inputTracing.handleSqlInjection).to.have.not.been.called;
67
- });
68
-
69
- it('handleSqlInjection() is not called if the sql value is empty or not a string', function() {
70
- const conn = new sequelize();
71
- conn.query('');
72
- conn.query(100);
73
- conn.query({ query: '' });
74
-
75
- expect(inputTracing.handleSqlInjection).to.have.not.been.called;
76
- });
77
- });
78
- });
79
- });
@@ -1,88 +0,0 @@
1
- 'use strict';
2
-
3
- const { expect } = require('chai');
4
- const sinon = require('sinon');
5
-
6
- describe('protect input-tracing installs: sqlite3 interfaces', function() {
7
- const store = { protect: {} };
8
-
9
- let core;
10
- let inputTracing;
11
-
12
- beforeEach(function() {
13
- const mocks = require('../../../../test/mocks');
14
- const patcher = require('@contrast/patcher');
15
-
16
- core = mocks.core();
17
- core.logger = mocks.logger();
18
- core.patcher = patcher(core);
19
- core.scopes = mocks.scopes();
20
- core.depHooks = mocks.depHooks();
21
- core.protect = mocks.protect();
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 }
57
- );
58
- });
59
-
60
- it('handleSqlInjection() is not called if instrumentation is locked', function() {
61
- 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).to.have.not.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).to.have.not.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).to.have.not.been.called;
84
- });
85
- });
86
- });
87
- });
88
- });
@@ -1,88 +0,0 @@
1
- 'use strict';
2
-
3
- const sinon = require('sinon');
4
- const { expect } = require('chai');
5
- const mocks = require('../../test/mocks');
6
-
7
- describe('protect make-response-blocker', function() {
8
-
9
- let res, core, makeResponseBlocker;
10
- beforeEach(function() {
11
-
12
- res = {
13
- end: sinon.stub(),
14
- headersSent: false,
15
- writeHead: sinon.stub()
16
- };
17
-
18
- core = mocks.core();
19
- core.logger = mocks.logger();
20
- core.patcher = mocks.patcher();
21
- core.protect = {};
22
-
23
- makeResponseBlocker = require('./make-response-blocker')(core);
24
- });
25
-
26
- it('Sends a blocked response', function() {
27
- const block = makeResponseBlocker(res);
28
- block('block', 'sql-injection');
29
- expect(core.patcher.unwrap).to.have.been.calledWith(res.writeHead);
30
- expect(core.patcher.unwrap).to.have.been.calledWith(res.end);
31
- expect(res.writeHead).to.have.been.calledWith(403);
32
- expect(res.end).to.have.been.calledWith('');
33
- expect(core.logger.info).to.have.been.calledWith(
34
- { mode: 'BLOCK', ruleId: 'sql-injection' },
35
- 'Request blocked'
36
- );
37
- });
38
-
39
- it('Sets blocked property', function() {
40
- const block = makeResponseBlocker(res);
41
- block('block', 'sql-injection');
42
- expect(makeResponseBlocker.blocked.has(res)).equal(true);
43
- });
44
-
45
- it('Set core.protect.makeResponseBlocker', function() {
46
- expect(core.protect.makeResponseBlocker).to.be.equal(makeResponseBlocker);
47
- });
48
-
49
- it('Blocks on the same "res" object only once', function() {
50
- const block = makeResponseBlocker(res);
51
- block('block', 'sql-injection');
52
- block('block', 'cmd-injection');
53
- expect(makeResponseBlocker.blocked.has(res));
54
- expect(res.end).to.have.been.calledOnce;
55
- expect(res.writeHead).to.have.been.calledOnce;
56
- expect(core.logger.info).to.have.been.calledOnce;
57
- });
58
-
59
- it('Adds multiple responses to blocked set', function() {
60
- const res1 = Object.assign({}, res);
61
- const res2 = Object.assign({}, res);
62
- const block1 = makeResponseBlocker(res1);
63
- const block2 = makeResponseBlocker(res2);
64
- block1('block', 'sql-injection');
65
- block2('block', 'cmd-injection');
66
- expect(makeResponseBlocker.blocked.has(res1));
67
- expect(makeResponseBlocker.blocked.has(res2));
68
- });
69
-
70
- it('Does not call writeHead when headers already sent', function() {
71
- const block = makeResponseBlocker(res);
72
- res.headersSent = true;
73
- block('block_at_perimeter', 'reflected-xss');
74
- expect(res.writeHead).to.not.have.been.called;
75
- expect(res.end).to.have.been.calledWith('');
76
- });
77
-
78
- it('Logs error when exception thrown', function() {
79
- const err = new Error('error');
80
- res.end.throws(err);
81
- const block = makeResponseBlocker(res);
82
- block('block', 'path-traversal');
83
- expect(core.logger.error).to.have.been.calledWith(
84
- { err, mode: 'BLOCK', ruleId: 'path-traversal' },
85
- 'Error blocking request'
86
- );
87
- });
88
- });