@contrast/protect 1.49.0 → 1.51.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.
- package/lib/error-handlers/index.js +1 -1
- package/lib/error-handlers/index.test.js +1 -1
- package/lib/error-handlers/init-domain.js +4 -39
- package/lib/error-handlers/init-domain.test.js +3 -15
- package/lib/error-handlers/install/express.js +162 -0
- package/lib/error-handlers/install/express.test.js +290 -0
- package/lib/error-handlers/install/hapi.js +2 -2
- package/lib/error-handlers/install/hapi.test.js +2 -2
- package/lib/error-handlers/install/koa2.js +1 -1
- package/lib/error-handlers/install/restify.js +1 -1
- package/lib/error-handlers/install/restify.test.js +1 -1
- package/lib/hardening/install/node-serialize0.js +2 -2
- package/lib/hardening/install/node-serialize0.test.js +1 -4
- package/lib/index.d.ts +3 -3
- package/lib/input-analysis/index.js +1 -1
- package/lib/input-analysis/index.test.js +1 -1
- package/lib/input-analysis/install/body-parser1.js +2 -2
- package/lib/input-analysis/install/busboy1.js +1 -1
- package/lib/input-analysis/install/cookie-parser1.js +1 -1
- package/lib/input-analysis/install/{express4.js → express.js} +61 -20
- package/lib/input-analysis/install/{express4.test.js → express.test.js} +92 -59
- package/lib/input-analysis/install/formidable1.js +1 -1
- package/lib/input-analysis/install/hapi.js +1 -1
- package/lib/input-analysis/install/hapi.test.js +6 -14
- package/lib/input-analysis/install/http.js +2 -9
- package/lib/input-analysis/install/koa-body5.js +1 -1
- package/lib/input-analysis/install/koa-bodyparser4.js +1 -1
- package/lib/input-analysis/install/koa2.js +5 -5
- package/lib/input-analysis/install/multer1.js +1 -1
- package/lib/input-analysis/install/qs6.js +1 -1
- package/lib/input-analysis/install/restify.js +1 -1
- package/lib/input-analysis/install/restify.test.js +1 -1
- package/lib/input-analysis/install/universal-cookie4.js +1 -1
- package/lib/input-tracing/install/child-process.js +1 -1
- package/lib/input-tracing/install/fs.js +2 -2
- package/lib/input-tracing/install/fs.test.js +2 -2
- package/lib/input-tracing/install/http.js +2 -2
- package/lib/input-tracing/install/http2.js +2 -2
- package/lib/input-tracing/install/marsdb.js +2 -2
- package/lib/input-tracing/install/marsdb.test.js +1 -1
- package/lib/input-tracing/install/mongodb.js +2 -2
- package/lib/input-tracing/install/mongodb.test.js +2 -4
- package/lib/input-tracing/install/mssql.js +3 -3
- package/lib/input-tracing/install/mssql.test.js +2 -2
- package/lib/input-tracing/install/mysql.js +7 -9
- package/lib/input-tracing/install/postgres.js +3 -3
- package/lib/input-tracing/install/postgres.test.js +2 -10
- package/lib/input-tracing/install/sequelize.js +2 -2
- package/lib/input-tracing/install/spdy.js +2 -2
- package/lib/input-tracing/install/sqlite3.js +2 -2
- package/lib/input-tracing/install/vm.js +2 -2
- package/lib/semantic-analysis/install/libxmljs.js +3 -3
- package/lib/semantic-analysis/install/libxmljs.test.js +2 -2
- package/package.json +12 -14
- package/lib/error-handlers/install/express4.js +0 -138
- package/lib/error-handlers/install/express4.test.js +0 -238
|
@@ -25,7 +25,7 @@ module.exports = function(core) {
|
|
|
25
25
|
require('./init-domain')(core);
|
|
26
26
|
|
|
27
27
|
// installers
|
|
28
|
-
require('./install/
|
|
28
|
+
require('./install/express')(core);
|
|
29
29
|
require('./install/fastify')(core);
|
|
30
30
|
require('./install/hapi')(core);
|
|
31
31
|
require('./install/koa2')(core);
|
|
@@ -15,47 +15,12 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
const semver = require('semver');
|
|
18
|
+
const { Domain } = require('async-hook-domain');
|
|
20
19
|
|
|
21
20
|
module.exports = function(core) {
|
|
22
|
-
const {
|
|
23
|
-
logger,
|
|
24
|
-
protect: { errorHandlers }
|
|
25
|
-
} = core;
|
|
21
|
+
const { protect: { errorHandlers } } = core;
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return errorHandlers.initDomain = function(req, res) {
|
|
30
|
-
const { AsyncHookDomain, Domain } = errorHandlers;
|
|
31
|
-
|
|
32
|
-
if (AsyncHookDomain) {
|
|
33
|
-
new AsyncHookDomain(errorHandlers.commonHandler);
|
|
34
|
-
} else {
|
|
35
|
-
const domain = new Domain();
|
|
36
|
-
domain.add(req);
|
|
37
|
-
domain.add(res);
|
|
38
|
-
domain.on('error', errorHandlers.commonHandler);
|
|
39
|
-
return domain;
|
|
40
|
-
}
|
|
23
|
+
return errorHandlers.initDomain = function initDomain() {
|
|
24
|
+
return new Domain(errorHandlers.commonHandler);
|
|
41
25
|
};
|
|
42
|
-
|
|
43
|
-
function initSupportedDomainPackage() {
|
|
44
|
-
let AsyncHookDomain, Domain;
|
|
45
|
-
|
|
46
|
-
if (semver.lt(process.version, '16.0.0')) {
|
|
47
|
-
logger.info(
|
|
48
|
-
'%s. %s. %s.',
|
|
49
|
-
'falling back to deprecated \'domain\' module for async SecurityException handling',
|
|
50
|
-
'upgrade to Node 16 LTS or above to allow use of \'async-hook-domain\' modern alternative',
|
|
51
|
-
'upgrading will resolve any deprecation warnings and prevent the logging of this message'
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
Domain = require('domain').Domain;
|
|
55
|
-
} else {
|
|
56
|
-
AsyncHookDomain = require('async-hook-domain');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return { AsyncHookDomain, Domain };
|
|
60
|
-
}
|
|
61
26
|
};
|
|
@@ -5,30 +5,18 @@ const sinon = require('sinon');
|
|
|
5
5
|
const { initProtectFixture } = require('@contrast/test/fixtures');
|
|
6
6
|
|
|
7
7
|
describe('protect error-handlers init-domain', function () {
|
|
8
|
-
let core
|
|
8
|
+
let core;
|
|
9
9
|
|
|
10
10
|
beforeEach(function () {
|
|
11
11
|
({ core } = initProtectFixture());
|
|
12
12
|
|
|
13
13
|
sinon.stub(core.protect.errorHandlers, 'commonHandler');
|
|
14
|
-
|
|
15
|
-
errorHandlers = core.protect.errorHandlers;
|
|
16
|
-
initDomain = errorHandlers.initDomain;
|
|
17
14
|
});
|
|
18
15
|
|
|
19
16
|
describe('domain integration', function () {
|
|
20
|
-
beforeEach(function () {
|
|
21
|
-
sinon.stub(errorHandlers, 'AsyncHookDomain');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('initializes correct domain', function () {
|
|
25
|
-
expect(core.protect.errorHandlers.AsyncHookDomain).to.be.a('function');
|
|
26
|
-
expect(core.protect.errorHandlers.Domain).to.be.undefined;
|
|
27
|
-
});
|
|
28
|
-
|
|
29
17
|
it('instantiates async-hook-domain with common error handler', function () {
|
|
30
|
-
initDomain();
|
|
31
|
-
expect(
|
|
18
|
+
const d = core.protect.errorHandlers.initDomain();
|
|
19
|
+
expect(d.onerror).to.equal(core.protect.errorHandlers.commonHandler);
|
|
32
20
|
});
|
|
33
21
|
});
|
|
34
22
|
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
|
+
* Contact: support@contrastsecurity.com
|
|
4
|
+
* License: Commercial
|
|
5
|
+
|
|
6
|
+
* NOTICE: This Software and the patented inventions embodied within may only be
|
|
7
|
+
* used as part of Contrast Security’s commercial offerings. Even though it is
|
|
8
|
+
* made available through public repositories, use of this Software is subject to
|
|
9
|
+
* the applicable End User Licensing Agreement found at
|
|
10
|
+
* https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
11
|
+
* between Contrast Security and the End User. The Software may not be reverse
|
|
12
|
+
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
|
+
* way not consistent with the End User License Agreement.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const SecurityException = require('../../security-exception');
|
|
19
|
+
const { patchType } = require('../constants');
|
|
20
|
+
|
|
21
|
+
module.exports = function (core) {
|
|
22
|
+
const {
|
|
23
|
+
logger,
|
|
24
|
+
depHooks,
|
|
25
|
+
patcher,
|
|
26
|
+
protect,
|
|
27
|
+
} = core;
|
|
28
|
+
|
|
29
|
+
const expressErrorHandler = protect.errorHandlers.expressErrorHandler = {};
|
|
30
|
+
|
|
31
|
+
function checkSecurityException(err, funcKey, orig, throwErr = false) {
|
|
32
|
+
const sourceContext = protect.getSourceContext();
|
|
33
|
+
const isSecurityException = SecurityException.isSecurityException(err);
|
|
34
|
+
|
|
35
|
+
if (isSecurityException && sourceContext) {
|
|
36
|
+
const blockInfo = sourceContext.securityException;
|
|
37
|
+
|
|
38
|
+
sourceContext.block(...blockInfo);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!sourceContext && isSecurityException) {
|
|
43
|
+
logger.info({ funcKey }, 'source context not found; unable to handle response');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (throwErr) {
|
|
48
|
+
throw err;
|
|
49
|
+
} else {
|
|
50
|
+
return orig();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
expressErrorHandler.install = function () {
|
|
55
|
+
|
|
56
|
+
// Express 4 and 5
|
|
57
|
+
depHooks.resolve({ name: 'finalhandler', version: '<3' }, (finalhandler) =>
|
|
58
|
+
patcher.patch(finalhandler, {
|
|
59
|
+
name: 'finalHandler',
|
|
60
|
+
patchType,
|
|
61
|
+
post(data) {
|
|
62
|
+
data.result = patcher.patch(data.result, {
|
|
63
|
+
name: 'finalHandler.returnedFunction',
|
|
64
|
+
patchType,
|
|
65
|
+
around(orig, data) {
|
|
66
|
+
const [err] = data.args;
|
|
67
|
+
checkSecurityException(err, data.funcKey, orig);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
function postHook() {
|
|
75
|
+
return function(data) {
|
|
76
|
+
patcher.patch(data.result, 'handle', {
|
|
77
|
+
name: 'handle',
|
|
78
|
+
patchType,
|
|
79
|
+
post(data) {
|
|
80
|
+
data.result = data.result?.catch?.((err) => {
|
|
81
|
+
checkSecurityException(err, data.funcKey, data.orig, true);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Express 4
|
|
89
|
+
depHooks.resolve({ name: 'express', version: '4', file: 'lib/router/layer.js' }, (Layer) => {
|
|
90
|
+
|
|
91
|
+
patcher.patch(Layer.prototype, 'handle_error', {
|
|
92
|
+
name: 'Layer.prototype.handle_error',
|
|
93
|
+
patchType,
|
|
94
|
+
around(orig, data) {
|
|
95
|
+
const [err] = data.args;
|
|
96
|
+
checkSecurityException(err, data.funcKey, orig);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return patcher.patch(Layer, {
|
|
101
|
+
name: 'Layer',
|
|
102
|
+
patchType,
|
|
103
|
+
post: postHook()
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Express 5
|
|
109
|
+
depHooks.resolve({ name: 'router', version: '2', file: 'lib/layer.js' }, (Layer) => {
|
|
110
|
+
|
|
111
|
+
patcher.patch(Layer.prototype, 'handleError', {
|
|
112
|
+
name: 'Layer.prototype.handleError',
|
|
113
|
+
patchType,
|
|
114
|
+
around(orig, data) {
|
|
115
|
+
const [err] = data.args;
|
|
116
|
+
checkSecurityException(err, data.funcKey, orig);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return patcher.patch(Layer, {
|
|
121
|
+
name: 'router.Layer',
|
|
122
|
+
patchType,
|
|
123
|
+
post: postHook()
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
function preHook() {
|
|
128
|
+
return function(data) {
|
|
129
|
+
if (typeof data.args[1] === 'function') {
|
|
130
|
+
patcher.patch(data.args, '1', {
|
|
131
|
+
name: 'express.route-handler',
|
|
132
|
+
patchType,
|
|
133
|
+
post(data) {
|
|
134
|
+
data.result = data.result?.catch?.((err) => {
|
|
135
|
+
checkSecurityException(err, data.funcKey, data.orig, true);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Express 4
|
|
144
|
+
depHooks.resolve({ name: 'express', version: '4', file: 'lib/router/index.js' }, (Router) => {
|
|
145
|
+
patcher.patch(Router.prototype.constructor, 'param', {
|
|
146
|
+
name: 'Router.prototype.constructor.param',
|
|
147
|
+
patchType,
|
|
148
|
+
pre: preHook()
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Express 5
|
|
153
|
+
depHooks.resolve({ name: 'router', version: '2' }, (Router) => {
|
|
154
|
+
patcher.patch(Router.prototype, 'param', {
|
|
155
|
+
name: 'Router.prototype.param',
|
|
156
|
+
patchType,
|
|
157
|
+
pre: preHook()
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
return expressErrorHandler;
|
|
162
|
+
};
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const sinon = require('sinon');
|
|
5
|
+
const { expect } = require('chai');
|
|
6
|
+
const scopes = require('@contrast/scopes');
|
|
7
|
+
const patcher = require('@contrast/patcher');
|
|
8
|
+
const mocks = require('@contrast/test/mocks');
|
|
9
|
+
const SecurityException = require('../../security-exception');
|
|
10
|
+
|
|
11
|
+
describe('protect error-handlers express', function () {
|
|
12
|
+
let core, store, errorHandlerInstr;
|
|
13
|
+
|
|
14
|
+
const throwingHandler = (error) => async function () {
|
|
15
|
+
await Promise.reject(error);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
beforeEach(function () {
|
|
19
|
+
core = mocks.core();
|
|
20
|
+
core.config = mocks.config();
|
|
21
|
+
core.logger = mocks.logger();
|
|
22
|
+
core.scopes = scopes(core);
|
|
23
|
+
core.protect = mocks.protect();
|
|
24
|
+
require('../../get-source-context')(core);
|
|
25
|
+
core.depHooks = mocks.depHooks();
|
|
26
|
+
core.patcher = patcher(core);
|
|
27
|
+
|
|
28
|
+
store = {
|
|
29
|
+
protect: {
|
|
30
|
+
block: sinon.stub(),
|
|
31
|
+
securityException: ['block', 'cmd-injection']
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
sinon.spy(core.patcher, 'patch');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('finalhandler', function () {
|
|
39
|
+
let finalhandler, returnedFn;
|
|
40
|
+
|
|
41
|
+
beforeEach(function () {
|
|
42
|
+
finalhandler = function () {
|
|
43
|
+
return function () { };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
core.depHooks.resolve.withArgs({ name: 'finalhandler', version: '<3' }).yields(finalhandler);
|
|
47
|
+
|
|
48
|
+
errorHandlerInstr = require('./express')(core);
|
|
49
|
+
errorHandlerInstr.install();
|
|
50
|
+
|
|
51
|
+
const patchedFinalhandler = core.patcher.patch.getCall(0).returnValue;
|
|
52
|
+
returnedFn = patchedFinalhandler();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should block the request when there is SecurityException', function () {
|
|
56
|
+
const error = SecurityException.create();
|
|
57
|
+
|
|
58
|
+
core.scopes.sources.run(store, () => {
|
|
59
|
+
returnedFn(error);
|
|
60
|
+
expect(core.logger.info).not.to.have.been.called;
|
|
61
|
+
expect(store.protect.block).to.have.been.calledWith('block', 'cmd-injection');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should not block the request when there is SecurityException but sourceContext is missing', function () {
|
|
66
|
+
const error = SecurityException.create();
|
|
67
|
+
|
|
68
|
+
core.scopes.sources.run({}, () => {
|
|
69
|
+
returnedFn(error);
|
|
70
|
+
expect(core.logger.info).to.have.been.calledWith(
|
|
71
|
+
{ funcKey: 'protect-error-handling:finalHandler.returnedFunction' },
|
|
72
|
+
'source context not found; unable to handle response',
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should skip the instrumentation when there is no SecurityException', function () {
|
|
78
|
+
const error = new Error('Error');
|
|
79
|
+
|
|
80
|
+
core.scopes.sources.run({}, () => {
|
|
81
|
+
returnedFn(error);
|
|
82
|
+
expect(core.logger.info).not.to.have.been.called;
|
|
83
|
+
expect(store.protect.block).not.to.have.been.called;
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
[
|
|
89
|
+
{ name: 'express', version: '4', file: 'lib/router/layer.js' },
|
|
90
|
+
{ name: 'router', version: '2', file: 'lib/layer.js' }
|
|
91
|
+
].forEach((args) => {
|
|
92
|
+
const fn = args.name === 'express' ? 'handle_error' : 'handleError';
|
|
93
|
+
describe(`${args.name} Layer.prototype.${fn}`, function () {
|
|
94
|
+
let Layer;
|
|
95
|
+
|
|
96
|
+
beforeEach(function () {
|
|
97
|
+
Layer = function () { };
|
|
98
|
+
|
|
99
|
+
Layer.prototype[fn] = function () { };
|
|
100
|
+
|
|
101
|
+
core.depHooks.resolve.withArgs(args).yields(Layer);
|
|
102
|
+
|
|
103
|
+
errorHandlerInstr = require('./express')(core);
|
|
104
|
+
errorHandlerInstr.install();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should block the request when there is SecurityException', function () {
|
|
108
|
+
const error = SecurityException.create();
|
|
109
|
+
|
|
110
|
+
core.scopes.sources.run(store, () => {
|
|
111
|
+
Layer.prototype[fn](error);
|
|
112
|
+
expect(core.logger.info).not.to.have.been.called;
|
|
113
|
+
expect(store.protect.block).to.have.been.calledWith('block', 'cmd-injection');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should not block the request when there is SecurityException but sourceContext is missing', function () {
|
|
118
|
+
const error = SecurityException.create();
|
|
119
|
+
|
|
120
|
+
core.scopes.sources.run({}, () => {
|
|
121
|
+
Layer.prototype[fn](error);
|
|
122
|
+
expect(core.logger.info).to.have.been.calledWith(
|
|
123
|
+
{ funcKey: `protect-error-handling:Layer.prototype.${fn}` },
|
|
124
|
+
'source context not found; unable to handle response'
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should skip the instrumentation when there is no SecurityException', function () {
|
|
130
|
+
const error = new Error('Error');
|
|
131
|
+
|
|
132
|
+
core.scopes.sources.run({}, () => {
|
|
133
|
+
Layer.prototype[fn](error);
|
|
134
|
+
expect(core.logger.info).not.to.have.been.called;
|
|
135
|
+
expect(store.protect.block).not.to.have.been.called;
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe(`${args.name} Layer handle`, function () {
|
|
141
|
+
let Layer, patchedLayer;
|
|
142
|
+
const error = SecurityException.create();
|
|
143
|
+
const fn = throwingHandler(error);
|
|
144
|
+
beforeEach(function () {
|
|
145
|
+
Layer = function () {
|
|
146
|
+
return {
|
|
147
|
+
handle: fn
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
errorHandlerInstr = require('./express')(core);
|
|
152
|
+
errorHandlerInstr.install();
|
|
153
|
+
[patchedLayer] = core.depHooks.resolve.withArgs(args).yield(Layer);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should block the request when there is SecurityException', async function () {
|
|
157
|
+
await core.scopes.sources.run(store, async () => {
|
|
158
|
+
await patchedLayer().handle();
|
|
159
|
+
});
|
|
160
|
+
expect(core.logger.info).not.to.have.been.called;
|
|
161
|
+
expect(store.protect.block).to.have.been.calledWith('block', 'cmd-injection');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should not block the request when there is SecurityException but sourceContext is missing', async function () {
|
|
165
|
+
await core.scopes.sources.run({}, async () => {
|
|
166
|
+
await patchedLayer().handle();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(core.logger.info).to.have.been.calledWith(
|
|
170
|
+
{ funcKey: 'protect-error-handling:handle' },
|
|
171
|
+
'source context not found; unable to handle response',
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should skip the instrumentation when there is no SecurityException', async function () {
|
|
176
|
+
const error = new Error('Error');
|
|
177
|
+
const fn = throwingHandler(error);
|
|
178
|
+
Layer = function () {
|
|
179
|
+
return {
|
|
180
|
+
handle: fn
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
errorHandlerInstr = require('./express')(core);
|
|
185
|
+
errorHandlerInstr.install();
|
|
186
|
+
[patchedLayer] = core.depHooks.resolve.withArgs(args).yield(Layer);
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
await core.scopes.sources.run(store, async () => {
|
|
190
|
+
await patchedLayer().handle();
|
|
191
|
+
});
|
|
192
|
+
} catch (err) {
|
|
193
|
+
expect(err).to.equal(error);
|
|
194
|
+
expect(core.logger.info).not.to.have.been.called;
|
|
195
|
+
expect(store.protect.block).not.to.have.been.called;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
[
|
|
202
|
+
{ name: 'express', version: '4', file: 'lib/router/index.js' },
|
|
203
|
+
{ name: 'router', version: '2' }
|
|
204
|
+
].forEach((args) => {
|
|
205
|
+
describe(`${args.name} Router param`, function () {
|
|
206
|
+
let Router, param;
|
|
207
|
+
const sampleFn = function () { };
|
|
208
|
+
|
|
209
|
+
beforeEach(function () {
|
|
210
|
+
|
|
211
|
+
Router = function() { };
|
|
212
|
+
if (args.name === 'express') {
|
|
213
|
+
Router.prototype.constructor = {
|
|
214
|
+
param: function () { },
|
|
215
|
+
};
|
|
216
|
+
param = (...args) => Router.prototype.constructor.param(...args);
|
|
217
|
+
} else {
|
|
218
|
+
Router.prototype = {
|
|
219
|
+
param: function () { },
|
|
220
|
+
};
|
|
221
|
+
param = (...args) => Router.prototype.param(...args);
|
|
222
|
+
}
|
|
223
|
+
core.depHooks.resolve.withArgs(args).yields(Router);
|
|
224
|
+
|
|
225
|
+
core.patcher.patch.resetHistory();
|
|
226
|
+
|
|
227
|
+
errorHandlerInstr = require('./express')(core);
|
|
228
|
+
errorHandlerInstr.install();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should patch the function for the param property', function () {
|
|
232
|
+
param('sampleFn', sampleFn);
|
|
233
|
+
expect(core.patcher.patch).to.have.been.calledWith(sinon.match.array, '1', sinon.match.object);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should block the request when there is SecurityException', async function () {
|
|
237
|
+
const error = SecurityException.create();
|
|
238
|
+
const fn = throwingHandler(error);
|
|
239
|
+
|
|
240
|
+
param('fn', fn);
|
|
241
|
+
|
|
242
|
+
const patchedFn = core.patcher.patch.getCall(1).returnValue[1];
|
|
243
|
+
|
|
244
|
+
await core.scopes.sources.run(store, async () => {
|
|
245
|
+
await patchedFn();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
expect(core.logger.info).not.to.have.been.called;
|
|
249
|
+
expect(store.protect.block).to.have.been.calledWith('block', 'cmd-injection');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should not block the request when there is SecurityException but sourceContext is missing', async function () {
|
|
253
|
+
const error = SecurityException.create();
|
|
254
|
+
const fn = throwingHandler(error);
|
|
255
|
+
|
|
256
|
+
param('fn', fn);
|
|
257
|
+
|
|
258
|
+
const patchedFn = core.patcher.patch.getCall(1).returnValue[1];
|
|
259
|
+
|
|
260
|
+
await core.scopes.sources.run({}, async () => {
|
|
261
|
+
await patchedFn();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
expect(core.logger.info).to.have.been.calledWith(
|
|
265
|
+
{ funcKey: 'protect-error-handling:express.route-handler' },
|
|
266
|
+
'source context not found; unable to handle response',
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should skip the instrumentation when there is no SecurityException', async function () {
|
|
271
|
+
const error = new Error('Error');
|
|
272
|
+
const fn = throwingHandler(error);
|
|
273
|
+
|
|
274
|
+
param('fn', fn);
|
|
275
|
+
|
|
276
|
+
const patchedFn = core.patcher.patch.getCall(1).returnValue[1];
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
await core.scopes.sources.run(store, async () => {
|
|
280
|
+
await patchedFn();
|
|
281
|
+
});
|
|
282
|
+
} catch (err) {
|
|
283
|
+
expect(err).to.equal(error);
|
|
284
|
+
expect(core.logger.info).not.to.have.been.called;
|
|
285
|
+
expect(store.protect.block).not.to.have.been.called;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
});
|
|
@@ -60,12 +60,12 @@ module.exports = function (core) {
|
|
|
60
60
|
|
|
61
61
|
hapiErrorHandler.install = function () {
|
|
62
62
|
depHooks.resolve(
|
|
63
|
-
{ name: 'boom' },
|
|
63
|
+
{ name: 'boom', version: '<8' },
|
|
64
64
|
(boom) => registerErrorHandler(boom, 'boom'),
|
|
65
65
|
);
|
|
66
66
|
|
|
67
67
|
depHooks.resolve(
|
|
68
|
-
{ name: '@hapi/boom' },
|
|
68
|
+
{ name: '@hapi/boom', version: '<11' },
|
|
69
69
|
(boom) => registerErrorHandler(boom, '@hapi/boom'),
|
|
70
70
|
);
|
|
71
71
|
};
|
|
@@ -39,8 +39,8 @@ describe('protect error-handlers hapi', function () {
|
|
|
39
39
|
Boom.boomify = function boom() { };
|
|
40
40
|
HapiBoom.boomify = function hapiBoom() { };
|
|
41
41
|
|
|
42
|
-
core.depHooks.resolve.withArgs({ name: Boom.name }).yields(Boom);
|
|
43
|
-
core.depHooks.resolve.withArgs({ name: HapiBoom.name }).yields(HapiBoom);
|
|
42
|
+
core.depHooks.resolve.withArgs(sinon.match({ name: Boom.name })).yields(Boom);
|
|
43
|
+
core.depHooks.resolve.withArgs(sinon.match({ name: HapiBoom.name })).yields(HapiBoom);
|
|
44
44
|
|
|
45
45
|
errorHandler = require('./hapi')(core);
|
|
46
46
|
errorHandler.install();
|
|
@@ -30,7 +30,7 @@ module.exports = function (core) {
|
|
|
30
30
|
const koa2ErrorHandler = protect.errorHandlers.koa2ErrorHandler = {};
|
|
31
31
|
|
|
32
32
|
koa2ErrorHandler.install = function () {
|
|
33
|
-
depHooks.resolve({ name: 'koa', version: '>=2.3.0' }, (Koa) => {
|
|
33
|
+
depHooks.resolve({ name: 'koa', version: '>=2.3.0 <3' }, (Koa) => {
|
|
34
34
|
patcher.patch(Koa.prototype, 'handleRequest', {
|
|
35
35
|
name: 'Koa.Application.handleRequest',
|
|
36
36
|
patchType,
|
|
@@ -24,7 +24,7 @@ module.exports = function init(core) {
|
|
|
24
24
|
return protect.errorHandlers.restifyErrorHandler = {
|
|
25
25
|
install() {
|
|
26
26
|
depHooks.resolve(
|
|
27
|
-
{ name: 'restify', file: 'lib/server.js', version: '>=8' },
|
|
27
|
+
{ name: 'restify', file: 'lib/server.js', version: '>=8 <12' },
|
|
28
28
|
(Server) => {
|
|
29
29
|
patcher.patch(Server.prototype, '_onHandlerError', {
|
|
30
30
|
name: 'restify.Server.prototype._onHandlerError',
|
|
@@ -15,7 +15,7 @@ describe('protect error-handlers restify v8+', function () {
|
|
|
15
15
|
store = { protect: { block: sinon.stub(), securityException: ['block'] } };
|
|
16
16
|
|
|
17
17
|
core.depHooks.resolve
|
|
18
|
-
.withArgs({ name: 'restify', file: 'lib/server.js', version: '>=8' })
|
|
18
|
+
.withArgs({ name: 'restify', file: 'lib/server.js', version: '>=8 <12' })
|
|
19
19
|
.yields(Server);
|
|
20
20
|
|
|
21
21
|
core.protect.errorHandlers.restifyErrorHandler.install();
|
|
@@ -32,13 +32,13 @@ module.exports = function(core) {
|
|
|
32
32
|
const method = 'unserialize';
|
|
33
33
|
|
|
34
34
|
depHooks.resolve(
|
|
35
|
-
{ name, version: '<1
|
|
35
|
+
{ name, version: '<1' },
|
|
36
36
|
(nodeSerialize) => {
|
|
37
37
|
patcher.patch(nodeSerialize, method, {
|
|
38
38
|
name,
|
|
39
39
|
patchType,
|
|
40
40
|
pre({ args: [value], hooked, orig }) {
|
|
41
|
-
const sourceContext = protect.getSourceContext(
|
|
41
|
+
const sourceContext = protect.getSourceContext();
|
|
42
42
|
|
|
43
43
|
if (!sourceContext || !value) return;
|
|
44
44
|
|
|
@@ -18,10 +18,7 @@ describe('protect hardening node-serialize0', function () {
|
|
|
18
18
|
core = mocks.core();
|
|
19
19
|
core.logger = mocks.logger();
|
|
20
20
|
core.depHooks = mocks.depHooks();
|
|
21
|
-
core.depHooks
|
|
22
|
-
.resolve
|
|
23
|
-
.withArgs({ name: 'node-serialize', version: '<1.0.0' })
|
|
24
|
-
.yields(mockLib);
|
|
21
|
+
core.depHooks.resolve.yields(mockLib);
|
|
25
22
|
core.patcher = patcher(core);
|
|
26
23
|
core.scopes = scopes(core);
|
|
27
24
|
core.protect = mocks.protect();
|
package/lib/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { ReqData, ProtectMessage, ResultMap, ProtectRuleMode } from '@contrast/c
|
|
|
17
17
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
18
18
|
import * as http from 'node:http';
|
|
19
19
|
import * as https from 'node:https';
|
|
20
|
-
import { Domain } from 'domain';
|
|
20
|
+
import { Domain } from 'async-hook-domain';
|
|
21
21
|
|
|
22
22
|
type Http = typeof http;
|
|
23
23
|
type Https = typeof https;
|
|
@@ -114,7 +114,7 @@ export interface Protect {
|
|
|
114
114
|
}
|
|
115
115
|
errorHandlers: {
|
|
116
116
|
commonHandler: (err: Error) => void;
|
|
117
|
-
initDomain: () =>
|
|
117
|
+
initDomain: () => Domain;
|
|
118
118
|
fastify3ErrorHandler: {
|
|
119
119
|
_userHandler: null | ((...args: any[]) => any),
|
|
120
120
|
defaultErrorHandler: (error: Error, request: IncomingMessage, reply: ServerResponse) => void,
|
|
@@ -122,7 +122,7 @@ export interface Protect {
|
|
|
122
122
|
install: () => void
|
|
123
123
|
}
|
|
124
124
|
koa2ErrorHandler: { install: () => void },
|
|
125
|
-
|
|
125
|
+
expressErrorHandler: { install: () => void },
|
|
126
126
|
install: () => void,
|
|
127
127
|
},
|
|
128
128
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -39,7 +39,7 @@ module.exports = function(core) {
|
|
|
39
39
|
// framework specific instrumentation
|
|
40
40
|
require('./install/fastify')(core);
|
|
41
41
|
require('./install/koa2')(core);
|
|
42
|
-
require('./install/
|
|
42
|
+
require('./install/express')(core);
|
|
43
43
|
require('./install/hapi')(core);
|
|
44
44
|
require('./install/restify')(core);
|
|
45
45
|
|