@contrast/assess 1.41.0 → 1.42.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 +4 -5
- package/lib/crypto-analysis/install/crypto.test.js +1 -1
- package/lib/crypto-analysis/install/math.js +2 -4
- package/lib/dataflow/propagation/install/JSON/parse.js +2 -3
- package/lib/dataflow/propagation/install/JSON/stringify.js +3 -4
- package/lib/dataflow/propagation/install/array-prototype-join.js +2 -3
- package/lib/dataflow/propagation/install/buffer.js +3 -4
- package/lib/dataflow/propagation/install/contrast-methods/add.js +2 -3
- package/lib/dataflow/propagation/install/contrast-methods/number.js +2 -3
- package/lib/dataflow/propagation/install/contrast-methods/string.js +2 -3
- package/lib/dataflow/propagation/install/contrast-methods/tag.js +2 -3
- package/lib/dataflow/propagation/install/decode-uri-component.js +2 -3
- package/lib/dataflow/propagation/install/ejs/escape-xml.js +3 -4
- package/lib/dataflow/propagation/install/ejs/template.js +3 -4
- package/lib/dataflow/propagation/install/ejs/template.test.js +1 -1
- package/lib/dataflow/propagation/install/encode-uri.js +2 -3
- package/lib/dataflow/propagation/install/escape-html.js +3 -4
- package/lib/dataflow/propagation/install/escape.js +2 -3
- package/lib/dataflow/propagation/install/fastify-send.js +3 -3
- package/lib/dataflow/propagation/install/fastify-send.test.js +1 -3
- package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +3 -4
- package/lib/dataflow/propagation/install/isnumeric-0.js +1 -1
- package/lib/dataflow/propagation/install/joi/any.js +1 -1
- package/lib/dataflow/propagation/install/joi/any.test.js +1 -1
- package/lib/dataflow/propagation/install/joi/array.test.js +5 -5
- package/lib/dataflow/propagation/install/joi/boolean.js +3 -3
- package/lib/dataflow/propagation/install/joi/boolean.test.js +1 -1
- package/lib/dataflow/propagation/install/joi/expression.js +3 -3
- package/lib/dataflow/propagation/install/joi/expression.test.js +1 -1
- package/lib/dataflow/propagation/install/joi/index.js +3 -3
- package/lib/dataflow/propagation/install/joi/keys.js +3 -3
- package/lib/dataflow/propagation/install/joi/number.js +3 -3
- package/lib/dataflow/propagation/install/joi/number.test.js +1 -1
- package/lib/dataflow/propagation/install/joi/object.js +1 -1
- package/lib/dataflow/propagation/install/joi/object.test.js +1 -1
- package/lib/dataflow/propagation/install/joi/ref.test.js +4 -4
- package/lib/dataflow/propagation/install/joi/string-schema.js +4 -4
- package/lib/dataflow/propagation/install/joi/string-schema.test.js +4 -4
- package/lib/dataflow/propagation/install/joi/values.js +3 -3
- package/lib/dataflow/propagation/install/mongoose/schema-map.js +4 -4
- package/lib/dataflow/propagation/install/mongoose/schema-mixed.js +4 -4
- package/lib/dataflow/propagation/install/mongoose/schema-string.js +4 -4
- package/lib/dataflow/propagation/install/mustache-escape.js +3 -4
- package/lib/dataflow/propagation/install/mustache-escape.test.js +1 -1
- package/lib/dataflow/propagation/install/mysql-connection-escape.js +22 -14
- package/lib/dataflow/propagation/install/mysql-connection-escape.test.js +1 -1
- package/lib/dataflow/propagation/install/parse-int.js +2 -3
- package/lib/dataflow/propagation/install/path/basename.js +3 -4
- package/lib/dataflow/propagation/install/path/dirname.js +3 -4
- package/lib/dataflow/propagation/install/path/extname.js +3 -4
- package/lib/dataflow/propagation/install/path/format.js +3 -4
- package/lib/dataflow/propagation/install/path/join-and-resolve.js +3 -4
- package/lib/dataflow/propagation/install/path/normalize.js +4 -5
- package/lib/dataflow/propagation/install/path/parse.js +3 -4
- package/lib/dataflow/propagation/install/path/relative.js +4 -5
- package/lib/dataflow/propagation/install/path/toNamespacedPath.js +3 -4
- package/lib/dataflow/propagation/install/pug/index.js +3 -4
- package/lib/dataflow/propagation/install/pug-runtime-escape.js +3 -4
- package/lib/dataflow/propagation/install/querystring/escape.js +3 -4
- package/lib/dataflow/propagation/install/querystring/escape.test.js +1 -1
- package/lib/dataflow/propagation/install/querystring/parse.js +3 -4
- package/lib/dataflow/propagation/install/querystring/parse.test.js +1 -1
- package/lib/dataflow/propagation/install/querystring/stringify.js +3 -4
- package/lib/dataflow/propagation/install/querystring/stringify.test.js +1 -1
- package/lib/dataflow/propagation/install/reg-exp-prototype-exec.js +2 -3
- package/lib/dataflow/propagation/install/send.js +3 -3
- package/lib/dataflow/propagation/install/sequelize/query-generator.js +3 -3
- package/lib/dataflow/propagation/install/sequelize/query-generator.test.js +2 -1
- package/lib/dataflow/propagation/install/sequelize/sql-string.js +5 -5
- package/lib/dataflow/propagation/install/sql-template-strings.js +3 -3
- package/lib/dataflow/propagation/install/string/concat.js +2 -3
- package/lib/dataflow/propagation/install/string/format-methods.js +2 -3
- package/lib/dataflow/propagation/install/string/html-methods.js +3 -4
- package/lib/dataflow/propagation/install/string/match-all.js +2 -3
- package/lib/dataflow/propagation/install/string/match.js +2 -3
- package/lib/dataflow/propagation/install/string/replace.js +2 -3
- package/lib/dataflow/propagation/install/string/slice.js +2 -3
- package/lib/dataflow/propagation/install/string/split.js +2 -3
- package/lib/dataflow/propagation/install/string/substring.js +2 -3
- package/lib/dataflow/propagation/install/string/trim.js +2 -3
- package/lib/dataflow/propagation/install/unescape.js +2 -3
- package/lib/dataflow/propagation/install/url/domain-parsers.js +3 -4
- package/lib/dataflow/propagation/install/url/parse.js +3 -4
- package/lib/dataflow/propagation/install/url/parse.test.js +2 -2
- package/lib/dataflow/propagation/install/url/searchParams.js +3 -4
- package/lib/dataflow/propagation/install/url/url.js +3 -4
- package/lib/dataflow/propagation/install/util-format.js +3 -4
- package/lib/dataflow/propagation/install/validator/hooks.js +9 -9
- package/lib/dataflow/sinks/install/child-process.js +5 -6
- package/lib/dataflow/sinks/install/eval.js +2 -3
- package/lib/dataflow/sinks/install/express/reflected-xss.js +2 -3
- package/lib/dataflow/sinks/install/express/unvalidated-redirect.js +2 -3
- package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +3 -4
- package/lib/dataflow/sinks/install/fs.js +4 -5
- package/lib/dataflow/sinks/install/fs.test.js +2 -2
- package/lib/dataflow/sinks/install/function.js +2 -3
- package/lib/dataflow/sinks/install/hapi/unvalidated-redirect.js +3 -4
- package/lib/dataflow/sinks/install/http/request.js +3 -4
- package/lib/dataflow/sinks/install/http/request.test.js +2 -2
- package/lib/dataflow/sinks/install/http/server-response.js +5 -6
- package/lib/dataflow/sinks/install/http/server-response.test.js +3 -3
- package/lib/dataflow/sinks/install/koa/unvalidated-redirect.js +3 -4
- package/lib/dataflow/sinks/install/libxmljs.js +4 -5
- package/lib/dataflow/sinks/install/libxmljs.test.js +2 -2
- package/lib/dataflow/sinks/install/marsdb.js +3 -4
- package/lib/dataflow/sinks/install/marsdb.test.js +3 -3
- package/lib/dataflow/sinks/install/mongodb.js +3 -4
- package/lib/dataflow/sinks/install/mongodb.test.js +2 -6
- package/lib/dataflow/sinks/install/mssql.js +4 -5
- package/lib/dataflow/sinks/install/mssql.test.js +2 -2
- package/lib/dataflow/sinks/install/mysql.js +4 -5
- package/lib/dataflow/sinks/install/mysql.test.js +2 -11
- package/lib/dataflow/sinks/install/node-serialize.js +3 -4
- package/lib/dataflow/sinks/install/node-serialize.test.js +1 -3
- package/lib/dataflow/sinks/install/postgres.js +5 -6
- package/lib/dataflow/sinks/install/postgres.test.js +3 -9
- package/lib/dataflow/sinks/install/restify.js +3 -4
- package/lib/dataflow/sinks/install/restify.test.js +3 -5
- package/lib/dataflow/sinks/install/sequelize.js +3 -4
- package/lib/dataflow/sinks/install/sqlite3.js +3 -4
- package/lib/dataflow/sinks/install/vm.js +3 -4
- package/lib/dataflow/sources/install/body-parser1.js +2 -3
- package/lib/dataflow/sources/install/busboy.js +3 -4
- package/lib/dataflow/sources/install/busboy.test.js +2 -2
- package/lib/dataflow/sources/install/cookie-parser1.js +2 -3
- package/lib/dataflow/sources/install/express/params.js +1 -2
- package/lib/dataflow/sources/install/express/parsedUrl.js +1 -2
- package/lib/dataflow/sources/install/express/parsedUrl.test.js +9 -8
- package/lib/dataflow/sources/install/fastify/fastify.js +2 -3
- package/lib/dataflow/sources/install/fastify/fastify.test.js +3 -6
- package/lib/dataflow/sources/install/formidable1.js +2 -3
- package/lib/dataflow/sources/install/hapi/hapi.js +1 -2
- package/lib/dataflow/sources/install/http.js +2 -3
- package/lib/dataflow/sources/install/http.test.js +2 -2
- package/lib/dataflow/sources/install/koa/koa-bodyparsers.js +3 -5
- package/lib/dataflow/sources/install/koa/koa-multer.js +3 -4
- package/lib/dataflow/sources/install/koa/koa-multer.test.js +1 -1
- package/lib/dataflow/sources/install/koa/koa-routers.js +3 -4
- package/lib/dataflow/sources/install/koa/koa2.js +2 -4
- package/lib/dataflow/sources/install/multer1.js +2 -3
- package/lib/dataflow/sources/install/multer1.test.js +1 -3
- package/lib/dataflow/sources/install/qs6.js +2 -3
- package/lib/dataflow/sources/install/querystring.js +2 -3
- package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.js +2 -3
- package/lib/dataflow/sources/install/restify/fieldedTextBodyParser.test.js +1 -1
- package/lib/dataflow/sources/install/restify/jsonBodyParser.js +2 -3
- package/lib/dataflow/sources/install/restify/jsonBodyParser.test.js +1 -1
- package/lib/dataflow/sources/install/restify/router.js +2 -3
- package/lib/dataflow/sources/install/restify/router.test.js +1 -1
- package/lib/get-source-context.js +58 -39
- package/lib/get-source-context.test.js +103 -78
- package/lib/index.d.ts +3 -9
- package/lib/response-scanning/install/http.js +3 -3
- package/lib/response-scanning/install/http.test.js +2 -2
- package/lib/session-configuration/install/express-session.js +1 -1
- package/lib/session-configuration/install/express-session.test.js +1 -3
- package/lib/session-configuration/install/fastify-cookie.js +1 -1
- package/lib/session-configuration/install/fastify-cookie.test.js +1 -3
- package/lib/session-configuration/install/koa.js +1 -1
- package/lib/session-configuration/install/koa.test.js +1 -1
- package/package.json +11 -11
- package/lib/constants.js +0 -26
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { InputType: { BODY } } = require('@contrast/common');
|
|
19
|
-
const { InstrumentationType: { SOURCE } } = require('../../../../constants');
|
|
20
19
|
const { patchType } = require('../../common');
|
|
21
20
|
|
|
22
21
|
module.exports = function init(core) {
|
|
@@ -33,7 +32,7 @@ module.exports = function init(core) {
|
|
|
33
32
|
return core.assess.dataflow.sources.restifyInstrumentation.fieldedTextBodyParser = {
|
|
34
33
|
install() {
|
|
35
34
|
depHooks.resolve(
|
|
36
|
-
{ name: 'restify', file: 'lib/plugins/fieldedTextBodyParser.js', version: '>=8' },
|
|
35
|
+
{ name: 'restify', file: 'lib/plugins/fieldedTextBodyParser.js', version: '>=8 <12' },
|
|
37
36
|
(fieldedTextBodyParser) => patcher.patch(fieldedTextBodyParser, {
|
|
38
37
|
name: 'restify.plugins.fieldedTextBodyParser',
|
|
39
38
|
patchType,
|
|
@@ -44,7 +43,7 @@ module.exports = function init(core) {
|
|
|
44
43
|
pre(data) {
|
|
45
44
|
const { args: [req, , next], name, funcKey } = data;
|
|
46
45
|
data.args[2] = function contrastNext(...args) {
|
|
47
|
-
const sourceContext = getSourceContext(
|
|
46
|
+
const sourceContext = getSourceContext();
|
|
48
47
|
|
|
49
48
|
if (!sourceContext) return next(...args);
|
|
50
49
|
|
|
@@ -20,7 +20,7 @@ describe('assess dataflow sources restify fieldedTextBodyParser', function () {
|
|
|
20
20
|
next = sinon.stub();
|
|
21
21
|
|
|
22
22
|
core.depHooks.resolve
|
|
23
|
-
.withArgs({ name: 'restify', file: 'lib/plugins/fieldedTextBodyParser.js', version: '>=8' })
|
|
23
|
+
.withArgs({ name: 'restify', file: 'lib/plugins/fieldedTextBodyParser.js', version: '>=8 <12' })
|
|
24
24
|
.callsFake((_, cb) => {
|
|
25
25
|
fieldedTextBodyParserStub = cb(fieldedTextBodyParserStub);
|
|
26
26
|
});
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { InputType: { JSON_VALUE } } = require('@contrast/common');
|
|
19
|
-
const { InstrumentationType: { SOURCE } } = require('../../../../constants');
|
|
20
19
|
const { patchType } = require('../../common');
|
|
21
20
|
|
|
22
21
|
module.exports = function init(core) {
|
|
@@ -33,7 +32,7 @@ module.exports = function init(core) {
|
|
|
33
32
|
return core.assess.dataflow.sources.restifyInstrumentation.jsonBodyParser = {
|
|
34
33
|
install() {
|
|
35
34
|
depHooks.resolve(
|
|
36
|
-
{ name: 'restify', file: 'lib/plugins/jsonBodyParser.js', version: '>=8' },
|
|
35
|
+
{ name: 'restify', file: 'lib/plugins/jsonBodyParser.js', version: '>=8 <12' },
|
|
37
36
|
(jsonBodyParser) => patcher.patch(jsonBodyParser, {
|
|
38
37
|
name: 'restify.plugins.jsonBodyParser',
|
|
39
38
|
patchType,
|
|
@@ -48,7 +47,7 @@ module.exports = function init(core) {
|
|
|
48
47
|
const { args: [req, , next], name, funcKey } = data;
|
|
49
48
|
|
|
50
49
|
data.args[2] = function contrastNext(...args) {
|
|
51
|
-
const sourceContext = getSourceContext(
|
|
50
|
+
const sourceContext = getSourceContext();
|
|
52
51
|
|
|
53
52
|
if (!sourceContext) {
|
|
54
53
|
return next(...args);
|
|
@@ -23,7 +23,7 @@ describe('assess dataflow sources restify jsonBodyParser', function () {
|
|
|
23
23
|
next = sinon.stub();
|
|
24
24
|
|
|
25
25
|
core.depHooks.resolve
|
|
26
|
-
.withArgs({ name: 'restify', file: 'lib/plugins/jsonBodyParser.js', version: '>=8' })
|
|
26
|
+
.withArgs({ name: 'restify', file: 'lib/plugins/jsonBodyParser.js', version: '>=8 <12' })
|
|
27
27
|
.callsFake((_, cb) => {
|
|
28
28
|
jsonBodyParserStub = cb(jsonBodyParserStub);
|
|
29
29
|
});
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { InputType: { URL_PARAMETER } } = require('@contrast/common');
|
|
19
|
-
const { InstrumentationType: { SOURCE } } = require('../../../../constants');
|
|
20
19
|
const { patchType } = require('../../common');
|
|
21
20
|
|
|
22
21
|
module.exports = function init(core) {
|
|
@@ -33,13 +32,13 @@ module.exports = function init(core) {
|
|
|
33
32
|
return core.assess.dataflow.sources.restifyInstrumentation.router = {
|
|
34
33
|
install() {
|
|
35
34
|
depHooks.resolve(
|
|
36
|
-
{ name: 'restify', file: 'lib/router.js', version: '>=8' },
|
|
35
|
+
{ name: 'restify', file: 'lib/router.js', version: '>=8 <12' },
|
|
37
36
|
(Router) => {
|
|
38
37
|
patcher.patch(Router.prototype, 'lookup', {
|
|
39
38
|
name: 'restify.Router.prototype.lookup',
|
|
40
39
|
patchType,
|
|
41
40
|
post({ args: [req], hooked, orig, name, funcKey }) {
|
|
42
|
-
const sourceContext = getSourceContext(
|
|
41
|
+
const sourceContext = getSourceContext();
|
|
43
42
|
|
|
44
43
|
if (!sourceContext) {
|
|
45
44
|
return;
|
|
@@ -17,7 +17,7 @@ describe('assess dataflow sources restify router', function () {
|
|
|
17
17
|
req = {};
|
|
18
18
|
|
|
19
19
|
core.depHooks.resolve
|
|
20
|
-
.withArgs({ name: 'restify', file: 'lib/router.js', version: '>=8' })
|
|
20
|
+
.withArgs({ name: 'restify', file: 'lib/router.js', version: '>=8 <12' })
|
|
21
21
|
.yields(Router);
|
|
22
22
|
|
|
23
23
|
init(core).install();
|
|
@@ -12,16 +12,17 @@
|
|
|
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
|
-
|
|
15
|
+
// @ts-check
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
/** @typedef {import('@contrast/assess').SourceContext} SourceContext */
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* @param {{
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* scopes: import('@contrast/scopes').Scopes
|
|
22
|
+
* readonly config: import('@contrast/config').Config;
|
|
23
|
+
* readonly logger: import('@contrast/logger').Logger;
|
|
24
|
+
* readonly scopes: import('@contrast/scopes').Scopes;
|
|
25
|
+
* assess: import('@contrast/assess').Assess;
|
|
25
26
|
* }} core
|
|
26
27
|
*/
|
|
27
28
|
module.exports = function(core) {
|
|
@@ -32,63 +33,81 @@ module.exports = function(core) {
|
|
|
32
33
|
} = core;
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
*
|
|
36
|
-
* - assess store is available
|
|
36
|
+
* Used by all propagator instrumentation to make sure that:
|
|
37
|
+
* - the assess store is available
|
|
37
38
|
* - assess is enabled for this request
|
|
38
39
|
* - instrumentation is not locked
|
|
39
40
|
*
|
|
40
41
|
* Any code that does not use this function and directly accesses the assess
|
|
41
42
|
* source context will recurse until the stack is blown if the following
|
|
42
43
|
* checks are not correctly made.
|
|
43
|
-
*
|
|
44
|
-
* @param {import('./constants.js').InstrumentationType} type
|
|
45
|
-
* @returns {import('@contrast/assess').SourceContext|null} the assess store
|
|
44
|
+
* @returns {SourceContext | null}
|
|
46
45
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
core.assess.getPropagatorContext = function getPropagatorContext() {
|
|
47
|
+
if (instrumentation.isLocked()) return null;
|
|
49
48
|
|
|
50
49
|
// the following logging used to be done by the caller, but has been moved
|
|
51
50
|
// here as opposed to overloading `ctx.policy` with a special value so the
|
|
52
51
|
// caller could determine whether no source context was available or the
|
|
53
52
|
// request is being intentionally excluded. A negative of this is that the
|
|
54
53
|
// function name is not available to be included in the log.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// that the caller previously logged, we generate a stack trace to
|
|
59
|
-
// capture that information.
|
|
60
|
-
const err = new Error('No source context found');
|
|
61
|
-
core.logger.warn({ err }, 'assess running outside of request scope');
|
|
62
|
-
}
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
54
|
+
const ctx = sources.getStore()?.assess;
|
|
55
|
+
if (!ctx) return null;
|
|
56
|
+
|
|
65
57
|
// there is a context, but if policy is null then assess is intentionally
|
|
66
58
|
// disabled (i.e., url exclusion or the request is not sampled).
|
|
67
59
|
if (!ctx.policy) {
|
|
68
60
|
core.logger.trace('Assess intentionally disabled for this request');
|
|
69
61
|
return null;
|
|
70
|
-
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (ctx.propagationEventsCount > config.assess.max_propagation_events) return null;
|
|
65
|
+
|
|
66
|
+
return ctx;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {import('@contrast/common').Rule} ruleId
|
|
71
|
+
* @returns {SourceContext | null}
|
|
72
|
+
*/
|
|
73
|
+
core.assess.getSinkContext = function getSinkContext(ruleId) {
|
|
74
|
+
if (instrumentation.isLocked()) return null;
|
|
75
|
+
|
|
76
|
+
const ctx = sources.getStore()?.assess;
|
|
77
|
+
if (!ctx) return null;
|
|
78
|
+
|
|
79
|
+
if (!ctx.policy) {
|
|
80
|
+
core.logger.trace('Assess intentionally disabled for this request');
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (ruleId && !ctx.policy?.enabledRules?.has?.(ruleId) || ruleScopes.isLocked(ruleId)) return null;
|
|
85
|
+
|
|
86
|
+
return ctx;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** @returns {SourceContext | null} */
|
|
90
|
+
core.assess.getSourceContext = function getSourceContext() {
|
|
91
|
+
if (instrumentation.isLocked()) return null;
|
|
92
|
+
|
|
93
|
+
const ctx = sources.getStore()?.assess;
|
|
94
|
+
if (!ctx) {
|
|
95
|
+
// because this is a real error, and we don't have the function name
|
|
96
|
+
// that the caller previously logged, we generate a stack trace to
|
|
97
|
+
// capture that information.
|
|
98
|
+
const err = new Error('No source context found');
|
|
99
|
+
core.logger.warn({ err }, 'assess running outside of request scope');
|
|
71
100
|
return null;
|
|
72
101
|
}
|
|
73
102
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
case InstrumentationType.SOURCE: {
|
|
80
|
-
if (ctx.sourceEventsCount > config.assess.max_context_source_events) return null;
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
case InstrumentationType.RULE: {
|
|
85
|
-
const [ruleId] = rest;
|
|
86
|
-
if (!ruleId) break;
|
|
87
|
-
if (!ctx.policy?.enabledRules?.has?.(ruleId) || ruleScopes.isLocked(ruleId)) return null;
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
103
|
+
if (!ctx.policy) {
|
|
104
|
+
core.logger.trace('Assess intentionally disabled for this request');
|
|
105
|
+
return null;
|
|
90
106
|
}
|
|
91
107
|
|
|
108
|
+
if (ctx.sourceEventsCount > config.assess.max_context_source_events) return null;
|
|
109
|
+
|
|
92
110
|
return ctx;
|
|
93
111
|
};
|
|
112
|
+
|
|
94
113
|
};
|
|
@@ -4,9 +4,28 @@ const { expect } = require('chai');
|
|
|
4
4
|
const { Event } = require('@contrast/common');
|
|
5
5
|
const { initAssessFixture } = require('@contrast/test/fixtures');
|
|
6
6
|
const sinon = require('sinon');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
|
|
8
|
+
function execStoreAssertions(assessStore) {
|
|
9
|
+
expect(assessStore).to.be.an('object').and.deep.include({
|
|
10
|
+
reqData: {
|
|
11
|
+
ip: '127.0.0.1',
|
|
12
|
+
httpVersion: '1.1',
|
|
13
|
+
method: 'get',
|
|
14
|
+
headers: {
|
|
15
|
+
'content-type': 'text/html',
|
|
16
|
+
language: 'en',
|
|
17
|
+
referer: 'http://fake.url.foo',
|
|
18
|
+
},
|
|
19
|
+
uriPath: '/index',
|
|
20
|
+
queries: '_id=123',
|
|
21
|
+
contentType: 'text/html'
|
|
22
|
+
},
|
|
23
|
+
responseData: {},
|
|
24
|
+
sourceEventsCount: 0,
|
|
25
|
+
propagationEventsCount: 0,
|
|
26
|
+
});
|
|
27
|
+
expect(assessStore.policy.enabledRules).to.have.length.greaterThan(5);
|
|
28
|
+
}
|
|
10
29
|
|
|
11
30
|
describe('assess getSourceContext', function () {
|
|
12
31
|
let core, simulateRequestScope;
|
|
@@ -15,113 +34,74 @@ describe('assess getSourceContext', function () {
|
|
|
15
34
|
({ core, simulateRequestScope } = initAssessFixture());
|
|
16
35
|
});
|
|
17
36
|
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
ip: '127.0.0.1',
|
|
22
|
-
httpVersion: '1.1',
|
|
23
|
-
method: 'get',
|
|
24
|
-
headers: {
|
|
25
|
-
'content-type': 'text/html',
|
|
26
|
-
language: 'en',
|
|
27
|
-
referer: 'http://fake.url.foo',
|
|
28
|
-
},
|
|
29
|
-
uriPath: '/index',
|
|
30
|
-
queries: '_id=123',
|
|
31
|
-
contentType: 'text/html'
|
|
32
|
-
},
|
|
33
|
-
responseData: {},
|
|
34
|
-
sourceEventsCount: 0,
|
|
35
|
-
propagationEventsCount: 0,
|
|
36
|
-
});
|
|
37
|
-
expect(assessStore.policy.enabledRules).to.have.length.greaterThan(5);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
it('return null when not in request scope', function() {
|
|
41
|
-
expect(core.assess.getSourceContext()).to.be.null;
|
|
42
|
-
expect(core.logger.warn.callCount).equal(0);
|
|
43
|
-
});
|
|
37
|
+
describe('getPropagatorContext()', function() {
|
|
38
|
+
it('returns null when instrumentation is locked', function () {
|
|
39
|
+
sinon.stub(core.scopes.instrumentation, 'isLocked').returns(true);
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
expect(core.logger.warn.args[0][1]).to.include('outside of request scope');
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('return null and log when intentionally disabled (policy === null)', function() {
|
|
52
|
-
simulateRequestScope(() => {
|
|
53
|
-
core.scopes.sources.getStore().assess.policy = null;
|
|
54
|
-
expect(core.assess.getSourceContext()).null;
|
|
55
|
-
// lots of code writes trace logs
|
|
56
|
-
const last = core.logger.trace.lastCall;
|
|
57
|
-
expect(last.args[0]).to.include('Assess intentionally disabled');
|
|
41
|
+
simulateRequestScope(() => {
|
|
42
|
+
expect(core.assess.getPropagatorContext()).to.be.null;
|
|
43
|
+
});
|
|
58
44
|
});
|
|
59
|
-
});
|
|
60
45
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
core.scopes.instrumentation.isLocked = sinon.stub().returns(true);
|
|
64
|
-
expect(core.assess.getSourceContext()).null;
|
|
65
|
-
if (core.logger.trace.called) {
|
|
66
|
-
expect(core.logger.trace.lastCall.args[0]).not.include('Assess intentionally disabled');
|
|
67
|
-
}
|
|
46
|
+
it('returns null when not in request scope', function() {
|
|
47
|
+
expect(core.assess.getPropagatorContext()).to.be.null;
|
|
68
48
|
});
|
|
69
|
-
});
|
|
70
49
|
|
|
71
|
-
|
|
72
|
-
it('returns assess store when in request scope', function() {
|
|
50
|
+
it('returns null when assess is disabled by policy', function() {
|
|
73
51
|
simulateRequestScope(() => {
|
|
74
|
-
|
|
75
|
-
|
|
52
|
+
expect(core.assess.getPropagatorContext()).to.be.null;
|
|
53
|
+
expect(core.logger.trace).to.have.been.calledWith('Assess intentionally disabled for this request');
|
|
54
|
+
}, { assess: { policy: undefined } });
|
|
76
55
|
});
|
|
77
|
-
});
|
|
78
56
|
|
|
79
|
-
|
|
80
|
-
it('returns assess store when max source event threshold is not met', function() {
|
|
57
|
+
it('returns assess store when max propagation event threshold is not met', function() {
|
|
81
58
|
simulateRequestScope(() => {
|
|
82
|
-
execStoreAssertions(core.assess.
|
|
59
|
+
execStoreAssertions(core.assess.getPropagatorContext());
|
|
83
60
|
});
|
|
84
61
|
});
|
|
85
62
|
|
|
86
|
-
it('returns
|
|
63
|
+
it('returns null when max propagation event threshold is exceeded', function() {
|
|
64
|
+
core.config.setValue('assess.max_propagation_events', 10);
|
|
65
|
+
|
|
87
66
|
simulateRequestScope(() => {
|
|
88
|
-
core.
|
|
89
|
-
|
|
90
|
-
expect(core.assess.getSourceContext(SOURCE)).to.be.null;
|
|
91
|
-
});
|
|
67
|
+
expect(core.assess.getPropagatorContext()).to.be.null;
|
|
68
|
+
}, { assess: { propagationEventsCount: 11 } });
|
|
92
69
|
});
|
|
93
70
|
});
|
|
94
71
|
|
|
95
|
-
describe('
|
|
96
|
-
it('returns
|
|
72
|
+
describe('getSinkContext()', function() {
|
|
73
|
+
it('returns null when instrumentation is locked', function () {
|
|
74
|
+
sinon.stub(core.scopes.instrumentation, 'isLocked').returns(true);
|
|
75
|
+
|
|
97
76
|
simulateRequestScope(() => {
|
|
98
|
-
|
|
77
|
+
expect(core.assess.getSinkContext()).to.be.null;
|
|
99
78
|
});
|
|
100
79
|
});
|
|
101
80
|
|
|
102
|
-
it('returns
|
|
81
|
+
it('returns null when not in request scope', function() {
|
|
82
|
+
expect(core.assess.getSinkContext()).to.be.null;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('returns null when assess is disabled by policy', function() {
|
|
103
86
|
simulateRequestScope(() => {
|
|
104
|
-
core.
|
|
105
|
-
core.
|
|
106
|
-
|
|
107
|
-
});
|
|
87
|
+
expect(core.assess.getSinkContext()).to.be.null;
|
|
88
|
+
expect(core.logger.trace).to.have.been.calledWith('Assess intentionally disabled for this request');
|
|
89
|
+
}, { assess: { policy: undefined } });
|
|
108
90
|
});
|
|
109
|
-
});
|
|
110
91
|
|
|
111
|
-
describe('.getSourceContext(RULE, ruleId?)', function() {
|
|
112
92
|
it('returns assess store when ruleId is not passed', function() {
|
|
113
93
|
simulateRequestScope(() => {
|
|
114
|
-
execStoreAssertions(core.assess.getSourceContext(
|
|
94
|
+
execStoreAssertions(core.assess.getSourceContext());
|
|
115
95
|
});
|
|
116
96
|
});
|
|
117
97
|
|
|
118
98
|
it('returns assess store when ruleId provided is enabled in the policy', function() {
|
|
119
99
|
simulateRequestScope(() => {
|
|
120
|
-
execStoreAssertions(core.assess.
|
|
100
|
+
execStoreAssertions(core.assess.getSinkContext('reflected-xss'));
|
|
121
101
|
});
|
|
122
102
|
});
|
|
123
103
|
|
|
124
|
-
it('returns
|
|
104
|
+
it('returns null when ruleId provided is not enabled in the policy', function() {
|
|
125
105
|
core.messages.emit(Event.SERVER_SETTINGS_UPDATE, {
|
|
126
106
|
assess: {
|
|
127
107
|
['reflected-xss']: { enable: false },
|
|
@@ -129,8 +109,53 @@ describe('assess getSourceContext', function () {
|
|
|
129
109
|
});
|
|
130
110
|
|
|
131
111
|
simulateRequestScope(() => {
|
|
132
|
-
expect(core.assess.
|
|
112
|
+
expect(core.assess.getSinkContext('reflected-xss')).to.be.null;
|
|
133
113
|
});
|
|
134
114
|
});
|
|
135
115
|
});
|
|
116
|
+
|
|
117
|
+
describe('getSourceContext()', function() {
|
|
118
|
+
it('returns null when instrumentation is locked', function () {
|
|
119
|
+
sinon.stub(core.scopes.instrumentation, 'isLocked').returns(true);
|
|
120
|
+
|
|
121
|
+
simulateRequestScope(() => {
|
|
122
|
+
expect(core.assess.getSourceContext()).to.be.null;
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('returns null when not in request scope', function() {
|
|
127
|
+
expect(core.assess.getSourceContext()).to.be.null;
|
|
128
|
+
expect(core.logger.warn).to.have.been.calledWithMatch(
|
|
129
|
+
sinon.match.object,
|
|
130
|
+
'assess running outside of request scope',
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('returns null when assess is disabled by policy', function() {
|
|
135
|
+
simulateRequestScope(() => {
|
|
136
|
+
expect(core.assess.getSourceContext()).to.be.null;
|
|
137
|
+
expect(core.logger.trace).to.have.been.calledWith('Assess intentionally disabled for this request');
|
|
138
|
+
}, { assess: { policy: undefined } });
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('returns assess store when in request scope', function() {
|
|
142
|
+
simulateRequestScope(() => {
|
|
143
|
+
execStoreAssertions(core.assess.getSourceContext());
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('returns assess store when max source event threshold is not met', function() {
|
|
148
|
+
simulateRequestScope(() => {
|
|
149
|
+
execStoreAssertions(core.assess.getSourceContext());
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('returns null when max source event threshold is exceeded', function() {
|
|
154
|
+
core.config.setValue('assess.max_context_source_events', 10);
|
|
155
|
+
|
|
156
|
+
simulateRequestScope(() => {
|
|
157
|
+
expect(core.assess.getSourceContext()).to.be.null;
|
|
158
|
+
}, { assess: { sourceEventsCount: 11 } });
|
|
159
|
+
});
|
|
160
|
+
});
|
|
136
161
|
});
|
package/lib/index.d.ts
CHANGED
|
@@ -37,12 +37,6 @@ export interface Core extends _Core {
|
|
|
37
37
|
metrics: any;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export enum InstrumentationType {
|
|
41
|
-
SOURCE = 'source',
|
|
42
|
-
PROPAGATOR = 'propagator',
|
|
43
|
-
RULE = 'rule',
|
|
44
|
-
}
|
|
45
|
-
|
|
46
40
|
export interface SourceContext {
|
|
47
41
|
reqData: object,
|
|
48
42
|
responseData: {
|
|
@@ -74,14 +68,14 @@ export interface RuleState {
|
|
|
74
68
|
|
|
75
69
|
export interface Assess {
|
|
76
70
|
getPolicy(): Policy,
|
|
77
|
-
|
|
71
|
+
getPropagatorContext(): SourceContext | null,
|
|
72
|
+
getSinkContext(ruleId: Rule): SourceContext | null,
|
|
73
|
+
getSourceContext(): SourceContext | null,
|
|
78
74
|
makeSourceContext(req: IncomingMessage, res: ServerResponse): SourceContext,
|
|
79
75
|
ruleScopes: RuleScopes,
|
|
80
76
|
ruleState: RuleState,
|
|
81
77
|
}
|
|
82
78
|
|
|
83
|
-
export function getSourceContext(instrType?: InstrumentationType, ops?: any): SourceContext;
|
|
84
|
-
|
|
85
79
|
declare function init(core: Core): Assess;
|
|
86
80
|
|
|
87
81
|
export = init;
|
|
@@ -89,7 +89,7 @@ module.exports = function(core) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
http.install = function() {
|
|
92
|
-
depHooks.resolve({ name: 'http' }, (http) => {
|
|
92
|
+
depHooks.resolve({ name: 'http', version: '*' }, (http) => {
|
|
93
93
|
{
|
|
94
94
|
const name = 'http.ServerResponse.prototype.write';
|
|
95
95
|
patcher.patch(http.ServerResponse.prototype, 'write', {
|
|
@@ -116,7 +116,7 @@ module.exports = function(core) {
|
|
|
116
116
|
}
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
-
depHooks.resolve({ name: 'http2' }, (http2) => {
|
|
119
|
+
depHooks.resolve({ name: 'http2', version: '*' }, (http2) => {
|
|
120
120
|
// todo: NODE-3467
|
|
121
121
|
{
|
|
122
122
|
const name = 'http2.Http2ServerResponse.prototype.write';
|
|
@@ -147,7 +147,7 @@ module.exports = function(core) {
|
|
|
147
147
|
}
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
depHooks.resolve({ name: 'spdy', file: 'lib/spdy/response.js' }, (response) => {
|
|
150
|
+
depHooks.resolve({ name: 'spdy', version: '<5', file: 'lib/spdy/response.js' }, (response) => {
|
|
151
151
|
patcher.patch(response, 'end', {
|
|
152
152
|
name: 'spdy.response.end',
|
|
153
153
|
patchType: 'test',
|
|
@@ -95,10 +95,10 @@ describe('assess response scanning sinks http', function () {
|
|
|
95
95
|
|
|
96
96
|
http = require('./http')(core);
|
|
97
97
|
core.depHooks.resolve
|
|
98
|
-
.withArgs({ name: 'http' })
|
|
98
|
+
.withArgs(sinon.match({ name: 'http' }))
|
|
99
99
|
.yields(httpModule);
|
|
100
100
|
core.depHooks.resolve
|
|
101
|
-
.withArgs({ name: 'http2' })
|
|
101
|
+
.withArgs(sinon.match({ name: 'http2' }))
|
|
102
102
|
.yields(http2Module);
|
|
103
103
|
|
|
104
104
|
http.install();
|
|
@@ -41,7 +41,7 @@ module.exports = function (core) {
|
|
|
41
41
|
const expressSession = core.assess.sessionConfiguration.expressSession = {};
|
|
42
42
|
|
|
43
43
|
expressSession.install = function () {
|
|
44
|
-
return depHooks.resolve({ name: 'express-session' }, (session) => {
|
|
44
|
+
return depHooks.resolve({ name: 'express-session', version: '<2' }, (session) => {
|
|
45
45
|
// Return the hooked function as the export.
|
|
46
46
|
const hooked = patcher.patch(session, {
|
|
47
47
|
name: 'express.hookedSessionConstructor',
|
|
@@ -16,9 +16,7 @@ describe('assess session-configuration http', function () {
|
|
|
16
16
|
|
|
17
17
|
beforeEach(function () {
|
|
18
18
|
({ core, simulateRequestScope } = initAssessFixture());
|
|
19
|
-
core.depHooks.resolve
|
|
20
|
-
.withArgs({ name: 'express-session' })
|
|
21
|
-
.yields(ExpressSession);
|
|
19
|
+
core.depHooks.resolve.yields(ExpressSession);
|
|
22
20
|
|
|
23
21
|
sinon.spy(core.assess.sessionConfiguration, 'handleHttpOnly');
|
|
24
22
|
sinon.spy(core.assess.sessionConfiguration, 'handleSecure');
|
|
@@ -40,7 +40,7 @@ module.exports = function (core) {
|
|
|
40
40
|
|
|
41
41
|
return core.assess.sessionConfiguration.fastifyCookie = {
|
|
42
42
|
install () {
|
|
43
|
-
depHooks.resolve({ name: '@fastify/cookie' }, (_export) => {
|
|
43
|
+
depHooks.resolve({ name: '@fastify/cookie', version: '<12' }, (_export) => {
|
|
44
44
|
const patched = patcher.patch(_export, {
|
|
45
45
|
name: 'express.hookedSessionConstructor',
|
|
46
46
|
patchType,
|
|
@@ -26,9 +26,7 @@ describe('assess session-configuration @fastify/cookie', function () {
|
|
|
26
26
|
addHook: sinon.stub().yields({}, reply),
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
core.depHooks.resolve
|
|
30
|
-
.withArgs({ name: '@fastify/cookie' })
|
|
31
|
-
.yields(mockExport);
|
|
29
|
+
core.depHooks.resolve.yields(mockExport);
|
|
32
30
|
|
|
33
31
|
sinon.stub(core.assess.sessionConfiguration, 'reportFindings');
|
|
34
32
|
core.assess.sessionConfiguration.fastifyCookie.install();
|
|
@@ -39,7 +39,7 @@ module.exports = function (core) {
|
|
|
39
39
|
|
|
40
40
|
return core.assess.sessionConfiguration.koa = {
|
|
41
41
|
install () {
|
|
42
|
-
depHooks.resolve({ name: 'koa', version: '>=2.3.0' }, (Koa) => {
|
|
42
|
+
depHooks.resolve({ name: 'koa', version: '>=2.3.0 <3' }, (Koa) => {
|
|
43
43
|
patcher.patch(Koa.prototype, 'use', {
|
|
44
44
|
name: 'Koa.Application',
|
|
45
45
|
patchType,
|
|
@@ -30,7 +30,7 @@ describe('assess sessionConfiguration Koa', function () {
|
|
|
30
30
|
ctxMock.cookies = { set: sinon.stub() };
|
|
31
31
|
|
|
32
32
|
core.depHooks.resolve
|
|
33
|
-
.withArgs({ name: 'koa', version: '>=2.3.0' })
|
|
33
|
+
.withArgs({ name: 'koa', version: '>=2.3.0 <3' })
|
|
34
34
|
.yields(koaMock);
|
|
35
35
|
|
|
36
36
|
sinon.stub(core.assess.sessionConfiguration, 'reportFindings');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/assess",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.42.0",
|
|
4
4
|
"description": "Contrast service providing framework-agnostic Assess support",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -17,17 +17,17 @@
|
|
|
17
17
|
"test": "../scripts/test.sh"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@contrast/common": "1.
|
|
21
|
-
"@contrast/config": "1.
|
|
22
|
-
"@contrast/core": "1.
|
|
23
|
-
"@contrast/dep-hooks": "1.
|
|
20
|
+
"@contrast/common": "1.27.0",
|
|
21
|
+
"@contrast/config": "1.37.0",
|
|
22
|
+
"@contrast/core": "1.42.0",
|
|
23
|
+
"@contrast/dep-hooks": "1.11.0",
|
|
24
24
|
"@contrast/distringuish": "^5.1.0",
|
|
25
|
-
"@contrast/instrumentation": "1.
|
|
26
|
-
"@contrast/logger": "1.
|
|
27
|
-
"@contrast/patcher": "1.
|
|
28
|
-
"@contrast/rewriter": "1.
|
|
29
|
-
"@contrast/route-coverage": "1.
|
|
30
|
-
"@contrast/scopes": "1.
|
|
25
|
+
"@contrast/instrumentation": "1.21.0",
|
|
26
|
+
"@contrast/logger": "1.15.0",
|
|
27
|
+
"@contrast/patcher": "1.14.0",
|
|
28
|
+
"@contrast/rewriter": "1.18.0",
|
|
29
|
+
"@contrast/route-coverage": "1.32.0",
|
|
30
|
+
"@contrast/scopes": "1.12.0",
|
|
31
31
|
"semver": "^7.6.0"
|
|
32
32
|
}
|
|
33
33
|
}
|