@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.
- package/lib/error-handlers/index.js +2 -0
- package/lib/error-handlers/install/koa2.js +53 -0
- package/lib/input-analysis/index.js +2 -0
- package/lib/input-analysis/install/co-body.js +51 -0
- package/lib/input-analysis/install/cookie-parser.js +48 -0
- package/lib/input-analysis/install/formidable.js +53 -0
- package/lib/input-analysis/install/koa2.js +137 -0
- package/lib/input-analysis/install/multer.js +52 -0
- package/lib/input-analysis/install/qs.js +40 -0
- package/lib/input-analysis/install/universal-cookie.js +34 -0
- package/package.json +4 -3
- package/lib/error-handlers/install/fastify3.test.js +0 -142
- package/lib/esm-loader.test.mjs +0 -11
- package/lib/index.test.js +0 -32
- package/lib/input-analysis/handlers.test.js +0 -898
- package/lib/input-analysis/index.test.js +0 -28
- package/lib/input-analysis/install/fastify3.test.js +0 -71
- package/lib/input-analysis/install/http.test.js +0 -315
- package/lib/input-tracing/handlers/index.test.js +0 -395
- package/lib/input-tracing/install/child-process.test.js +0 -112
- package/lib/input-tracing/install/fs.test.js +0 -118
- package/lib/input-tracing/install/mysql.test.js +0 -108
- package/lib/input-tracing/install/postgres.test.js +0 -125
- package/lib/input-tracing/install/sequelize.test.js +0 -79
- package/lib/input-tracing/install/sqlite3.test.js +0 -88
- package/lib/make-response-blocker.test.js +0 -88
- package/lib/make-source-context.test.js +0 -298
- package/lib/throw-security-exception.test.js +0 -50
- package/lib/utils.test.js +0 -40
|
@@ -4,9 +4,11 @@ module.exports = function(core) {
|
|
|
4
4
|
const errorHandlers = core.protect.errorHandlers = {};
|
|
5
5
|
|
|
6
6
|
require('./install/fastify3')(core);
|
|
7
|
+
require('./install/koa2')(core);
|
|
7
8
|
|
|
8
9
|
errorHandlers.install = function() {
|
|
9
10
|
errorHandlers.fastify3ErrorHandler.install();
|
|
11
|
+
errorHandlers.koa2ErrorHandler.install();
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
return errorHandlers;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const SecurityException = require('../../security-exception');
|
|
4
|
+
const { patchType } = require('../constants');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
module.exports = function(core) {
|
|
8
|
+
const {
|
|
9
|
+
logger,
|
|
10
|
+
depHooks,
|
|
11
|
+
patcher,
|
|
12
|
+
scopes: { sources },
|
|
13
|
+
protect,
|
|
14
|
+
} = core;
|
|
15
|
+
|
|
16
|
+
const koa2ErrorHandler = protect.errorHandlers.koa2ErrorHandler = {};
|
|
17
|
+
|
|
18
|
+
koa2ErrorHandler.install = function () {
|
|
19
|
+
depHooks.resolve({ name: 'koa', version: '>=2.3.0' }, (Koa) => {
|
|
20
|
+
patcher.patch(Koa.prototype, 'handleRequest', {
|
|
21
|
+
name: 'Koa.Application',
|
|
22
|
+
patchType,
|
|
23
|
+
pre(data) {
|
|
24
|
+
const [ctx] = data.args;
|
|
25
|
+
patcher.patch(ctx, 'onerror', {
|
|
26
|
+
name: 'koa.ctx.onerror',
|
|
27
|
+
patchType,
|
|
28
|
+
around(org, data) {
|
|
29
|
+
const [err] = data.args;
|
|
30
|
+
const sourceContext = sources.getStore()?.protect;
|
|
31
|
+
const isSecurityException = SecurityException.isSecurityException(err);
|
|
32
|
+
|
|
33
|
+
if (isSecurityException && sourceContext) {
|
|
34
|
+
data.obj.body = '';
|
|
35
|
+
const blockInfo = sourceContext.findings.securityException;
|
|
36
|
+
sourceContext.block(...blockInfo);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!sourceContext && isSecurityException) {
|
|
41
|
+
logger.info('source context not found; unable to handle response');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
org();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return koa2ErrorHandler;
|
|
53
|
+
};
|
|
@@ -6,10 +6,12 @@ module.exports = function(core) {
|
|
|
6
6
|
require('./handlers')(core);
|
|
7
7
|
require('./install/http')(core);
|
|
8
8
|
require('./install/fastify3')(core);
|
|
9
|
+
require('./install/koa2')(core);
|
|
9
10
|
|
|
10
11
|
inputAnalysis.install = function() {
|
|
11
12
|
inputAnalysis.httpInstrumentation.install();
|
|
12
13
|
inputAnalysis.fastifyInstrumentation.install();
|
|
14
|
+
inputAnalysis.koaInstrumentation.install();
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
return inputAnalysis;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = (core) => {
|
|
4
|
+
const {
|
|
5
|
+
depHooks,
|
|
6
|
+
patcher,
|
|
7
|
+
logger,
|
|
8
|
+
scopes: { sources },
|
|
9
|
+
protect: { inputAnalysis },
|
|
10
|
+
} = core;
|
|
11
|
+
|
|
12
|
+
async function postHook(data) {
|
|
13
|
+
const { args: [, opts], result } = data;
|
|
14
|
+
if (result) {
|
|
15
|
+
const sourceContext = sources.getStore()?.protect;
|
|
16
|
+
if (!sourceContext) {
|
|
17
|
+
logger.debug('source context not available in `co-body` hook');
|
|
18
|
+
} else {
|
|
19
|
+
result.then((resolved) => {
|
|
20
|
+
const parsedBody = opts?.returnRawBody ? resolved.parsed : resolved;
|
|
21
|
+
sourceContext.parsedBody = parsedBody;
|
|
22
|
+
inputAnalysis.handleParsedBody(sourceContext, parsedBody);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
// Patch lower level parser - `co-body` used by `koa-body` and `koa-bodyparser`
|
|
30
|
+
install() {
|
|
31
|
+
depHooks.resolve({ name: 'co-body' }, (coBody) => {
|
|
32
|
+
coBody = patcher.patch(coBody, {
|
|
33
|
+
name: 'co-body',
|
|
34
|
+
patchType: 'framework-patch',
|
|
35
|
+
post: postHook
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
['json', 'form', 'text'].forEach((property) => {
|
|
39
|
+
patcher.patch(coBody, property, {
|
|
40
|
+
name: `co-body.${property}`,
|
|
41
|
+
patchType: 'framework-patch',
|
|
42
|
+
post: postHook
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return coBody;
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = (core) => {
|
|
4
|
+
const {
|
|
5
|
+
depHooks,
|
|
6
|
+
patcher,
|
|
7
|
+
logger,
|
|
8
|
+
scopes: { sources },
|
|
9
|
+
protect: { inputAnalysis },
|
|
10
|
+
} = core;
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
// Patch `cookie-parser` package
|
|
14
|
+
install() {
|
|
15
|
+
depHooks.resolve({ name: 'cookie-parser' }, (cookieParser) => patcher.patch(cookieParser, {
|
|
16
|
+
name: 'cookie-parser',
|
|
17
|
+
patchType: 'framework-patch',
|
|
18
|
+
post(data) {
|
|
19
|
+
data.result = patcher.patch(data.result, {
|
|
20
|
+
name: 'cookie-parser',
|
|
21
|
+
patchType: 'framework-patch',
|
|
22
|
+
pre(data) {
|
|
23
|
+
const [req, , origNext] = data.args;
|
|
24
|
+
|
|
25
|
+
async function contrastNext() {
|
|
26
|
+
const sourceContext = sources.getStore()?.protect;
|
|
27
|
+
|
|
28
|
+
if (!sourceContext) {
|
|
29
|
+
logger.debug('source context not available in `cookie-parser` hook');
|
|
30
|
+
} else {
|
|
31
|
+
if (req.cookies) {
|
|
32
|
+
sourceContext.parsedCookies = req.cookies;
|
|
33
|
+
inputAnalysis.handleCookies(sourceContext, req.cookies);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
await origNext();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
data.args[2] = contrastNext;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = (core) => {
|
|
4
|
+
const {
|
|
5
|
+
depHooks,
|
|
6
|
+
patcher,
|
|
7
|
+
logger,
|
|
8
|
+
scopes: { sources },
|
|
9
|
+
protect: { inputAnalysis },
|
|
10
|
+
} = core;
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
// Patch `formidable`
|
|
14
|
+
install() {
|
|
15
|
+
depHooks.resolve({ name: 'formidable' }, (formidable) => {
|
|
16
|
+
formidable.IncomingForm.prototype.parse = patcher.patch(formidable.IncomingForm.prototype.parse, {
|
|
17
|
+
name: 'Formidable.IncomingForm.prototype.parse',
|
|
18
|
+
patchType: 'framework-patch',
|
|
19
|
+
pre(data) {
|
|
20
|
+
const origCb = data.args[1];
|
|
21
|
+
|
|
22
|
+
function hookedCb(...cbArgs) {
|
|
23
|
+
const sourceContext = sources.getStore()?.protect;
|
|
24
|
+
const [, fields, files] = cbArgs;
|
|
25
|
+
|
|
26
|
+
if (!sourceContext) {
|
|
27
|
+
logger.debug('source context not available in `formidable` hook');
|
|
28
|
+
} else {
|
|
29
|
+
if (fields) {
|
|
30
|
+
sourceContext.parsedBody = fields;
|
|
31
|
+
inputAnalysis.handleParsedBody(sourceContext, fields);
|
|
32
|
+
}
|
|
33
|
+
if (files) {
|
|
34
|
+
logger.debug('Check for vulnerable filename upload nyi');
|
|
35
|
+
// CHECK FILENAME - NYI
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (origCb && typeof origCb === 'function') {
|
|
40
|
+
// Should we explicitly run in the current source context?
|
|
41
|
+
origCb.apply(this, cbArgs);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
data.args[1] = hookedCb;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return formidable;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Function that exports an install method to patch Fastify framework with our instrumentation
|
|
5
|
+
* @param {Object} core - the core Contrast object in v5
|
|
6
|
+
* @return {Object} object with install method and the other relative functions exported for testing purposes
|
|
7
|
+
*/
|
|
8
|
+
module.exports = (core) => {
|
|
9
|
+
const {
|
|
10
|
+
depHooks,
|
|
11
|
+
patcher,
|
|
12
|
+
logger,
|
|
13
|
+
scopes: { sources },
|
|
14
|
+
protect: { inputAnalysis },
|
|
15
|
+
} = core;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* registers a depHook for koa module instrumentation
|
|
19
|
+
*/
|
|
20
|
+
function install() {
|
|
21
|
+
depHooks.resolve({ name: 'koa', version: '>=2.3.0' }, (Koa) => {
|
|
22
|
+
const coBodyPatch = require('./co-body.js')(core);
|
|
23
|
+
const multerPatch = require('./multer')(core);
|
|
24
|
+
const formidablePatch = require('./formidable')(core);
|
|
25
|
+
const qsPatch = require('./qs')(core);
|
|
26
|
+
const cookieParserPatch = require('./cookie-parser')(core);
|
|
27
|
+
const universalCookiePatch = require('./universal-cookie')(core);
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
function contrastStartMiddleware(ctx, next) {
|
|
31
|
+
if (ctx.query && Object.keys(ctx.query).length) {
|
|
32
|
+
const sourceContext = sources.getStore()?.protect;
|
|
33
|
+
|
|
34
|
+
if (!sourceContext) {
|
|
35
|
+
logger.debug('source context not available in `qs` hook');
|
|
36
|
+
} else if (!('parsedQuery' in sourceContext)) {
|
|
37
|
+
sourceContext.parsedQuery = ctx.query;
|
|
38
|
+
inputAnalysis.handleQueryParams(sourceContext, ctx.query);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
return next();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// mark these middleware as ours
|
|
46
|
+
contrastStartMiddleware._isContrastStartMiddleware = true;
|
|
47
|
+
|
|
48
|
+
patcher.patch(Koa.prototype, 'use', {
|
|
49
|
+
name: 'Koa.Application',
|
|
50
|
+
patchType: 'framework-patch',
|
|
51
|
+
pre({ obj: app }) {
|
|
52
|
+
// if not already inserted, insert the initial middleware.
|
|
53
|
+
if (
|
|
54
|
+
app.middleware &&
|
|
55
|
+
(!app.middleware[0] || !app.middleware[0]._isContrastStartMiddleware)
|
|
56
|
+
) {
|
|
57
|
+
app.middleware.splice(0, 0, contrastStartMiddleware);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Patch `koa-router` and `@koa/router` to handle parsed params
|
|
63
|
+
['koa-router', '@koa/router'].forEach(router => {
|
|
64
|
+
depHooks.resolve(
|
|
65
|
+
{ name: router, file: 'lib/layer.js' },
|
|
66
|
+
(layer) => {
|
|
67
|
+
layer.prototype = patcher.patch(layer.prototype, 'params', {
|
|
68
|
+
name: `[${router}].layer.prototype`,
|
|
69
|
+
patchType: 'framework-patch',
|
|
70
|
+
post({ result }) {
|
|
71
|
+
const sourceContext = sources.getStore()?.protect;
|
|
72
|
+
|
|
73
|
+
if (!sourceContext) {
|
|
74
|
+
logger.debug(`source context not available in \`[${router}].layer\` hook`);
|
|
75
|
+
} else {
|
|
76
|
+
if (Object.keys(result).length) {
|
|
77
|
+
sourceContext.parsedParams = result;
|
|
78
|
+
inputAnalysis.handleUrlParams(sourceContext, result);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return layer;
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Patch `koa-cookie`
|
|
89
|
+
depHooks.resolve({ name: 'koa-cookie' }, (koaCookie) => {
|
|
90
|
+
const { default: cookieParser } = koaCookie;
|
|
91
|
+
koaCookie.default = patcher.patch(cookieParser, {
|
|
92
|
+
name: 'koa-cookie',
|
|
93
|
+
patchType: 'framework-patch',
|
|
94
|
+
post(data) {
|
|
95
|
+
data.result = patcher.patch(data.result, {
|
|
96
|
+
name: 'koa-cookie',
|
|
97
|
+
patchType: 'framework-patch',
|
|
98
|
+
pre(data) {
|
|
99
|
+
const [ctx, origNext] = data.args;
|
|
100
|
+
|
|
101
|
+
async function contrastNext() {
|
|
102
|
+
const sourceContext = sources.getStore()?.protect;
|
|
103
|
+
|
|
104
|
+
if (!sourceContext) {
|
|
105
|
+
logger.debug('source context not available in `koa-cookie` hook');
|
|
106
|
+
} else {
|
|
107
|
+
if (ctx.cookie) {
|
|
108
|
+
sourceContext.parsedCookies = ctx.cookie;
|
|
109
|
+
inputAnalysis.handleCookies(sourceContext, ctx.cookie);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await origNext();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
data.args[1] = contrastNext;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
coBodyPatch.install();
|
|
124
|
+
multerPatch.install();
|
|
125
|
+
formidablePatch.install();
|
|
126
|
+
qsPatch.install();
|
|
127
|
+
cookieParserPatch.install();
|
|
128
|
+
universalCookiePatch.install();
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const koaInstrumentation = inputAnalysis.koaInstrumentation = {
|
|
133
|
+
install
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return koaInstrumentation;
|
|
137
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = (core) => {
|
|
4
|
+
const {
|
|
5
|
+
depHooks,
|
|
6
|
+
patcher,
|
|
7
|
+
logger,
|
|
8
|
+
scopes: { sources },
|
|
9
|
+
protect: { inputAnalysis },
|
|
10
|
+
} = core;
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
// Patch `multer`
|
|
14
|
+
install() {
|
|
15
|
+
depHooks.resolve({ name: 'multer', file: 'lib/make-middleware.js' }, (multerMakeMiddleware) => patcher.patch(multerMakeMiddleware, {
|
|
16
|
+
name: 'multer.make-middleware',
|
|
17
|
+
patchType: 'framework-patch',
|
|
18
|
+
post(data) {
|
|
19
|
+
data.result = patcher.patch(data.result, {
|
|
20
|
+
name: 'multerMiddleware',
|
|
21
|
+
patchType: 'framework-patch',
|
|
22
|
+
pre(data) {
|
|
23
|
+
const [req, , origNext] = data.args;
|
|
24
|
+
|
|
25
|
+
async function contrastNext() {
|
|
26
|
+
|
|
27
|
+
const sourceContext = sources.getStore()?.protect;
|
|
28
|
+
|
|
29
|
+
if (!sourceContext) {
|
|
30
|
+
logger.debug('source context not available in `multer` hook');
|
|
31
|
+
} else {
|
|
32
|
+
if (req.body) {
|
|
33
|
+
sourceContext.parsedBody = req.body;
|
|
34
|
+
inputAnalysis.handleParsedBody(sourceContext, req.body);
|
|
35
|
+
}
|
|
36
|
+
if (req.file || req.files) {
|
|
37
|
+
logger.debug('Check for vulnerable filename upload nyi');
|
|
38
|
+
// CHECK FILENAME - NYI
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await origNext();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
data.args[2] = contrastNext;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = (core) => {
|
|
4
|
+
const {
|
|
5
|
+
depHooks,
|
|
6
|
+
patcher,
|
|
7
|
+
logger,
|
|
8
|
+
scopes: { sources },
|
|
9
|
+
protect: { inputAnalysis },
|
|
10
|
+
} = core;
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
// Patch `qs`
|
|
14
|
+
install() {
|
|
15
|
+
depHooks.resolve({ name: 'qs' },
|
|
16
|
+
(qs) => patcher.patch(qs, 'parse', {
|
|
17
|
+
name: 'qs',
|
|
18
|
+
patchType: 'framework-patch',
|
|
19
|
+
post({ args, result }) {
|
|
20
|
+
if (result && Object.keys(result).length) {
|
|
21
|
+
const sourceContext = sources.getStore()?.protect;
|
|
22
|
+
|
|
23
|
+
if (!sourceContext) {
|
|
24
|
+
logger.debug('source context not available in `qs` hook');
|
|
25
|
+
|
|
26
|
+
// We need to run analysis for the `qs` result only when it's used as a query parser.
|
|
27
|
+
// `qs` is used also for parsing bodies, but these cases we handle individually with
|
|
28
|
+
// the respective library that's using it (e.g. `formidable`, `co-body`) because in
|
|
29
|
+
// some cases its use is optional and we cannot rely on it.
|
|
30
|
+
} else if (sourceContext.reqData?.queries === args[0]) {
|
|
31
|
+
sourceContext.parsedQuery = result;
|
|
32
|
+
inputAnalysis.handleQueryParams(sourceContext, result);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = (core) => {
|
|
4
|
+
const {
|
|
5
|
+
depHooks,
|
|
6
|
+
patcher,
|
|
7
|
+
logger,
|
|
8
|
+
scopes: { sources },
|
|
9
|
+
protect: { inputAnalysis },
|
|
10
|
+
} = core;
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
// Patch `universal-cookie` package
|
|
14
|
+
install() {
|
|
15
|
+
depHooks.resolve({ name: 'universal-cookie', file: 'cjs/utils.js' }, (uCookieUtils) => patcher.patch(uCookieUtils, 'parseCookies', {
|
|
16
|
+
name: 'universal-cookie.utils',
|
|
17
|
+
patchType: 'framework-patch',
|
|
18
|
+
post({ result }) {
|
|
19
|
+
if (result && Object.keys(result).length) {
|
|
20
|
+
const sourceContext = sources.getStore()?.protect;
|
|
21
|
+
|
|
22
|
+
if (!sourceContext) {
|
|
23
|
+
logger.debug('source context not available in `universal-cookie` hook');
|
|
24
|
+
} else {
|
|
25
|
+
sourceContext.parsedCookies = result;
|
|
26
|
+
inputAnalysis.handleCookies(sourceContext, result);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/protect",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.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)",
|
|
@@ -24,8 +24,9 @@
|
|
|
24
24
|
"@babel/types": "^7.16.8",
|
|
25
25
|
"@contrast/agent-lib": "^4.2.0",
|
|
26
26
|
"@contrast/common": "1.0.0",
|
|
27
|
-
"@contrast/core": "1.
|
|
28
|
-
"@contrast/
|
|
27
|
+
"@contrast/core": "1.1.0",
|
|
28
|
+
"@contrast/scopes": "1.0.0",
|
|
29
|
+
"@contrast/esm-hooks": "1.1.0",
|
|
29
30
|
"async-hook-domain": "^2.0.4",
|
|
30
31
|
"builtin-modules": "^3.2.0"
|
|
31
32
|
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const sinon = require('sinon');
|
|
4
|
-
const { expect } = require('chai');
|
|
5
|
-
const securityException = require('../../security-exception');
|
|
6
|
-
const mocks = require('../../../../test/mocks');
|
|
7
|
-
|
|
8
|
-
describe('protect error-handlers: fastify3', function() {
|
|
9
|
-
let core;
|
|
10
|
-
let errorHandler;
|
|
11
|
-
let fastify;
|
|
12
|
-
let server;
|
|
13
|
-
let reply;
|
|
14
|
-
|
|
15
|
-
beforeEach(function() {
|
|
16
|
-
core = mocks.core();
|
|
17
|
-
core.config = mocks.config();
|
|
18
|
-
core.logger = mocks.logger();
|
|
19
|
-
core.scopes = mocks.scopes();
|
|
20
|
-
core.protect = mocks.protect();
|
|
21
|
-
core.depHooks = mocks.depHooks();
|
|
22
|
-
core.patcher = require('@contrast/patcher')(core);
|
|
23
|
-
|
|
24
|
-
server = {
|
|
25
|
-
setErrorHandler: sinon.stub(),
|
|
26
|
-
errorHandler: sinon.stub()
|
|
27
|
-
};
|
|
28
|
-
fastify = function() {
|
|
29
|
-
return server;
|
|
30
|
-
};
|
|
31
|
-
core.depHooks.resolve.callsFake((desc, cb) => {
|
|
32
|
-
fastify = cb(fastify);
|
|
33
|
-
});
|
|
34
|
-
reply = {
|
|
35
|
-
log: {
|
|
36
|
-
info: sinon.stub(),
|
|
37
|
-
error: sinon.stub()
|
|
38
|
-
},
|
|
39
|
-
send: sinon.stub(),
|
|
40
|
-
statusCode: 500,
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
errorHandler = require('./fastify3')(core);
|
|
44
|
-
sinon.spy(errorHandler, 'defaultErrorHandler');
|
|
45
|
-
errorHandler.install();
|
|
46
|
-
|
|
47
|
-
fastify();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('instrumentation adds custom handler', function() {
|
|
51
|
-
it('patches fastify', function() {
|
|
52
|
-
expect(server.setErrorHandler).to.have.been.calledWith(errorHandler.handler);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('defaultErrorHandler()', function() {
|
|
57
|
-
let request;
|
|
58
|
-
let err;
|
|
59
|
-
|
|
60
|
-
beforeEach(function() {
|
|
61
|
-
request = {};
|
|
62
|
-
err = {};
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('logs at error level when 500 status code', function() {
|
|
66
|
-
errorHandler.handler(err, request, reply);
|
|
67
|
-
expect(reply.log.error).calledWith({ req: request, res: reply, err });
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('logs at info level when <500 status code', function() {
|
|
71
|
-
reply.statusCode = 404;
|
|
72
|
-
errorHandler.handler(err, request, reply);
|
|
73
|
-
expect(reply.log.info).calledWith({ res: reply, err });
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe('handler()', function() {
|
|
78
|
-
let request;
|
|
79
|
-
let err;
|
|
80
|
-
let store;
|
|
81
|
-
let userHandler;
|
|
82
|
-
|
|
83
|
-
beforeEach(function() {
|
|
84
|
-
request = {};
|
|
85
|
-
err = securityException.create();
|
|
86
|
-
store = {
|
|
87
|
-
protect: {
|
|
88
|
-
block: sinon.stub(),
|
|
89
|
-
findings: {
|
|
90
|
-
securityException: ['block', 'cmd-injection']
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
userHandler = sinon.stub();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('calls default handler when the source context is not available', function() {
|
|
98
|
-
errorHandler.handler(err, request, reply);
|
|
99
|
-
expect(core.logger.info).calledWith('source context not found; unable to handle response');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('when set, calls user handler when the source context is not available', function() {
|
|
103
|
-
server.setErrorHandler(userHandler);
|
|
104
|
-
errorHandler.handler(err, request, reply);
|
|
105
|
-
expect(core.logger.info).calledWith('source context not found; unable to handle response');
|
|
106
|
-
expect(userHandler).calledWith(err, request, reply);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('calls source context\'s .block() with mode and id of rule which raised error', function() {
|
|
110
|
-
const {
|
|
111
|
-
protect: {
|
|
112
|
-
block,
|
|
113
|
-
findings: { securityException }
|
|
114
|
-
}
|
|
115
|
-
} = store;
|
|
116
|
-
core.scopes.sources.run(store, () => {
|
|
117
|
-
errorHandler.handler(err, request, reply);
|
|
118
|
-
expect(core.logger.info).not.called;
|
|
119
|
-
expect(block).calledWith(...securityException);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('calls default error handler when error is not security exception', function() {
|
|
124
|
-
err = {};
|
|
125
|
-
core.scopes.sources.run(store, () => {
|
|
126
|
-
errorHandler.handler(err, request, reply);
|
|
127
|
-
expect(core.logger.info).not.called;
|
|
128
|
-
expect(errorHandler.defaultErrorHandler).calledWith(err, request, reply);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('when set, calls user handler when error is ont seecurity exception', function() {
|
|
133
|
-
err = {};
|
|
134
|
-
server.setErrorHandler(userHandler);
|
|
135
|
-
core.scopes.sources.run(store, () => {
|
|
136
|
-
errorHandler.handler(err, request, reply);
|
|
137
|
-
expect(core.logger.info).not.called;
|
|
138
|
-
expect(userHandler).calledWith(err, request, reply);
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
});
|
package/lib/esm-loader.test.mjs
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import chai from 'chai';
|
|
2
|
-
const { expect } = chai;
|
|
3
|
-
|
|
4
|
-
describe('protect esm-loader', function() {
|
|
5
|
-
it('exports the ESM hooks', async function() {
|
|
6
|
-
const hooks = await import('./esm-loader.mjs');
|
|
7
|
-
expect(hooks).itself.to.respondTo('getSource');
|
|
8
|
-
expect(hooks).itself.to.respondTo('transformSource');
|
|
9
|
-
expect(hooks).itself.to.respondTo('load');
|
|
10
|
-
});
|
|
11
|
-
});
|