@contrast/assess 1.34.0 → 1.36.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/crypto-analysis/install/crypto.js +1 -1
- package/lib/dataflow/propagation/install/JSON/parse-fn.js +1 -1
- package/lib/dataflow/propagation/install/JSON/parse.js +3 -2
- package/lib/dataflow/propagation/install/JSON/parse.test.js +2 -2
- package/lib/dataflow/propagation/install/JSON/stringify.js +11 -10
- package/lib/dataflow/propagation/install/JSON/stringify.test.js +3 -3
- package/lib/dataflow/propagation/install/array-prototype-join.js +4 -3
- package/lib/dataflow/propagation/install/array-prototype-join.test.js +3 -3
- package/lib/dataflow/propagation/install/buffer.js +2 -3
- package/lib/dataflow/propagation/install/contrast-methods/tag.test.js +2 -2
- package/lib/dataflow/propagation/install/decode-uri-component.js +5 -8
- package/lib/dataflow/propagation/install/decode-uri-component.test.js +1 -1
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +6 -9
- package/lib/dataflow/propagation/install/ejs/escape-xml.test.js +2 -2
- package/lib/dataflow/propagation/install/ejs/template.js +2 -2
- package/lib/dataflow/propagation/install/encode-uri.js +4 -6
- package/lib/dataflow/propagation/install/encode-uri.test.js +2 -2
- package/lib/dataflow/propagation/install/escape-html.js +5 -8
- package/lib/dataflow/propagation/install/escape-html.test.js +3 -3
- package/lib/dataflow/propagation/install/escape.js +5 -8
- package/lib/dataflow/propagation/install/escape.test.js +2 -2
- package/lib/dataflow/propagation/install/fastify-send.js +3 -5
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +6 -9
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.test.js +1 -1
- package/lib/dataflow/propagation/install/joi/boolean.js +50 -52
- package/lib/dataflow/propagation/install/joi/expression.js +3 -10
- package/lib/dataflow/propagation/install/joi/index.js +98 -101
- package/lib/dataflow/propagation/install/joi/keys.js +10 -5
- package/lib/dataflow/propagation/install/joi/number.js +50 -52
- package/lib/dataflow/propagation/install/joi/string-schema.js +9 -14
- package/lib/dataflow/propagation/install/joi/utils.js +7 -4
- package/lib/dataflow/propagation/install/joi/values.js +5 -7
- package/lib/dataflow/propagation/install/mongoose/schema-map.js +5 -4
- package/lib/dataflow/propagation/install/mongoose/schema-map.test.js +4 -4
- package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +5 -4
- package/lib/dataflow/propagation/install/mongoose/schema-mixed.test.js +4 -5
- package/lib/dataflow/propagation/install/mongoose/schema-string.js +3 -4
- package/lib/dataflow/propagation/install/mustache-escape.js +5 -8
- package/lib/dataflow/propagation/install/mustache-escape.test.js +2 -2
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +5 -8
- package/lib/dataflow/propagation/install/mysql-connection-escape.test.js +2 -2
- package/lib/dataflow/propagation/install/parse-int.js +3 -3
- package/lib/dataflow/propagation/install/path/basename.js +7 -12
- package/lib/dataflow/propagation/install/path/basename.test.js +2 -2
- package/lib/dataflow/propagation/install/path/common.js +2 -2
- package/lib/dataflow/propagation/install/path/dirname.js +5 -10
- package/lib/dataflow/propagation/install/path/dirname.test.js +2 -2
- package/lib/dataflow/propagation/install/path/extname.js +6 -11
- package/lib/dataflow/propagation/install/path/extname.test.js +2 -2
- package/lib/dataflow/propagation/install/path/format.js +7 -13
- package/lib/dataflow/propagation/install/path/format.test.js +2 -2
- package/lib/dataflow/propagation/install/path/join-and-resolve.js +7 -12
- package/lib/dataflow/propagation/install/path/join-and-resolve.test.js +2 -2
- package/lib/dataflow/propagation/install/path/normalize.js +4 -11
- package/lib/dataflow/propagation/install/path/normalize.test.js +2 -2
- package/lib/dataflow/propagation/install/path/parse.js +3 -8
- package/lib/dataflow/propagation/install/path/parse.test.js +2 -2
- package/lib/dataflow/propagation/install/path/relative.js +5 -11
- package/lib/dataflow/propagation/install/path/relative.test.js +2 -2
- package/lib/dataflow/propagation/install/path/toNamespacedPath.js +5 -11
- package/lib/dataflow/propagation/install/path/toNamespacedPath.test.js +2 -2
- package/lib/dataflow/propagation/install/pug/index.js +8 -3
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +5 -8
- package/lib/dataflow/propagation/install/pug-runtime-escape.test.js +1 -1
- package/lib/dataflow/propagation/install/querystring/escape.js +3 -3
- package/lib/dataflow/propagation/install/querystring/parse.js +7 -11
- package/lib/dataflow/propagation/install/querystring/stringify.js +3 -3
- package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +4 -3
- package/lib/dataflow/propagation/install/reg-exp-prototype-exec.test.js +5 -3
- package/lib/dataflow/propagation/install/send.js +5 -10
- package/lib/dataflow/propagation/install/sequelize/query-generator.js +3 -4
- package/lib/dataflow/propagation/install/sequelize/sql-string.js +8 -12
- package/lib/dataflow/propagation/install/sequelize/sql-string.test.js +2 -13
- package/lib/dataflow/propagation/install/sql-template-strings.js +3 -5
- package/lib/dataflow/propagation/install/sql-template-strings.test.js +2 -2
- package/lib/dataflow/propagation/install/string/concat.js +2 -1
- package/lib/dataflow/propagation/install/string/concat.test.js +15 -2
- package/lib/dataflow/propagation/install/string/format-methods.js +4 -2
- package/lib/dataflow/propagation/install/string/format-methods.test.js +15 -2
- package/lib/dataflow/propagation/install/string/html-methods.js +1 -1
- package/lib/dataflow/propagation/install/string/html-methods.test.js +15 -2
- package/lib/dataflow/propagation/install/string/index.js +2 -2
- package/lib/dataflow/propagation/install/string/match-all.js +2 -1
- package/lib/dataflow/propagation/install/string/match-all.test.js +13 -0
- package/lib/dataflow/propagation/install/string/match.js +11 -10
- package/lib/dataflow/propagation/install/string/match.test.js +13 -0
- package/lib/dataflow/propagation/install/string/replace.js +15 -9
- package/lib/dataflow/propagation/install/string/replace.test.js +13 -0
- package/lib/dataflow/propagation/install/string/slice.js +2 -1
- package/lib/dataflow/propagation/install/string/slice.test.js +13 -0
- package/lib/dataflow/propagation/install/string/split.js +2 -1
- package/lib/dataflow/propagation/install/string/split.test.js +13 -0
- package/lib/dataflow/propagation/install/string/substring.js +2 -1
- package/lib/dataflow/propagation/install/string/substring.test.js +13 -0
- package/lib/dataflow/propagation/install/string/trim.js +4 -1
- package/lib/dataflow/propagation/install/string/trim.test.js +13 -0
- package/lib/dataflow/propagation/install/unescape.js +5 -8
- package/lib/dataflow/propagation/install/unescape.test.js +2 -2
- package/lib/dataflow/propagation/install/url/domain-parsers.js +4 -5
- package/lib/dataflow/propagation/install/url/domain-parsers.test.js +2 -2
- package/lib/dataflow/propagation/install/url/parse.js +3 -2
- package/lib/dataflow/propagation/install/url/parse.test.js +2 -2
- package/lib/dataflow/propagation/install/url/searchParams.js +5 -5
- package/lib/dataflow/propagation/install/url/searchParams.test.js +2 -2
- package/lib/dataflow/propagation/install/url/url.js +6 -3
- package/lib/dataflow/propagation/install/url/url.test.js +2 -2
- package/lib/dataflow/propagation/install/util-format.js +7 -6
- package/lib/dataflow/propagation/install/util-format.test.js +2 -2
- package/lib/dataflow/propagation/install/validator/hooks.js +7 -2
- package/lib/dataflow/sinks/install/child-process.js +1 -1
- package/lib/dataflow/sinks/install/child-process.test.js +1 -1
- package/lib/dataflow/sinks/install/fs.js +1 -1
- package/lib/dataflow/sinks/install/fs.test.js +1 -1
- package/lib/dataflow/sinks/install/function.js +1 -1
- package/lib/dataflow/sinks/install/http/request.js +2 -1
- package/lib/dataflow/sinks/install/http/request.test.js +1 -1
- package/lib/dataflow/sinks/install/http/server-response.test.js +3 -5
- package/lib/dataflow/sinks/install/restify.js +1 -1
- package/lib/dataflow/sinks/install/vm.js +4 -2
- package/lib/dataflow/sinks/install/vm.test.js +1 -1
- package/lib/dataflow/sources/handler.js +6 -3
- package/lib/dataflow/sources/handler.test.js +38 -0
- package/lib/dataflow/sources/install/body-parser1.test.js +4 -4
- package/lib/dataflow/sources/install/busboy.js +8 -3
- package/lib/dataflow/sources/install/busboy.test.js +2 -2
- package/lib/dataflow/sources/install/cookie-parser1.test.js +2 -2
- package/lib/dataflow/sources/install/express/params.js +14 -11
- package/lib/dataflow/sources/install/express/params.test.js +5 -7
- package/lib/dataflow/sources/install/express/parsedUrl.js +3 -2
- package/lib/dataflow/sources/install/fastify/fastify.js +7 -6
- package/lib/dataflow/sources/install/fastify/fastify.test.js +2 -2
- package/lib/dataflow/sources/install/formidable1.js +7 -6
- package/lib/dataflow/sources/install/formidable1.test.js +2 -2
- package/lib/dataflow/sources/install/hapi/hapi.js +8 -10
- package/lib/dataflow/sources/install/hapi/hapi.test.js +0 -1
- package/lib/dataflow/sources/install/http.js +20 -16
- package/lib/dataflow/sources/install/http.test.js +28 -34
- package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +7 -7
- package/lib/dataflow/sources/install/koa/koa-bodyparsers.test.js +3 -4
- package/lib/dataflow/sources/install/koa/koa-multer.js +8 -4
- package/lib/dataflow/sources/install/koa/koa-routers.js +7 -6
- package/lib/dataflow/sources/install/koa/koa-routers.test.js +2 -2
- package/lib/dataflow/sources/install/koa/koa2.js +7 -3
- package/lib/dataflow/sources/install/koa/koa2.test.js +1 -1
- package/lib/dataflow/sources/install/multer1.js +6 -2
- package/lib/dataflow/sources/install/qs6.js +1 -1
- package/lib/dataflow/sources/install/querystring.js +1 -1
- package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.js +1 -4
- package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.test.js +6 -8
- package/lib/dataflow/sources/install/restify/jsonBodyParser.js +0 -1
- package/lib/dataflow/sources/install/restify/jsonBodyParser.test.js +4 -8
- package/lib/dataflow/sources/install/restify/router.test.js +2 -2
- package/lib/dataflow/tag-utils.js +1 -1
- package/lib/dataflow/tracker.js +1 -1
- package/lib/dataflow/utils/is-safe-content-type.js +3 -2
- package/lib/event-factory.js +4 -4
- package/lib/event-factory.test.js +19 -14
- package/lib/get-policy.js +2 -2
- package/lib/index.d.ts +11 -6
- package/lib/index.js +18 -7
- package/lib/index.test.js +4 -0
- package/lib/make-source-context.js +37 -28
- package/lib/make-source-context.test.js +7 -7
- package/lib/response-scanning/handlers/index.js +7 -5
- package/lib/response-scanning/handlers/utils.js +11 -8
- package/lib/response-scanning/install/http.js +1 -1
- package/lib/sampler.js +136 -0
- package/lib/sampler.test.js +296 -0
- package/lib/session-configuration/install/express-session.js +1 -1
- package/lib/session-configuration/install/fastify-cookie.js +1 -1
- package/package.json +10 -10
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { InputType } = require('@contrast/common');
|
|
19
|
+
const { InstrumentationType: { SOURCE } } = require('../../../constants');
|
|
19
20
|
const { patchType } = require('../common');
|
|
20
21
|
|
|
21
22
|
module.exports = (core) => {
|
|
@@ -23,12 +24,15 @@ module.exports = (core) => {
|
|
|
23
24
|
depHooks,
|
|
24
25
|
patcher,
|
|
25
26
|
logger,
|
|
26
|
-
assess: {
|
|
27
|
+
assess: {
|
|
28
|
+
getSourceContext,
|
|
29
|
+
dataflow: { sources }
|
|
30
|
+
},
|
|
27
31
|
scopes,
|
|
28
32
|
} = core;
|
|
29
33
|
|
|
30
34
|
function handler(req, constructorOpt) {
|
|
31
|
-
const sourceContext =
|
|
35
|
+
const sourceContext = getSourceContext(SOURCE);
|
|
32
36
|
if (!sourceContext) return;
|
|
33
37
|
|
|
34
38
|
function handle(context, data, key) {
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { InputType: { QUERYSTRING: inputType } } = require('@contrast/common');
|
|
19
|
-
const { patchType } = require('../common');
|
|
20
19
|
const { InstrumentationType: { SOURCE } } = require('../../../constants');
|
|
20
|
+
const { patchType } = require('../common');
|
|
21
21
|
|
|
22
22
|
module.exports = (core) => {
|
|
23
23
|
const {
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { InputType } = require('@contrast/common');
|
|
19
|
-
const { patchType } = require('../common');
|
|
20
19
|
const { InstrumentationType: { SOURCE } } = require('../../../constants');
|
|
20
|
+
const { patchType } = require('../common');
|
|
21
21
|
|
|
22
22
|
module.exports = (core) => {
|
|
23
23
|
const {
|
|
@@ -46,10 +46,7 @@ module.exports = function init(core) {
|
|
|
46
46
|
data.args[2] = function contrastNext(...args) {
|
|
47
47
|
const sourceContext = getSourceContext(SOURCE);
|
|
48
48
|
|
|
49
|
-
if (!sourceContext)
|
|
50
|
-
logger.error({ funcKey }, 'unable to handle source. Missing `sourceContext`');
|
|
51
|
-
return next(...args);
|
|
52
|
-
}
|
|
49
|
+
if (!sourceContext) return next(...args);
|
|
53
50
|
|
|
54
51
|
if (sourceContext.parsedBody) {
|
|
55
52
|
logger.trace({ funcKey }, 'values already tracked');
|
|
@@ -29,20 +29,18 @@ describe('assess dataflow sources restify fieldedTextBodyParser', function () {
|
|
|
29
29
|
middleware = fieldedTextBodyParserStub();
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
it('does not call `handle` when
|
|
32
|
+
it('does not call `handle` when there is no assess policy in request context', function () {
|
|
33
33
|
simulateRequestScope(() => {
|
|
34
34
|
middleware(req, res, next);
|
|
35
35
|
|
|
36
36
|
expect(handle).not.to.have.been.called;
|
|
37
|
-
|
|
38
|
-
{ funcKey: 'assess-dataflow-source:restify.plugins.fieldedTextBodyParser.parseFieldedText' },
|
|
39
|
-
'unable to handle source. Missing `sourceContext`'
|
|
40
|
-
);
|
|
41
|
-
}, {});
|
|
37
|
+
}, { assess: { policy: null } });
|
|
42
38
|
});
|
|
43
39
|
|
|
44
|
-
it('does not
|
|
40
|
+
it('does not handle source when body is already tracked', function () {
|
|
45
41
|
simulateRequestScope(() => {
|
|
42
|
+
core.scopes.sources.getStore().assess.parsedBody = true;
|
|
43
|
+
|
|
46
44
|
middleware(req, res, next);
|
|
47
45
|
|
|
48
46
|
expect(handle).not.to.have.been.called;
|
|
@@ -50,7 +48,7 @@ describe('assess dataflow sources restify fieldedTextBodyParser', function () {
|
|
|
50
48
|
{ funcKey: 'assess-dataflow-source:restify.plugins.fieldedTextBodyParser.parseFieldedText' },
|
|
51
49
|
'values already tracked'
|
|
52
50
|
);
|
|
53
|
-
}
|
|
51
|
+
});
|
|
54
52
|
});
|
|
55
53
|
|
|
56
54
|
it('calls `handle` appropriately', function () {
|
|
@@ -31,24 +31,20 @@ describe('assess dataflow sources restify jsonBodyParser', function () {
|
|
|
31
31
|
init(core).install();
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
it('does not
|
|
34
|
+
it('does not handle source when there is no assess policy in request context', function () {
|
|
35
35
|
const [, middleware] = jsonBodyParserStub();
|
|
36
36
|
|
|
37
37
|
simulateRequestScope(() => {
|
|
38
38
|
middleware(req, res, next);
|
|
39
|
-
|
|
40
39
|
expect(handle).not.to.have.been.called;
|
|
41
|
-
|
|
42
|
-
{ funcKey: 'assess-dataflow-source:restify.plugins.jsonBodyParser.parseJson' },
|
|
43
|
-
'unable to handle source. Missing `sourceContext`'
|
|
44
|
-
);
|
|
45
|
-
}, {});
|
|
40
|
+
}, { assess: { policy: null } });
|
|
46
41
|
});
|
|
47
42
|
|
|
48
43
|
it('does not call `handle` when body is already tracked', function () {
|
|
49
44
|
const [, middleware] = jsonBodyParserStub();
|
|
50
45
|
|
|
51
46
|
simulateRequestScope(() => {
|
|
47
|
+
core.scopes.sources.getStore().assess.parsedBody = true;
|
|
52
48
|
middleware(req, res, next);
|
|
53
49
|
|
|
54
50
|
expect(handle).not.to.have.been.called;
|
|
@@ -56,7 +52,7 @@ describe('assess dataflow sources restify jsonBodyParser', function () {
|
|
|
56
52
|
{ funcKey: 'assess-dataflow-source:restify.plugins.jsonBodyParser.parseJson' },
|
|
57
53
|
'values already tracked'
|
|
58
54
|
);
|
|
59
|
-
}
|
|
55
|
+
});
|
|
60
56
|
});
|
|
61
57
|
|
|
62
58
|
it('calls `handle` appropriately', function () {
|
|
@@ -23,7 +23,7 @@ describe('assess dataflow sources restify router', function () {
|
|
|
23
23
|
init(core).install();
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
it('does not call `handle` when
|
|
26
|
+
it('does not call `handle` when there is no assess policy in request context', function () {
|
|
27
27
|
simulateRequestScope(() => {
|
|
28
28
|
Router.prototype.lookup(req);
|
|
29
29
|
|
|
@@ -32,7 +32,7 @@ describe('assess dataflow sources restify router', function () {
|
|
|
32
32
|
{ funcKey: 'assess-dataflow-source:restify.Router.prototype.lookup' },
|
|
33
33
|
'unable to handle source. Missing `sourceContext`'
|
|
34
34
|
);
|
|
35
|
-
}, {});
|
|
35
|
+
}, { assess: { policy: null } });
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
it('does not call `handle` when body is already tracked', function () {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
const { StringPrototypeSplit } = require('@contrast/common');
|
|
17
|
+
const { primordials: { StringPrototypeSplit } } = require('@contrast/common');
|
|
18
18
|
|
|
19
19
|
//
|
|
20
20
|
// This module implements tag range manipulation functions. There are generally
|
package/lib/dataflow/tracker.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const distringuish = require('@contrast/distringuish');
|
|
19
|
-
const { BufferPrototypeToString, BufferFrom, isString } = require('@contrast/common');
|
|
19
|
+
const { primordials: { BufferPrototypeToString, BufferFrom }, isString } = require('@contrast/common');
|
|
20
20
|
|
|
21
21
|
module.exports = function tracker(core) {
|
|
22
22
|
const {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* way not consistent with the End User License Agreement.
|
|
14
14
|
*/
|
|
15
15
|
'use strict';
|
|
16
|
-
|
|
16
|
+
const { primordials: { ArrayPrototypeJoin, RegExpPrototypeTest } } = require('@contrast/common');
|
|
17
17
|
const SAFE_XSS_CONTENT_TYPES = [
|
|
18
18
|
'/json',
|
|
19
19
|
'/x-json',
|
|
@@ -22,9 +22,10 @@ const SAFE_XSS_CONTENT_TYPES = [
|
|
|
22
22
|
'/pdf',
|
|
23
23
|
'/csv'
|
|
24
24
|
];
|
|
25
|
+
const SAFE_XSS_REGEX = new RegExp(ArrayPrototypeJoin.call(SAFE_XSS_CONTENT_TYPES, '|'));
|
|
25
26
|
|
|
26
27
|
function isSafeContentType(contentType) {
|
|
27
|
-
return
|
|
28
|
+
return RegExpPrototypeTest.call(SAFE_XSS_REGEX, contentType);
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
module.exports = { isSafeContentType, SAFE_XSS_CONTENT_TYPES };
|
package/lib/event-factory.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const { InputType, StringPrototypeMatch } = require('@contrast/common');
|
|
18
|
+
const { InputType, primordials: { StringPrototypeMatch } } = require('@contrast/common');
|
|
19
19
|
const ANNOTATION_REGEX = /^(A|O|R|P|P\d+)$/;
|
|
20
20
|
const SOURCE_EVENT_MSG = 'Source event not created: %s';
|
|
21
21
|
const PROPAGATION_EVENT_MSG = 'Propagation event not created: %s';
|
|
@@ -183,7 +183,7 @@ module.exports = function (core) {
|
|
|
183
183
|
return null;
|
|
184
184
|
}
|
|
185
185
|
if (
|
|
186
|
-
(!source || !
|
|
186
|
+
(!source || !StringPrototypeMatch.call(source, ANNOTATION_REGEX))
|
|
187
187
|
) {
|
|
188
188
|
logger.debug({ name }, 'malformed or missing sink event source field');
|
|
189
189
|
return null;
|
|
@@ -237,7 +237,7 @@ module.exports = function (core) {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
if (
|
|
240
|
-
(!source || !
|
|
240
|
+
(!source || !StringPrototypeMatch.call(source, ANNOTATION_REGEX))
|
|
241
241
|
) {
|
|
242
242
|
logger.debug({ name }, 'malformed or missing sink event source field');
|
|
243
243
|
return null;
|
|
@@ -299,7 +299,7 @@ module.exports = function (core) {
|
|
|
299
299
|
return null;
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
if (!source || !
|
|
302
|
+
if (!source || !StringPrototypeMatch.call(source, ANNOTATION_REGEX)) {
|
|
303
303
|
logger.debug({ name }, 'malformed or missing sink event source field');
|
|
304
304
|
return null;
|
|
305
305
|
}
|
|
@@ -199,14 +199,16 @@ testMethod('assess event-factory', function () {
|
|
|
199
199
|
});
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
202
|
+
['SOME', 'SINK', 'NONE'].forEach((option) => {
|
|
203
|
+
it(`returns an event without stacktrace generator function when stacktraces option is not set to "ALL" and set to ${option}`, function () {
|
|
204
|
+
core.config.assess.stacktraces = option;
|
|
205
|
+
core.scopes.sources.run(validStore, function () {
|
|
206
|
+
const result = createPropagationEvent(validData);
|
|
206
207
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
expect(result).to.be.like(validResult);
|
|
209
|
+
expect(result.time).not.to.be.undefined;
|
|
210
|
+
expect(result.stack).to.deep.equal([]);
|
|
211
|
+
});
|
|
210
212
|
});
|
|
211
213
|
});
|
|
212
214
|
});
|
|
@@ -296,14 +298,17 @@ testMethod('assess event-factory', function () {
|
|
|
296
298
|
});
|
|
297
299
|
});
|
|
298
300
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
['ALL', 'SOME', 'SINK'].forEach((option) => {
|
|
302
|
+
it(`returns an event with stacktrace generator function when stacktraces option is not set to "NONE" and set to ${option}`, function () {
|
|
303
|
+
core.config.assess.stacktraces = option;
|
|
304
|
+
core.scopes.sources.run(validStore, function () {
|
|
305
|
+
const result = createSinkEvent(validData);
|
|
303
306
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
+
expect(result).to.be.like(validResult);
|
|
308
|
+
expect(result.time).not.to.be.undefined;
|
|
309
|
+
expect(result.stack).to.be.instanceOf(Array);
|
|
310
|
+
expect(result.stack.length).to.be.greaterThan(0);
|
|
311
|
+
});
|
|
307
312
|
});
|
|
308
313
|
});
|
|
309
314
|
|
package/lib/get-policy.js
CHANGED
|
@@ -22,7 +22,7 @@ const {
|
|
|
22
22
|
Rule,
|
|
23
23
|
ResponseScanningRule,
|
|
24
24
|
SessionConfigurationRule,
|
|
25
|
-
ArrayPrototypeJoin,
|
|
25
|
+
primordials: { ArrayPrototypeJoin, RegExpPrototypeTest }
|
|
26
26
|
} = require('@contrast/common');
|
|
27
27
|
|
|
28
28
|
const ASSESS_RULES = Object.values({
|
|
@@ -323,7 +323,7 @@ class InputExclusion extends UrlExclusion {
|
|
|
323
323
|
matchesInputName(name) {
|
|
324
324
|
// BODY and QUERYSTRING always match since they apply broadly
|
|
325
325
|
if (!this._inputName && !this._inputNameRegex) return true;
|
|
326
|
-
return this._inputNameRegex ? this._inputNameRegex
|
|
326
|
+
return this._inputNameRegex ? RegExpPrototypeTest.call(this._inputNameRegex, name) : this._inputName === name;
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
329
|
|
package/lib/index.d.ts
CHANGED
|
@@ -12,17 +12,22 @@
|
|
|
12
12
|
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
13
|
* way not consistent with the End User License Agreement.
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
Rule,
|
|
18
|
-
SessionConfigurationRule,
|
|
19
|
-
} from '@contrast/common';
|
|
15
|
+
import { Rule, SessionConfigurationRule } from '@contrast/common';
|
|
16
|
+
import { Config } from '@contrast/config';
|
|
20
17
|
import { Core as _Core } from '@contrast/core';
|
|
18
|
+
import { Deadzones } from '@contrast/deadzones';
|
|
19
|
+
import { DepHooks } from '@contrast/dep-hooks';
|
|
20
|
+
import { Logger } from '@contrast/logger';
|
|
21
|
+
import { Patcher } from '@contrast/patcher';
|
|
22
|
+
import { ReporterBus } from '@contrast/reporter';
|
|
23
|
+
import { Rewriter } from '@contrast/rewriter';
|
|
24
|
+
import { Scopes } from '@contrast/scopes';
|
|
25
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
21
26
|
|
|
22
27
|
export interface Core extends _Core {
|
|
23
28
|
config: Config;
|
|
24
29
|
logger: Logger;
|
|
25
|
-
depHooks:
|
|
30
|
+
depHooks: DepHooks;
|
|
26
31
|
patcher: Patcher
|
|
27
32
|
rewriter: Rewriter;
|
|
28
33
|
scopes: Scopes;
|
package/lib/index.js
CHANGED
|
@@ -19,16 +19,22 @@ const { inspect } = require('util');
|
|
|
19
19
|
const { callChildComponentMethodsSync } = require('@contrast/common');
|
|
20
20
|
|
|
21
21
|
module.exports = function assess(core) {
|
|
22
|
-
const {
|
|
22
|
+
const {
|
|
23
|
+
config,
|
|
24
|
+
sources,
|
|
25
|
+
scopes: { instrumentation }
|
|
26
|
+
} = core;
|
|
23
27
|
|
|
24
28
|
const assess = core.assess = {
|
|
25
29
|
install() {
|
|
26
|
-
if (!
|
|
30
|
+
if (!config.getEffectiveValue('assess.enable')) {
|
|
27
31
|
core.logger.debug('assess is disabled, skipping installation');
|
|
28
32
|
return;
|
|
29
33
|
}
|
|
30
34
|
|
|
35
|
+
// configure rewriter
|
|
31
36
|
core.rewriter.install('assess');
|
|
37
|
+
// dispatch subcomponent installation
|
|
32
38
|
callChildComponentMethodsSync(core.assess, 'install');
|
|
33
39
|
},
|
|
34
40
|
};
|
|
@@ -43,19 +49,24 @@ module.exports = function assess(core) {
|
|
|
43
49
|
instrumentation.run(store, inspect, val, opts);
|
|
44
50
|
};
|
|
45
51
|
|
|
46
|
-
|
|
52
|
+
// ancillary tools used by different features
|
|
53
|
+
require('./sampler')(core);
|
|
47
54
|
require('./get-policy')(core);
|
|
48
55
|
require('./make-source-context')(core);
|
|
56
|
+
require('./rule-scopes')(core);
|
|
49
57
|
require('./get-source-context')(core);
|
|
50
58
|
require('./event-factory')(core);
|
|
51
|
-
|
|
59
|
+
|
|
60
|
+
// various Assess features
|
|
52
61
|
require('./dataflow')(core);
|
|
62
|
+
require('./crypto-analysis')(core);
|
|
53
63
|
require('./response-scanning')(core);
|
|
54
64
|
require('./session-configuration')(core);
|
|
55
65
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
// append async state to sources store when request-scope sources are created
|
|
67
|
+
sources.addHook('onSource', (ctx) => {
|
|
68
|
+
ctx.store.assess = assess.makeSourceContext(ctx);
|
|
69
|
+
});
|
|
59
70
|
|
|
60
71
|
return assess;
|
|
61
72
|
};
|
package/lib/index.test.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { expect } = require('chai');
|
|
4
|
+
const sinon = require('sinon');
|
|
4
5
|
const proxyquire = require('proxyquire');
|
|
5
6
|
const mocks = require('@contrast/test/mocks');
|
|
6
7
|
|
|
@@ -19,6 +20,9 @@ describe('assess', function () {
|
|
|
19
20
|
core.rewriter = mocks.rewriter();
|
|
20
21
|
core.scopes = mocks.scopes();
|
|
21
22
|
core.config.assess.enable = true;
|
|
23
|
+
core.sources = {
|
|
24
|
+
addHook: sinon.stub(),
|
|
25
|
+
};
|
|
22
26
|
|
|
23
27
|
assessModule = proxyquire('.', {
|
|
24
28
|
'./session-configuration': moduleMock('sessionConfiguration', assessMock),
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const { StringPrototypeToLowerCase } = require('@contrast/common');
|
|
19
|
-
|
|
18
|
+
const { primordials: { StringPrototypeToLowerCase, StringPrototypeSlice } } = require('@contrast/common');
|
|
20
19
|
/**
|
|
21
20
|
* @param {{
|
|
22
21
|
* assess: import('@contrast/assess').Assess,
|
|
@@ -24,46 +23,56 @@ const { StringPrototypeToLowerCase } = require('@contrast/common');
|
|
|
24
23
|
* }} core
|
|
25
24
|
*/
|
|
26
25
|
module.exports = function(core) {
|
|
27
|
-
const {
|
|
28
|
-
assess: { getPolicy },
|
|
29
|
-
logger,
|
|
30
|
-
} = core;
|
|
26
|
+
const { assess, logger } = core;
|
|
31
27
|
|
|
32
28
|
/**
|
|
33
29
|
* @returns {import('@contrast/assess').SourceContext}
|
|
34
30
|
*/
|
|
35
|
-
return core.assess.makeSourceContext = function (
|
|
36
|
-
let contentType, queries, uriPath;
|
|
37
|
-
|
|
31
|
+
return core.assess.makeSourceContext = function (sourceData) {
|
|
38
32
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
// todo: how to handle non-HTTP sources
|
|
34
|
+
const { incomingMessage: req } = sourceData;
|
|
35
|
+
|
|
36
|
+
// minimally process the request data for sampling and exclusions.
|
|
37
|
+
// more request fields will be appended in final result below.
|
|
38
|
+
let uriPath;
|
|
39
|
+
let queries;
|
|
40
|
+
const idx = req.url.indexOf('?');
|
|
41
|
+
if (idx >= 0) {
|
|
42
|
+
uriPath = StringPrototypeSlice.call(req.url, 0, idx);
|
|
43
|
+
queries = StringPrototypeSlice.call(req.url, idx + 1);
|
|
43
44
|
} else {
|
|
44
45
|
uriPath = req.url;
|
|
45
46
|
queries = '';
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
const ctx = sourceData.store.assess = {
|
|
50
|
+
// default policy to `null` until it is set later below. this will cause
|
|
51
|
+
// all instrumentation to short-circuit, see `./get-source-context.js`.
|
|
52
|
+
policy: null,
|
|
53
|
+
reqData: { uriPath, queries },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// check whether sampling allows processing
|
|
57
|
+
ctx.sampleInfo = assess.sampler?.getSampleInfo?.(sourceData) ?? null;
|
|
58
|
+
if (ctx.sampleInfo?.canSample === false) return ctx;
|
|
59
|
+
|
|
60
|
+
// set policy - can be returned as `null` if request is url-excluded.
|
|
61
|
+
ctx.policy = assess.getPolicy(ctx.reqData);
|
|
62
|
+
if (!ctx.policy) return ctx;
|
|
63
|
+
|
|
64
|
+
// build remaining reqData
|
|
65
|
+
ctx.reqData.headers = { ...req.headers }; // copy to avoid storing tracked values
|
|
66
|
+
ctx.reqData.ip = req.socket.remoteAddress;
|
|
67
|
+
ctx.reqData.httpVersion = req.httpVersion;
|
|
68
|
+
ctx.reqData.method = req.method;
|
|
69
|
+
if (ctx.reqData.headers['content-type'])
|
|
70
|
+
ctx.reqData.contentType = StringPrototypeToLowerCase.call(ctx.reqData.headers['content-type']);
|
|
53
71
|
|
|
54
72
|
return {
|
|
73
|
+
...ctx,
|
|
55
74
|
propagationEventsCount: 0,
|
|
56
75
|
sourceEventsCount: 0,
|
|
57
|
-
policy: getPolicy({ uriPath }),
|
|
58
|
-
reqData: {
|
|
59
|
-
ip: req.socket.remoteAddress,
|
|
60
|
-
httpVersion: req.httpVersion,
|
|
61
|
-
method: req.method,
|
|
62
|
-
headers,
|
|
63
|
-
uriPath,
|
|
64
|
-
queries,
|
|
65
|
-
contentType,
|
|
66
|
-
},
|
|
67
76
|
responseData: {},
|
|
68
77
|
ruleState: {},
|
|
69
78
|
};
|
|
@@ -5,7 +5,6 @@ const sinon = require('sinon');
|
|
|
5
5
|
const { initAssessFixture } = require('@contrast/test/fixtures');
|
|
6
6
|
const mocks = require('@contrast/test/mocks');
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
describe('assess makeSourceContext', function () {
|
|
10
9
|
let core;
|
|
11
10
|
|
|
@@ -14,7 +13,7 @@ describe('assess makeSourceContext', function () {
|
|
|
14
13
|
});
|
|
15
14
|
|
|
16
15
|
it('will return `null` and log error when passed insufficient parameters', function() {
|
|
17
|
-
const assessCtx = core.assess.makeSourceContext({}
|
|
16
|
+
const assessCtx = core.assess.makeSourceContext({});
|
|
18
17
|
expect(assessCtx).to.be.null;
|
|
19
18
|
expect(core.logger.error).to.have.been.calledWithMatch({
|
|
20
19
|
err: sinon.match.has('message'),
|
|
@@ -22,13 +21,14 @@ describe('assess makeSourceContext', function () {
|
|
|
22
21
|
});
|
|
23
22
|
|
|
24
23
|
it('construct the store correctly given req and res', function() {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
const mockSourceData = {
|
|
25
|
+
store: {},
|
|
26
|
+
incomingMessage: mocks.incomingMessage()
|
|
27
|
+
};
|
|
28
|
+
const assessCtx = core.assess.makeSourceContext(mockSourceData);
|
|
30
29
|
expect(assessCtx).to.deep.include({
|
|
31
30
|
propagationEventsCount: 0,
|
|
31
|
+
sampleInfo: null,
|
|
32
32
|
sourceEventsCount: 0,
|
|
33
33
|
reqData: {
|
|
34
34
|
contentType: 'text/html',
|
|
@@ -16,10 +16,12 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
primordials: {
|
|
20
|
+
StringPrototypeToLowerCase,
|
|
21
|
+
JSONStringify,
|
|
22
|
+
StringPrototypeSubstring,
|
|
23
|
+
},
|
|
24
|
+
ResponseScanningRule,
|
|
23
25
|
} = require('@contrast/common');
|
|
24
26
|
const {
|
|
25
27
|
escapeHtml,
|
|
@@ -201,7 +203,7 @@ module.exports = function(core) {
|
|
|
201
203
|
|
|
202
204
|
reportFindings(sourceContext, {
|
|
203
205
|
ruleId: ResponseScanningRule.CSP_HEADER_INSECURE,
|
|
204
|
-
vulnerabilityMetadata: { data:
|
|
206
|
+
vulnerabilityMetadata: { data: JSONStringify(vulnerabilityMetadata) }
|
|
205
207
|
});
|
|
206
208
|
}
|
|
207
209
|
};
|
|
@@ -16,12 +16,15 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
primordials: {
|
|
20
|
+
ArrayPrototypeJoin,
|
|
21
|
+
StringPrototypeSubstring,
|
|
22
|
+
StringPrototypeToLowerCase,
|
|
23
|
+
StringPrototypeSplit,
|
|
24
|
+
StringPrototypeTrim,
|
|
25
|
+
StringPrototypeReplace,
|
|
26
|
+
RegExpPrototypeTest
|
|
27
|
+
}
|
|
25
28
|
} = require('@contrast/common');
|
|
26
29
|
|
|
27
30
|
//
|
|
@@ -38,7 +41,7 @@ const reUnescapedHtml = /[&<>"']/g;
|
|
|
38
41
|
const reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
|
|
39
42
|
|
|
40
43
|
function escapeHtml(string) {
|
|
41
|
-
return (string &&
|
|
44
|
+
return (string && RegExpPrototypeTest.call(reHasUnescapedHtml, string))
|
|
42
45
|
? StringPrototypeReplace.call(string, reUnescapedHtml, (chr) => htmlEscapes[chr])
|
|
43
46
|
: (string || '');
|
|
44
47
|
}
|
|
@@ -257,7 +260,7 @@ function isSrcSecure(sources, defaultSources) {
|
|
|
257
260
|
function isSourceSecure(sources) {
|
|
258
261
|
return (
|
|
259
262
|
sources.length > 0 &&
|
|
260
|
-
sources.every((source) => !!source &&
|
|
263
|
+
sources.every((source) => !!source && !RegExpPrototypeTest.call(/\*/, source))
|
|
261
264
|
);
|
|
262
265
|
}
|
|
263
266
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const { StringPrototypeSplit, StringPrototypeSubstring, StringPrototypeToLowerCase, StringPrototypeTrim } = require('@contrast/common');
|
|
18
|
+
const { primordials: { StringPrototypeSplit, StringPrototypeSubstring, StringPrototypeToLowerCase, StringPrototypeTrim } } = require('@contrast/common');
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* @param {{
|