@contrast/protect 1.29.0 → 1.30.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/LICENSE +1 -1
- package/lib/error-handlers/common-handler.js +1 -1
- package/lib/error-handlers/constants.js +1 -1
- package/lib/error-handlers/index.js +1 -1
- package/lib/error-handlers/init-domain.js +1 -1
- package/lib/error-handlers/install/express4.js +4 -4
- package/lib/error-handlers/install/fastify.js +1 -1
- package/lib/error-handlers/install/hapi.js +3 -3
- package/lib/error-handlers/install/koa2.js +3 -3
- package/lib/get-source-context.js +1 -1
- package/lib/hardening/constants.js +1 -1
- package/lib/hardening/handlers.js +1 -1
- package/lib/hardening/index.js +1 -1
- package/lib/hardening/install/node-serialize0.js +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +61 -10
- package/lib/input-analysis/constants.js +1 -1
- package/lib/input-analysis/handlers.js +14 -14
- package/lib/input-analysis/index.js +1 -1
- package/lib/input-analysis/install/body-parser1.js +1 -1
- package/lib/input-analysis/install/busboy1.js +1 -1
- package/lib/input-analysis/install/cookie-parser1.js +5 -2
- package/lib/input-analysis/install/express4.js +5 -2
- package/lib/input-analysis/install/fastify.js +3 -3
- package/lib/input-analysis/install/formidable1.js +1 -1
- package/lib/input-analysis/install/hapi.js +12 -10
- package/lib/input-analysis/install/http.js +5 -5
- 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 +1 -1
- package/lib/input-analysis/install/multer1.js +9 -3
- package/lib/input-analysis/install/qs6.js +1 -1
- package/lib/input-analysis/install/universal-cookie4.js +1 -1
- package/lib/input-analysis/ip-analysis.js +1 -1
- package/lib/input-analysis/virtual-patches.js +1 -1
- package/lib/input-tracing/constants.js +1 -1
- package/lib/input-tracing/handlers/index.js +1 -1
- package/lib/input-tracing/index.js +1 -1
- package/lib/input-tracing/install/child-process.js +1 -1
- package/lib/input-tracing/install/eval.js +1 -1
- package/lib/input-tracing/install/fs.js +1 -1
- package/lib/input-tracing/install/function.js +1 -1
- package/lib/input-tracing/install/http.js +1 -1
- package/lib/input-tracing/install/marsdb.js +1 -1
- package/lib/input-tracing/install/mongodb.js +1 -1
- package/lib/input-tracing/install/mssql.js +1 -1
- package/lib/input-tracing/install/mysql.js +1 -1
- package/lib/input-tracing/install/postgres.js +1 -1
- package/lib/input-tracing/install/sequelize.js +1 -1
- package/lib/input-tracing/install/sqlite3.js +1 -1
- package/lib/input-tracing/install/vm.js +1 -1
- package/lib/make-response-blocker.js +1 -1
- package/lib/make-source-context.js +1 -1
- package/lib/policy.js +42 -29
- package/lib/security-exception.js +1 -1
- package/lib/semantic-analysis/constants.js +1 -1
- package/lib/semantic-analysis/handlers.js +1 -1
- package/lib/semantic-analysis/index.js +1 -1
- package/lib/semantic-analysis/install/libxmljs.js +3 -3
- package/lib/semantic-analysis/utils/xml-analysis.js +1 -1
- package/lib/throw-security-exception.js +1 -1
- package/package.json +4 -3
- package/lib/esm-loader.mjs +0 -70
package/LICENSE
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -43,7 +43,7 @@ module.exports = function (core) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
if (!sourceContext && isSecurityException) {
|
|
46
|
-
logger.info('source context not found; unable to handle response');
|
|
46
|
+
logger.info({ funcKey: data.funcKey }, 'source context not found; unable to handle response');
|
|
47
47
|
}
|
|
48
48
|
return orig();
|
|
49
49
|
};
|
|
@@ -103,7 +103,7 @@ module.exports = function (core) {
|
|
|
103
103
|
return patcher.patch(fn, {
|
|
104
104
|
name: 'express.route-handler',
|
|
105
105
|
patchType,
|
|
106
|
-
around(orig) {
|
|
106
|
+
around(orig, data) {
|
|
107
107
|
const ret = orig();
|
|
108
108
|
if (ret && ret.catch) {
|
|
109
109
|
return ret.catch((err) => {
|
|
@@ -118,7 +118,7 @@ module.exports = function (core) {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
if (!sourceContext && isSecurityException) {
|
|
121
|
-
logger.info('source context not found; unable to handle response');
|
|
121
|
+
logger.info({ funcKey: data.funcKey }, 'source context not found; unable to handle response');
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -46,13 +46,13 @@ module.exports = function (core) {
|
|
|
46
46
|
err.output.payload = undefined;
|
|
47
47
|
data.result = err;
|
|
48
48
|
|
|
49
|
-
logger.info({ mode, ruleId }, 'Request blocked');
|
|
49
|
+
logger.info({ funcKey: data.funcKey, mode, ruleId }, 'Request blocked');
|
|
50
50
|
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
if (!sourceContext && isSecurityException) {
|
|
55
|
-
logger.info('source context not found; unable to handle response');
|
|
55
|
+
logger.info({ funcKey: data.funcKey }, 'source context not found; unable to handle response');
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -32,7 +32,7 @@ module.exports = function (core) {
|
|
|
32
32
|
koa2ErrorHandler.install = function () {
|
|
33
33
|
depHooks.resolve({ name: 'koa', version: '>=2.3.0' }, (Koa) => {
|
|
34
34
|
patcher.patch(Koa.prototype, 'handleRequest', {
|
|
35
|
-
name: 'Koa.Application',
|
|
35
|
+
name: 'Koa.Application.handleRequest',
|
|
36
36
|
patchType,
|
|
37
37
|
pre(data) {
|
|
38
38
|
const [ctx] = data.args;
|
|
@@ -52,7 +52,7 @@ module.exports = function (core) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
if (!sourceContext && isSecurityException) {
|
|
55
|
-
logger.info('source context not found; unable to handle response');
|
|
55
|
+
logger.info({ funcKey: data.funcKey }, 'source context not found; unable to handle response');
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
return orig();
|
package/lib/hardening/index.js
CHANGED
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -15,12 +15,13 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
const { isMainThread } = require('node:worker_threads');
|
|
19
|
+
|
|
19
20
|
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
20
21
|
|
|
21
22
|
module.exports = function(core) {
|
|
22
23
|
const protect = core.protect = {
|
|
23
|
-
agentLib: module.exports.instantiateAgentLib(
|
|
24
|
+
agentLib: module.exports.instantiateAgentLib(),
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
require('./policy')(core);
|
|
@@ -49,19 +50,69 @@ module.exports = function(core) {
|
|
|
49
50
|
|
|
50
51
|
module.exports.instantiateAgentLib = instantiateAgentLib;
|
|
51
52
|
|
|
53
|
+
// this is just enough agentLib definition for initialization to work.
|
|
54
|
+
// it allows us to avoid instantiating agent-lib in the loader thread
|
|
55
|
+
// until we find the issue with napi-rs/agent-lib-node-bindings.
|
|
56
|
+
const skeletalAgentLib = {
|
|
57
|
+
RuleType: {
|
|
58
|
+
'unsafe-file-upload': 1,
|
|
59
|
+
'path-traversal': 2,
|
|
60
|
+
'reflected-xss': 4,
|
|
61
|
+
'sql-injection': 8,
|
|
62
|
+
'cmd-injection': 16,
|
|
63
|
+
'nosql-injection-mongo': 32,
|
|
64
|
+
'bot-blocker': 64,
|
|
65
|
+
'ssjs-injection': 128,
|
|
66
|
+
'method-tampering': 256,
|
|
67
|
+
'prototype-pollution': 512
|
|
68
|
+
},
|
|
69
|
+
InputType: {
|
|
70
|
+
CookieName: 1,
|
|
71
|
+
CookieValue: 2,
|
|
72
|
+
HeaderKey: 3,
|
|
73
|
+
HeaderValue: 4,
|
|
74
|
+
JsonKey: 5,
|
|
75
|
+
JsonValue: 6,
|
|
76
|
+
Method: 7,
|
|
77
|
+
ParameterKey: 8,
|
|
78
|
+
ParameterValue: 9,
|
|
79
|
+
UriPath: 10,
|
|
80
|
+
UrlParameter: 11,
|
|
81
|
+
MultipartName: 12,
|
|
82
|
+
XmlValue: 13,
|
|
83
|
+
RawBody: 14
|
|
84
|
+
},
|
|
85
|
+
DbType: {
|
|
86
|
+
DB2: 1,
|
|
87
|
+
MySQL: 2,
|
|
88
|
+
Oracle: 3,
|
|
89
|
+
Postgres: 4,
|
|
90
|
+
Sqlite: 5,
|
|
91
|
+
SqlServer: 6,
|
|
92
|
+
Unknown: 7
|
|
93
|
+
},
|
|
94
|
+
version: '8.2.0',
|
|
95
|
+
};
|
|
52
96
|
/**
|
|
53
97
|
* Create an instance of agent-lib and attach the constants directly to it.
|
|
54
98
|
*
|
|
55
99
|
* @param {Object} lib the value returned by require('@contrast/agent-lib')
|
|
56
100
|
* @returns {Object} an agent-lib instance with the constants attached to it.
|
|
57
101
|
*/
|
|
58
|
-
function instantiateAgentLib(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
102
|
+
function instantiateAgentLib() {
|
|
103
|
+
let agentLib;
|
|
104
|
+
if (isMainThread) {
|
|
105
|
+
const lib = require('@contrast/agent-lib');
|
|
106
|
+
agentLib = new lib.Agent();
|
|
107
|
+
for (const c in lib.constants) {
|
|
108
|
+
agentLib[c] = lib.constants[c];
|
|
109
|
+
}
|
|
110
|
+
if ('version' in lib) {
|
|
111
|
+
agentLib.version = lib.version;
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
agentLib = skeletalAgentLib;
|
|
65
115
|
}
|
|
116
|
+
|
|
66
117
|
return agentLib;
|
|
67
118
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -92,7 +92,7 @@ const acceptedMethods = new Set([
|
|
|
92
92
|
'version-control',
|
|
93
93
|
]);
|
|
94
94
|
|
|
95
|
-
module.exports = function(core) {
|
|
95
|
+
module.exports = function (core) {
|
|
96
96
|
const {
|
|
97
97
|
logger,
|
|
98
98
|
protect: {
|
|
@@ -225,7 +225,7 @@ module.exports = function(core) {
|
|
|
225
225
|
* @param {Object} sourceContext
|
|
226
226
|
* @param {Object} urlParams pojo
|
|
227
227
|
*/
|
|
228
|
-
inputAnalysis.handleUrlParams = function(sourceContext, urlParams) {
|
|
228
|
+
inputAnalysis.handleUrlParams = function (sourceContext, urlParams) {
|
|
229
229
|
if (sourceContext.analyzedUrlParams) return;
|
|
230
230
|
sourceContext.analyzedUrlParams = true;
|
|
231
231
|
|
|
@@ -240,7 +240,7 @@ module.exports = function(core) {
|
|
|
240
240
|
const resultsList = [];
|
|
241
241
|
const { UrlParameter } = agentLib.InputType;
|
|
242
242
|
|
|
243
|
-
traverseValues(urlParams, function(path, type, value) {
|
|
243
|
+
traverseValues(urlParams, function (path, type, value) {
|
|
244
244
|
// url param names are not checked.
|
|
245
245
|
if (type !== 'Value') {
|
|
246
246
|
return;
|
|
@@ -292,7 +292,7 @@ module.exports = function(core) {
|
|
|
292
292
|
* @param {Object} sourceContext
|
|
293
293
|
* @param {Object} cookies pojo
|
|
294
294
|
*/
|
|
295
|
-
inputAnalysis.handleCookies = function(sourceContext, cookies) {
|
|
295
|
+
inputAnalysis.handleCookies = function (sourceContext, cookies) {
|
|
296
296
|
if (sourceContext.analyzedCookies) return;
|
|
297
297
|
sourceContext.analyzedCookies = true;
|
|
298
298
|
|
|
@@ -324,7 +324,7 @@ module.exports = function(core) {
|
|
|
324
324
|
* @param {Object} sourceContext
|
|
325
325
|
* @param {Object} parsedBody
|
|
326
326
|
*/
|
|
327
|
-
inputAnalysis.handleParsedBody = function(sourceContext, parsedBody) {
|
|
327
|
+
inputAnalysis.handleParsedBody = function (sourceContext, parsedBody) {
|
|
328
328
|
if (sourceContext.analyzedBody) return;
|
|
329
329
|
|
|
330
330
|
sourceContext.analyzedBody = true;
|
|
@@ -357,7 +357,7 @@ module.exports = function(core) {
|
|
|
357
357
|
|
|
358
358
|
// was MULTIPART_NAME but maybe we should just call it what it is. it's kind
|
|
359
359
|
// of a dumb rule anyway. but maybe some code actually uses the name provided.
|
|
360
|
-
inputAnalysis.handleFileUploadName = function(sourceContext, names) {
|
|
360
|
+
inputAnalysis.handleFileUploadName = function (sourceContext, names) {
|
|
361
361
|
const type = agentLib.InputType.MultipartName;
|
|
362
362
|
const { policy } = sourceContext;
|
|
363
363
|
const resultsList = [];
|
|
@@ -401,7 +401,7 @@ module.exports = function(core) {
|
|
|
401
401
|
}
|
|
402
402
|
};
|
|
403
403
|
|
|
404
|
-
inputAnalysis.handleVirtualPatches = function(sourceContext, requestInput) {
|
|
404
|
+
inputAnalysis.handleVirtualPatches = function (sourceContext, requestInput) {
|
|
405
405
|
const ruleId = Rule.VIRTUAL_PATCH;
|
|
406
406
|
|
|
407
407
|
if (!Object.keys(requestInput).filter(Boolean).length || !sourceContext?.virtualPatchesEvaluators.length) return;
|
|
@@ -430,7 +430,7 @@ module.exports = function(core) {
|
|
|
430
430
|
}
|
|
431
431
|
};
|
|
432
432
|
|
|
433
|
-
inputAnalysis.handleIpAllowlist = function(sourceContext, ipAllowlist) {
|
|
433
|
+
inputAnalysis.handleIpAllowlist = function (sourceContext, ipAllowlist) {
|
|
434
434
|
if (!sourceContext || !ipAllowlist.length) return;
|
|
435
435
|
|
|
436
436
|
const { ip: reqIp, headers: reqHeaders } = sourceContext.reqData;
|
|
@@ -443,7 +443,7 @@ module.exports = function(core) {
|
|
|
443
443
|
}
|
|
444
444
|
};
|
|
445
445
|
|
|
446
|
-
inputAnalysis.handleIpDenylist = function(sourceContext, ipDenylist) {
|
|
446
|
+
inputAnalysis.handleIpDenylist = function (sourceContext, ipDenylist) {
|
|
447
447
|
const ruleId = Rule.IP_DENYLIST;
|
|
448
448
|
|
|
449
449
|
if (!sourceContext || !ipDenylist.length) return;
|
|
@@ -466,7 +466,7 @@ module.exports = function(core) {
|
|
|
466
466
|
}
|
|
467
467
|
};
|
|
468
468
|
|
|
469
|
-
inputAnalysis.handleMethodTampering = function(sourceContext, connectInputs) {
|
|
469
|
+
inputAnalysis.handleMethodTampering = function (sourceContext, connectInputs) {
|
|
470
470
|
const ruleId = Rule.METHOD_TAMPERING;
|
|
471
471
|
const mode = sourceContext.policy[ruleId];
|
|
472
472
|
if (mode !== OFF) {
|
|
@@ -651,7 +651,7 @@ module.exports = function(core) {
|
|
|
651
651
|
// another day.
|
|
652
652
|
|
|
653
653
|
/* eslint-disable-next-line complexity */
|
|
654
|
-
traverseKeysAndValues(object, function(path, type, value) {
|
|
654
|
+
traverseKeysAndValues(object, function (path, type, value) {
|
|
655
655
|
let itemType;
|
|
656
656
|
let isMongoQueryType;
|
|
657
657
|
// this is a bit awkward now because nosql-injection-mongo is not integrated
|
|
@@ -745,13 +745,13 @@ module.exports = function(core) {
|
|
|
745
745
|
|
|
746
746
|
// Ignore bad IP values.
|
|
747
747
|
if (!address.isValid(currentIp)) {
|
|
748
|
-
logger.warn(
|
|
748
|
+
logger.warn('Unable to parse %s.', currentIp);
|
|
749
749
|
continue;
|
|
750
750
|
}
|
|
751
751
|
const expired = doesExpire ? expiresAt - now <= 0 : false;
|
|
752
752
|
|
|
753
753
|
if (expired) {
|
|
754
|
-
logger.info(
|
|
754
|
+
logger.info('IP expired: %s, %s', listEntry.name, listEntry.ip);
|
|
755
755
|
continue;
|
|
756
756
|
}
|
|
757
757
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -54,7 +54,10 @@ module.exports = (core) => {
|
|
|
54
54
|
if (isSecurityException(err)) {
|
|
55
55
|
securityException = err;
|
|
56
56
|
} else {
|
|
57
|
-
logger.error(
|
|
57
|
+
logger.error(
|
|
58
|
+
{ err, funcKey: data.funcKey },
|
|
59
|
+
'Unexpected error during input analysis'
|
|
60
|
+
);
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -64,7 +64,10 @@ module.exports = (core) => {
|
|
|
64
64
|
if (isSecurityException(err)) {
|
|
65
65
|
securityException = err;
|
|
66
66
|
} else {
|
|
67
|
-
logger.error(
|
|
67
|
+
logger.error(
|
|
68
|
+
{ err, funcKey: data.funcKey },
|
|
69
|
+
'Unexpected error during input analysis'
|
|
70
|
+
);
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -39,7 +39,7 @@ module.exports = (core) => {
|
|
|
39
39
|
depHooks.resolve({ name: 'fastify', version: '>=3 <5' }, (fastify) => patcher.patch(fastify, {
|
|
40
40
|
name: 'fastify.build',
|
|
41
41
|
patchType,
|
|
42
|
-
post({ result: server }) {
|
|
42
|
+
post({ result: server, funcKey }) {
|
|
43
43
|
server.addHook('preValidation', function (request, reply, done) {
|
|
44
44
|
let securityException;
|
|
45
45
|
const sourceContext = protect.getSourceContext();
|
|
@@ -67,7 +67,7 @@ module.exports = (core) => {
|
|
|
67
67
|
if (isSecurityException(err)) {
|
|
68
68
|
securityException = err;
|
|
69
69
|
} else {
|
|
70
|
-
logger.error({ err }, 'Unexpected error during input analysis');
|
|
70
|
+
logger.error({ err, funcKey }, 'Unexpected error during input analysis');
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -50,22 +50,24 @@ module.exports = (core) => {
|
|
|
50
50
|
name: 'hapi.server',
|
|
51
51
|
patchType,
|
|
52
52
|
post(data) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
instrumentServer(server);
|
|
53
|
+
if (data.result) {
|
|
54
|
+
instrumentServer(data);
|
|
56
55
|
} else {
|
|
57
|
-
logger.error(
|
|
56
|
+
logger.error(
|
|
57
|
+
{ funcKey: data.funcKey },
|
|
58
|
+
'Hapi Server is called but there is no server instance!'
|
|
59
|
+
);
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
});
|
|
61
63
|
};
|
|
62
64
|
|
|
63
|
-
const instrumentServer = (
|
|
64
|
-
|
|
65
|
-
logger.
|
|
65
|
+
const instrumentServer = ({ result, funcKey }) => {
|
|
66
|
+
result.ext('onPreStart', function onPreStart(core) {
|
|
67
|
+
logger.debug('hapi version %s', core.version);
|
|
66
68
|
});
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
result.ext('onPreHandler', function onPreHandler(req, h) {
|
|
69
71
|
const sourceContext = protect.getSourceContext();
|
|
70
72
|
|
|
71
73
|
if (sourceContext) {
|
|
@@ -93,7 +95,7 @@ module.exports = (core) => {
|
|
|
93
95
|
if (isSecurityException(err)) {
|
|
94
96
|
throw err;
|
|
95
97
|
} else {
|
|
96
|
-
logger.error({ err }, 'Unexpected error during input analysis');
|
|
98
|
+
logger.error({ err, funcKey }, 'Unexpected error during input analysis');
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
const { Event, toLowerCase } = require('@contrast/common');
|
|
19
19
|
const { patchType } = require('../constants');
|
|
20
20
|
|
|
21
|
-
module.exports = function(core) {
|
|
21
|
+
module.exports = function (core) {
|
|
22
22
|
const {
|
|
23
23
|
logger,
|
|
24
24
|
messages,
|
|
@@ -70,7 +70,7 @@ module.exports = function(core) {
|
|
|
70
70
|
try {
|
|
71
71
|
store = sources.getStore();
|
|
72
72
|
if (!store) {
|
|
73
|
-
logger.debug('request store not available during http input-analysis');
|
|
73
|
+
logger.debug({ funcKey: data.funcKey }, 'request store not available during http input-analysis');
|
|
74
74
|
return;
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -112,14 +112,14 @@ module.exports = function(core) {
|
|
|
112
112
|
|
|
113
113
|
block = block || inputAnalysis.handleConnect(store.protect, connectInputs);
|
|
114
114
|
} catch (err) {
|
|
115
|
-
logger.error({ err }, 'Error during http input analysis');
|
|
115
|
+
logger.error({ err, funcKey: data.funcKey }, 'Error during http input analysis');
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
if (!block) {
|
|
119
119
|
callNext();
|
|
120
120
|
} else {
|
|
121
121
|
store.protect.block(...block);
|
|
122
|
-
logger.debug({ block }, 'request blocked by not emitting request event');
|
|
122
|
+
logger.debug({ block, funcKey: data.funcKey }, 'request blocked by not emitting request event');
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -59,7 +59,10 @@ module.exports = (core) => {
|
|
|
59
59
|
if (isSecurityException(err)) {
|
|
60
60
|
securityException = err;
|
|
61
61
|
} else {
|
|
62
|
-
logger.error(
|
|
62
|
+
logger.error(
|
|
63
|
+
{ err, funcKey: data.funcKey },
|
|
64
|
+
'Unexpected error during input analysis',
|
|
65
|
+
);
|
|
63
66
|
}
|
|
64
67
|
}
|
|
65
68
|
}
|
|
@@ -81,7 +84,10 @@ module.exports = (core) => {
|
|
|
81
84
|
if (isSecurityException(err)) {
|
|
82
85
|
securityException = err;
|
|
83
86
|
} else {
|
|
84
|
-
logger.error(
|
|
87
|
+
logger.error(
|
|
88
|
+
{ err, funcKey: data.funcKey },
|
|
89
|
+
'Unexpected error during input analysis',
|
|
90
|
+
);
|
|
85
91
|
}
|
|
86
92
|
}
|
|
87
93
|
}
|
package/lib/policy.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -17,33 +17,34 @@
|
|
|
17
17
|
|
|
18
18
|
const {
|
|
19
19
|
Rule,
|
|
20
|
-
ProtectRuleMode
|
|
21
|
-
BLOCK_AT_PERIMETER,
|
|
22
|
-
OFF,
|
|
23
|
-
},
|
|
24
|
-
Rule: {
|
|
25
|
-
BOT_BLOCKER,
|
|
26
|
-
CMD_INJECTION,
|
|
27
|
-
CMD_INJECTION_COMMAND_BACKDOORS,
|
|
28
|
-
CMD_INJECTION_SEMANTIC_CHAINED_COMMANDS,
|
|
29
|
-
CMD_INJECTION_SEMANTIC_DANGEROUS_PATHS,
|
|
30
|
-
METHOD_TAMPERING,
|
|
31
|
-
NOSQL_INJECTION,
|
|
32
|
-
NOSQL_INJECTION_MONGO,
|
|
33
|
-
PATH_TRAVERSAL,
|
|
34
|
-
PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS,
|
|
35
|
-
REFLECTED_XSS,
|
|
36
|
-
SQL_INJECTION,
|
|
37
|
-
SSJS_INJECTION,
|
|
38
|
-
UNSAFE_FILE_UPLOAD,
|
|
39
|
-
UNTRUSTED_DESERIALIZATION,
|
|
40
|
-
XXE,
|
|
41
|
-
},
|
|
20
|
+
ProtectRuleMode,
|
|
42
21
|
Event,
|
|
43
22
|
toLowerCase,
|
|
44
23
|
split,
|
|
45
24
|
join
|
|
46
25
|
} = require('@contrast/common');
|
|
26
|
+
const { ConfigSource } = require('@contrast/config');
|
|
27
|
+
|
|
28
|
+
const { BLOCK_AT_PERIMETER, OFF } = ProtectRuleMode;
|
|
29
|
+
const {
|
|
30
|
+
BOT_BLOCKER,
|
|
31
|
+
CMD_INJECTION,
|
|
32
|
+
CMD_INJECTION_COMMAND_BACKDOORS,
|
|
33
|
+
CMD_INJECTION_SEMANTIC_CHAINED_COMMANDS,
|
|
34
|
+
CMD_INJECTION_SEMANTIC_DANGEROUS_PATHS,
|
|
35
|
+
METHOD_TAMPERING,
|
|
36
|
+
NOSQL_INJECTION,
|
|
37
|
+
NOSQL_INJECTION_MONGO,
|
|
38
|
+
PATH_TRAVERSAL,
|
|
39
|
+
PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS,
|
|
40
|
+
REFLECTED_XSS,
|
|
41
|
+
SQL_INJECTION,
|
|
42
|
+
SSJS_INJECTION,
|
|
43
|
+
UNSAFE_FILE_UPLOAD,
|
|
44
|
+
UNTRUSTED_DESERIALIZATION,
|
|
45
|
+
XXE,
|
|
46
|
+
} = Rule;
|
|
47
|
+
const { DEFAULT_VALUE } = ConfigSource;
|
|
47
48
|
|
|
48
49
|
module.exports = function (core) {
|
|
49
50
|
const {
|
|
@@ -116,8 +117,14 @@ module.exports = function (core) {
|
|
|
116
117
|
if (config.protect.rules.disabled_rules.includes(ruleId)) {
|
|
117
118
|
return OFF;
|
|
118
119
|
}
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
// If nosql-injection is set in config but nosql-injection-mongo isn't,
|
|
121
|
+
// then have nosql-injection-mongo inherit config value from umbrella rule.
|
|
122
|
+
if (
|
|
123
|
+
ruleId === 'nosql-injection-mongo' &&
|
|
124
|
+
config.getEffectiveSource(`protect.rules.${ruleId}.mode`) === DEFAULT_VALUE &&
|
|
125
|
+
config.getEffectiveSource(`protect.rules.${NOSQL_INJECTION}.mode`) !== DEFAULT_VALUE
|
|
126
|
+
) {
|
|
127
|
+
return config.protect.rules?.[NOSQL_INJECTION]?.mode;
|
|
121
128
|
}
|
|
122
129
|
return config.protect.rules?.[ruleId]?.mode;
|
|
123
130
|
}
|
|
@@ -235,8 +242,6 @@ module.exports = function (core) {
|
|
|
235
242
|
}
|
|
236
243
|
|
|
237
244
|
function updateGlobalPolicy(remoteSettings) {
|
|
238
|
-
let update;
|
|
239
|
-
|
|
240
245
|
const protectionRules = remoteSettings?.protect?.rules;
|
|
241
246
|
if (protectionRules) {
|
|
242
247
|
[
|
|
@@ -246,7 +251,6 @@ module.exports = function (core) {
|
|
|
246
251
|
CMD_INJECTION_SEMANTIC_DANGEROUS_PATHS,
|
|
247
252
|
METHOD_TAMPERING,
|
|
248
253
|
NOSQL_INJECTION,
|
|
249
|
-
NOSQL_INJECTION_MONGO,
|
|
250
254
|
PATH_TRAVERSAL,
|
|
251
255
|
PATH_TRAVERSAL_SEMANTIC_FILE_SECURITY_BYPASS,
|
|
252
256
|
REFLECTED_XSS,
|
|
@@ -260,6 +264,15 @@ module.exports = function (core) {
|
|
|
260
264
|
policy[ruleId] = OFF;
|
|
261
265
|
} else {
|
|
262
266
|
policy[ruleId] = config.getEffectiveValue(`protect.rules.${ruleId}.mode`);
|
|
267
|
+
|
|
268
|
+
// nosql-injection-mongo should inherit nosql-injection mode if unset in config
|
|
269
|
+
if (ruleId === NOSQL_INJECTION) {
|
|
270
|
+
if (config.getEffectiveSource(`protect.rules.${NOSQL_INJECTION_MONGO}.mode`) === DEFAULT_VALUE) {
|
|
271
|
+
policy[NOSQL_INJECTION_MONGO] = policy[ruleId];
|
|
272
|
+
} else {
|
|
273
|
+
policy[NOSQL_INJECTION_MONGO] = config.getEffectiveValue(`protect.rules.${NOSQL_INJECTION_MONGO}.mode`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
263
276
|
}
|
|
264
277
|
});
|
|
265
278
|
|
|
@@ -275,7 +288,7 @@ module.exports = function (core) {
|
|
|
275
288
|
|
|
276
289
|
updateRulesMask();
|
|
277
290
|
protect.policy.exclusions = compiled;
|
|
278
|
-
logger.info({ policy: protect.policy },
|
|
291
|
+
logger.info({ policy: protect.policy }, 'Protect policy updated');
|
|
279
292
|
}
|
|
280
293
|
}
|
|
281
294
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright:
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
3
|
* Contact: support@contrastsecurity.com
|
|
4
4
|
* License: Commercial
|
|
5
5
|
|
|
@@ -44,7 +44,7 @@ module.exports = function (core) {
|
|
|
44
44
|
patcher.patch(mod, method, {
|
|
45
45
|
name,
|
|
46
46
|
patchType,
|
|
47
|
-
pre({ args, hooked, orig }) {
|
|
47
|
+
pre({ args, hooked, orig, funcKey }) {
|
|
48
48
|
const sourceContext = protect.getSourceContext(name);
|
|
49
49
|
const [value, options] = args;
|
|
50
50
|
|
|
@@ -68,7 +68,7 @@ module.exports = function (core) {
|
|
|
68
68
|
throw err;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
logger.error({ err }, 'Unexpected error during semantic analysis');
|
|
71
|
+
logger.error({ err, funcKey }, 'Unexpected error during semantic analysis');
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/protect",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.30.0",
|
|
4
4
|
"description": "Contrast service providing framework-agnostic Protect support",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@contrast/agent-lib": "^7.0.1",
|
|
21
21
|
"@contrast/common": "1.16.0",
|
|
22
|
-
"@contrast/
|
|
23
|
-
"@contrast/
|
|
22
|
+
"@contrast/config": "1.23.0",
|
|
23
|
+
"@contrast/core": "1.27.1",
|
|
24
|
+
"@contrast/esm-hooks": "2.0.1",
|
|
24
25
|
"@contrast/scopes": "1.4.0",
|
|
25
26
|
"ipaddr.js": "^2.0.1",
|
|
26
27
|
"semver": "^7.3.7"
|
package/lib/esm-loader.mjs
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright: 2023 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
|
-
import esmHooks from '@contrast/esm-hooks';
|
|
17
|
-
import { createRequire } from 'node:module';
|
|
18
|
-
|
|
19
|
-
const ERROR_MESSAGE = 'A fatal agent installation error has occurred. The application will be run without instrumentation.';
|
|
20
|
-
|
|
21
|
-
let getSource = async function getSource(url, context, defaultGetSource) {
|
|
22
|
-
return defaultGetSource(url, context, defaultGetSource);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
let transformSource = async function transformSource(url, context, defaultTransformSource) {
|
|
26
|
-
return defaultTransformSource(url, context, defaultTransformSource);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
let load = async function load(url, context, defaultLoad) {
|
|
30
|
-
return defaultLoad(url, context, defaultLoad);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const require = createRequire(import.meta.url);
|
|
34
|
-
const { name: agentName, version: agentVersion } = require('../package.json');
|
|
35
|
-
const core = { agentName, agentVersion };
|
|
36
|
-
try {
|
|
37
|
-
require('@contrast/core/lib/messages')(core);
|
|
38
|
-
require('@contrast/config')(core);
|
|
39
|
-
require('@contrast/logger').default(core);
|
|
40
|
-
|
|
41
|
-
// @contrast/info ?
|
|
42
|
-
require('@contrast/core/lib/agent-info')(core);
|
|
43
|
-
require('@contrast/core/lib/system-info')(core);
|
|
44
|
-
require('@contrast/core/lib/app-info')(core);
|
|
45
|
-
require('@contrast/core/lib/sensitive-data-masking')(core);
|
|
46
|
-
require('@contrast/core/lib/is-agent-path')(core);
|
|
47
|
-
require('@contrast/core/lib/capture-stacktrace')(core);
|
|
48
|
-
|
|
49
|
-
require('@contrast/patcher')(core);
|
|
50
|
-
require('@contrast/rewriter')(core); // merge contrast-methods?
|
|
51
|
-
require('@contrast/core/lib/contrast-methods')(core); // can we remove dependency on patcher?
|
|
52
|
-
|
|
53
|
-
require('@contrast/dep-hooks')(core);
|
|
54
|
-
require('@contrast/scopes')(core);
|
|
55
|
-
require('@contrast/deadzones')(core);
|
|
56
|
-
require('@contrast/reporter').default(core);
|
|
57
|
-
require('@contrast/instrumentation')(core);
|
|
58
|
-
|
|
59
|
-
// TODO: i think we still need to INSTALL components.
|
|
60
|
-
({ getSource, transformSource, load } = esmHooks(core));
|
|
61
|
-
} catch (err) {
|
|
62
|
-
// TODO: Consider proper UNINSTALLATION and normal startup w/o agent
|
|
63
|
-
if (core.logger) {
|
|
64
|
-
core.logger.error({ err }, ERROR_MESSAGE);
|
|
65
|
-
} else {
|
|
66
|
-
console.error(new Error(ERROR_MESSAGE, { cause: err }));
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export { getSource, transformSource, load };
|