@contrast/protect 1.0.1

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 (48) hide show
  1. package/LICENSE +12 -0
  2. package/README.md +9 -0
  3. package/lib/cli-rewriter.js +20 -0
  4. package/lib/error-handlers/constants.js +5 -0
  5. package/lib/error-handlers/index.js +13 -0
  6. package/lib/error-handlers/install/fastify3.js +88 -0
  7. package/lib/error-handlers/install/fastify3.test.js +142 -0
  8. package/lib/esm-loader.mjs +2 -0
  9. package/lib/esm-loader.test.mjs +11 -0
  10. package/lib/index.d.ts +36 -0
  11. package/lib/index.js +89 -0
  12. package/lib/index.test.js +32 -0
  13. package/lib/input-analysis/handlers.js +462 -0
  14. package/lib/input-analysis/handlers.test.js +898 -0
  15. package/lib/input-analysis/index.js +16 -0
  16. package/lib/input-analysis/index.test.js +28 -0
  17. package/lib/input-analysis/install/fastify3.js +79 -0
  18. package/lib/input-analysis/install/fastify3.test.js +71 -0
  19. package/lib/input-analysis/install/http.js +185 -0
  20. package/lib/input-analysis/install/http.test.js +315 -0
  21. package/lib/input-tracing/constants.js +5 -0
  22. package/lib/input-tracing/handlers/index.js +117 -0
  23. package/lib/input-tracing/handlers/index.test.js +395 -0
  24. package/lib/input-tracing/handlers/nosql-injection-mongo.js +48 -0
  25. package/lib/input-tracing/index.js +32 -0
  26. package/lib/input-tracing/install/README.md +1 -0
  27. package/lib/input-tracing/install/child-process.js +45 -0
  28. package/lib/input-tracing/install/child-process.test.js +112 -0
  29. package/lib/input-tracing/install/fs.js +107 -0
  30. package/lib/input-tracing/install/fs.test.js +118 -0
  31. package/lib/input-tracing/install/mysql.js +57 -0
  32. package/lib/input-tracing/install/mysql.test.js +108 -0
  33. package/lib/input-tracing/install/postgres.js +61 -0
  34. package/lib/input-tracing/install/postgres.test.js +125 -0
  35. package/lib/input-tracing/install/sequelize.js +51 -0
  36. package/lib/input-tracing/install/sequelize.test.js +79 -0
  37. package/lib/input-tracing/install/sqlite3.js +45 -0
  38. package/lib/input-tracing/install/sqlite3.test.js +88 -0
  39. package/lib/make-response-blocker.js +35 -0
  40. package/lib/make-response-blocker.test.js +88 -0
  41. package/lib/make-source-context.js +130 -0
  42. package/lib/make-source-context.test.js +298 -0
  43. package/lib/security-exception.js +12 -0
  44. package/lib/throw-security-exception.js +30 -0
  45. package/lib/throw-security-exception.test.js +50 -0
  46. package/lib/utils.js +88 -0
  47. package/lib/utils.test.js +40 -0
  48. package/package.json +32 -0
@@ -0,0 +1,112 @@
1
+ 'use strict';
2
+
3
+ const sinon = require('sinon');
4
+ const { expect } = require('chai');
5
+
6
+ describe('protect input-tracing child-process', function() {
7
+ const cpInstr = require('./child-process');
8
+ const store = { protect: {} };
9
+ let cp, core;
10
+
11
+ beforeEach(function() {
12
+ const mocks = require('../../../../test/mocks/');
13
+ const patcher = require('@contrast/patcher');
14
+
15
+ cp = {
16
+ exec: sinon.stub(),
17
+ execSync: sinon.stub()
18
+ };
19
+
20
+ core = mocks.core();
21
+ core.logger = mocks.logger();
22
+ core.depHooks = mocks.depHooks();
23
+ core.depHooks.resolve.yields(cp);
24
+ core.scopes = mocks.scopes();
25
+ core.scopes.sources = {
26
+ getStore: sinon.stub().returns(store)
27
+ };
28
+ core.patcher = patcher(core);
29
+ core.protect = mocks.protect();
30
+
31
+ cpInstr(core);
32
+
33
+ core.protect.inputTracing.cpInstrumentation.install();
34
+ });
35
+
36
+ describe('handleCommandInjection() is called with expected values', function() {
37
+ const methodArgs = ['foo', 'bar'];
38
+
39
+ ['exec', 'execSync'].forEach((method) => {
40
+ const name = `child_process.${method}`;
41
+
42
+ it(`${name}`, function() {
43
+ cp[method](...methodArgs);
44
+
45
+ const value = methodArgs[0];
46
+
47
+ expect(core.captureStacktrace).to.have.been.calledOnceWith(
48
+ { name, value },
49
+ { constructorOpt: cp[method] }
50
+ );
51
+
52
+ expect(core.protect.inputTracing.handleCommandInjection).to.have.been.calledWith(
53
+ store.protect,
54
+ { name, value }
55
+ );
56
+ });
57
+ });
58
+ });
59
+
60
+ describe('handleCommandInjection() is not called when method args are not relevant', function() {
61
+ const methodArgs = [1, undefined];
62
+
63
+ ['exec', 'execSync'].forEach((method) => {
64
+ const name = `child_process.${method}`;
65
+
66
+ it(name, function() {
67
+ cp[method](...methodArgs);
68
+
69
+ expect(core.captureStacktrace).to.not.have.been.called;
70
+ expect(core.protect.inputTracing.handleCommandInjection).to.not.have.been.called;
71
+ });
72
+ });
73
+ });
74
+
75
+ describe('handleCommandInjection() is not called when instrumentation is locked', function() {
76
+ const methodArgs = ['foo', 'bar'];
77
+
78
+ beforeEach(function() {
79
+ core.scopes.instrumentation.isLocked.returns(true);
80
+ });
81
+
82
+ ['exec', 'execSync'].forEach((method) => {
83
+ const name = `child_process.${method}`;
84
+
85
+ it(name, function() {
86
+ cp[method](...methodArgs);
87
+
88
+ expect(core.captureStacktrace).to.not.have.been.called;
89
+ expect(core.protect.inputTracing.handleCommandInjection).to.not.have.been.called;
90
+ });
91
+ });
92
+ });
93
+
94
+ describe('handleCommandInjection() is not called when there is no Protect sourceContext', function() {
95
+ const methodArgs = ['foo', 'bar'];
96
+
97
+ beforeEach(function() {
98
+ core.scopes.sources.getStore.returns({ protect: null });
99
+ });
100
+
101
+ ['exec', 'execSync'].forEach((method) => {
102
+ const name = `child_process.${method}`;
103
+
104
+ it(name, function() {
105
+ cp[method](...methodArgs);
106
+
107
+ expect(core.captureStacktrace).to.not.have.been.called;
108
+ expect(core.protect.inputTracing.handleCommandInjection).to.not.have.been.called;
109
+ });
110
+ });
111
+ });
112
+ });
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ const { isString } = require('@contrast/common');
4
+ const { patchType } = require('../constants');
5
+
6
+ const fsMethods = [
7
+ { method: 'access' },
8
+ { method: 'accessSync' },
9
+ { method: 'appendFile' },
10
+ { method: 'appendFileSync' },
11
+ { method: 'chmod' },
12
+ { method: 'chmodSync' },
13
+ { method: 'chown' },
14
+ { method: 'chownSync' },
15
+ { method: 'copyFile', indices: [0, 1] },
16
+ { method: 'copyFileSync', indices: [0, 1] },
17
+ { method: 'createReadStream' },
18
+ { method: 'createWriteStream' },
19
+ { method: 'lchmod' },
20
+ { method: 'lchmodSync' },
21
+ { method: 'lchown' },
22
+ { method: 'lchownSync' },
23
+ { method: 'mkdir' },
24
+ { method: 'mkdirSync' },
25
+ { method: 'open' },
26
+ { method: 'openSync' },
27
+ { method: 'readFile' },
28
+ { method: 'readFileSync' },
29
+ { method: 'readdir' },
30
+ { method: 'readdirSync' },
31
+ { method: 'readlink' },
32
+ { method: 'readlinkSync' },
33
+ { method: 'rename', indices: [0, 1] },
34
+ { method: 'renameSync', indices: [0, 1] },
35
+ { method: 'rmdir' },
36
+ { method: 'rmdirSync' },
37
+ { method: 'symlink', indices: [0, 1] },
38
+ { method: 'symlinkSync', indices: [0, 1] },
39
+ { method: 'truncate' },
40
+ { method: 'truncateSync' },
41
+ { method: 'unlink' },
42
+ { method: 'unlinkSync' },
43
+ { method: 'writeFile' },
44
+ { method: 'writeFileSync' },
45
+ ];
46
+
47
+ module.exports = function(core) {
48
+ const {
49
+ scopes: { sources, instrumentation },
50
+ patcher,
51
+ depHooks,
52
+ captureStacktrace,
53
+ protect: { inputTracing }
54
+ } = core;
55
+
56
+ function getValues(indices, args) {
57
+ return indices.reduce((acc, idx) => {
58
+ const value = args[idx];
59
+ if (value && isString(value)) acc.push(value);
60
+ return acc;
61
+ }, []);
62
+ }
63
+
64
+ function install() {
65
+ depHooks.resolve({ name: 'fs' }, fs => {
66
+ fsMethods.forEach(({ method, indices = [0] }) => {
67
+ const name = `fs.${method}`;
68
+ patcher.patch(fs, method, {
69
+ name,
70
+ patchType,
71
+ pre({ args, hooked, name }) {
72
+ // don't proceed if instrumentation is off e.g. within require() call
73
+ if (instrumentation.isLocked()) return;
74
+
75
+ // obtain the Protect sourceContext
76
+ const sourceContext = sources.getStore()?.protect;
77
+ if (!sourceContext) return;
78
+
79
+ // build list of values on which to perform INPUT TRACING
80
+ const values = getValues(indices, args);
81
+ if (!values.length) return;
82
+
83
+ // while we need to check whether instrumentation is locked above, we
84
+ // don't need to necessarily need to lock it here - there are no
85
+ // lower-level calls that we instrument
86
+ for (const value of values) {
87
+ const sinkContext = captureStacktrace(
88
+ { name, value },
89
+ { constructorOpt: hooked }
90
+ );
91
+ inputTracing.handlePathTraversal(sourceContext, sinkContext);
92
+ }
93
+ }
94
+ });
95
+ });
96
+ });
97
+ }
98
+
99
+ const fsInstrumentation = inputTracing.fsInstrumentation = {
100
+ getValues,
101
+ install,
102
+ };
103
+
104
+ return fsInstrumentation;
105
+ };
106
+
107
+ module.exports.fsMethods = fsMethods;
@@ -0,0 +1,118 @@
1
+ 'use strict';
2
+
3
+ const sinon = require('sinon');
4
+ const { expect } = require('chai');
5
+
6
+ describe('protect input-tracing fs', function() {
7
+ const fsInstr = require('./fs');
8
+ const store = { protect: {} };
9
+ let fs;
10
+ let core;
11
+
12
+ function makeFsMock() {
13
+ return fsInstr.fsMethods.reduce((fs, { method, indices }) => {
14
+ fs[method] = sinon.stub();
15
+ return fs;
16
+ }, {});
17
+ }
18
+
19
+ beforeEach(function() {
20
+ const mocks = require('../../../../test/mocks');
21
+ const patcher = require('@contrast/patcher');
22
+
23
+ fs = makeFsMock();
24
+
25
+ core = mocks.core();
26
+ core.logger = mocks.logger();
27
+ core.depHooks = mocks.depHooks();
28
+ core.depHooks.resolve.yields(fs);
29
+ core.scopes = mocks.scopes();
30
+ sinon.stub(core.scopes.sources, 'getStore').returns(store);
31
+ core.patcher = patcher(core);
32
+ core.protect = mocks.protect();
33
+
34
+ fsInstr(core);
35
+
36
+ core.protect.inputTracing.fsInstrumentation.install();
37
+ });
38
+
39
+ describe('handlePathTraversal() is called with expected values', function() {
40
+ const methodArgs = ['foo', 'bar'];
41
+
42
+ for (const { method, indices = [0] } of fsInstr.fsMethods) {
43
+ const name = `fs.${method}`;
44
+
45
+ it(`${name}`, function() {
46
+ fs[method](...methodArgs);
47
+
48
+ const calledWith = indices.length === 0 ? 'calledOnceWith' : 'calledWith';
49
+ for (const idx of indices) {
50
+
51
+ const value = methodArgs[idx];
52
+
53
+ // this is profound - we are asserting that the stacktrace constructor
54
+ // opt is the original method
55
+ expect(core.captureStacktrace).to.have.been[calledWith](
56
+ { name, value },
57
+ { constructorOpt: fs[method] }
58
+ );
59
+
60
+ expect(core.protect.inputTracing.handlePathTraversal).to.have.been.calledWith(
61
+ store.protect,
62
+ { name, value }
63
+ );
64
+ }
65
+ });
66
+ }
67
+ });
68
+
69
+ describe('handlePathTraversal() is not called when method args are not relevant', function() {
70
+ const methodArgs = [1, undefined];
71
+
72
+ for (const { method } of fsInstr.fsMethods) {
73
+ const name = `fs.${method}`;
74
+
75
+ it(name, function() {
76
+ fs[method](...methodArgs);
77
+
78
+ expect(core.protect.inputTracing.handlePathTraversal).not.to.have.been.called;
79
+ });
80
+ }
81
+ });
82
+
83
+ describe('handlePathTraversal() is not called when instrumentation is locked', function() {
84
+ const methodArgs = ['foo', 'bar'];
85
+
86
+ beforeEach(function() {
87
+ core.scopes.instrumentation.isLocked.returns(true);
88
+ });
89
+
90
+ for (const { method } of fsInstr.fsMethods) {
91
+ const name = `fs.${method}`;
92
+
93
+ it(name, function() {
94
+ fs[method](...methodArgs);
95
+
96
+ expect(core.protect.inputTracing.handlePathTraversal).not.to.have.been.called;
97
+ });
98
+ }
99
+ });
100
+
101
+ describe('handlePathTraversal() is not called when there is no Protect sourceContext', function() {
102
+ const methodArgs = ['foo', 'bar'];
103
+
104
+ beforeEach(function() {
105
+ core.scopes.sources.getStore.returns({ protect: null });
106
+ });
107
+
108
+ for (const { method } of fsInstr.fsMethods) {
109
+ const name = `fs.${method}`;
110
+
111
+ it(name, function() {
112
+ fs[method](...methodArgs);
113
+
114
+ expect(core.protect.inputTracing.handlePathTraversal).not.to.have.been.called;
115
+ });
116
+ }
117
+ });
118
+ });
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ const { isString } = require('@contrast/common');
4
+ const { patchType } = require('../constants');
5
+
6
+ module.exports = function(core) {
7
+ const {
8
+ depHooks,
9
+ patcher,
10
+ captureStacktrace,
11
+ scopes: { sources },
12
+ protect: { inputTracing }
13
+ } = core;
14
+
15
+ const mysqlInstr = {};
16
+
17
+ mysqlInstr.getValueFromArgs = function([value]) {
18
+ if (isString(value)) {
19
+ return value;
20
+ }
21
+ };
22
+
23
+ mysqlInstr.install = function() {
24
+ [
25
+ { module: 'mysql', file: 'lib/Connection.js', method: 'query' },
26
+ { module: 'mysql2', file: 'lib/Connection.js', method: 'execute' }
27
+ ].forEach(
28
+ ({ module, file, method }) => {
29
+ depHooks.resolve({ module, file, method }, conn => {
30
+ const name = `${module}.Connection.prototype.${method}`;
31
+
32
+ patcher.patch(conn.prototype, method, {
33
+ name,
34
+ patchType,
35
+ pre({ args, hooked, name }) {
36
+ const sourceContext = sources.getStore()?.protect;
37
+ if (!sourceContext) return;
38
+
39
+ const value = mysqlInstr.getValueFromArgs(args);
40
+ if (!value) return;
41
+
42
+ const sinkContext = captureStacktrace(
43
+ { name, value },
44
+ { constructorOpt: hooked }
45
+ );
46
+
47
+ inputTracing.handleSqlInjection(sourceContext, sinkContext);
48
+ }
49
+ });
50
+ });
51
+ });
52
+ };
53
+
54
+ core.protect.inputTracing.mysqlInstrumentation = mysqlInstr;
55
+
56
+ return mysqlInstr;
57
+ };
@@ -0,0 +1,108 @@
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
+ });
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ const { isString } = require('@contrast/common');
4
+ const { patchType } = require('../constants');
5
+
6
+ module.exports = function(core) {
7
+ const {
8
+ depHooks,
9
+ patcher,
10
+ scopes: { sources },
11
+ captureStacktrace,
12
+ protect: { inputTracing }
13
+ } = core;
14
+
15
+ function getQueryFromArgs([value]) {
16
+ const query = value?.text || value;
17
+ if (query && isString(query)) return query;
18
+ }
19
+
20
+ function preHook({ args, hooked, name }) {
21
+ const sourceContext = sources.getStore()?.protect;
22
+ if (!sourceContext) return;
23
+
24
+ const value = getQueryFromArgs(args);
25
+ if (!value) return;
26
+
27
+ const sinkContext = captureStacktrace(
28
+ { name, value },
29
+ { constructorOpt: hooked }
30
+ );
31
+
32
+ inputTracing.handleSqlInjection(sourceContext, sinkContext);
33
+ }
34
+
35
+ function install() {
36
+ depHooks.resolve({ name: 'pg', file: 'lib/client.js' }, client => {
37
+ const name = 'pg.Client.prototype.query';
38
+ patcher.patch(client.prototype, 'query', {
39
+ name,
40
+ patchType,
41
+ pre: preHook
42
+ });
43
+ });
44
+
45
+ depHooks.resolve({ name: 'pg-pool' }, pool => {
46
+ const name = 'pg-pool.Pool.prototype.query';
47
+ patcher.patch(pool.prototype, 'query', {
48
+ name,
49
+ patchType,
50
+ pre: preHook
51
+ });
52
+ });
53
+ }
54
+
55
+ const postgresInstr = core.protect.inputTracing.postgresInstrumentation = {
56
+ getQueryFromArgs,
57
+ install
58
+ };
59
+
60
+ return postgresInstr;
61
+ };
@@ -0,0 +1,125 @@
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
+ });